/* ============================================================================ MOWDEW — Scheduled work & repairs (trust + transparency) The "no surprise charges" loop: the OWNER schedules a work item (filter replacement, repair, custom…) with a date and a cost → the HOMEOWNER gets it for approval BEFORE anyone shows up → once approved the PRO sees it on his day and marks it complete → it lands in the homeowner's timeline as a first-class card, right between regular visits. Heterogeneous timeline: visits, replacements, repairs — every event on the record. Loads after mowdew-kit (+ frame/data on mobile). Exposed on window. ========================================================================= */ /* ─── The finite work catalogue (+ custom) ──────────────────────────── */ const WORK_TYPES = [ { key: 'filter', label: 'Filter replacement', icon: 'filter', tone: 'aqua', cents: 18000, every: 'Every 6 months' }, { key: 'pump', label: 'Pump repair', icon: 'zap', tone: 'yellow', cents: 24000 }, { key: 'salt', label: 'Salt cell clean', icon: 'beaker', tone: 'blue', cents: 9000 }, { key: 'rescue', label: 'Green-to-clean rescue', icon: 'flask', tone: 'green', cents: 30000 }, { key: 'custom', label: 'Something else', icon: 'notes', tone: 'ink' }, ]; const WORK_TONE = { aqua: { bg: 'var(--aqua-50)', fg: 'var(--aqua-700)', solid: 'var(--aqua-500)' }, yellow:{ bg: 'var(--yellow-50)', fg: 'var(--yellow-700)', solid: 'var(--yellow-500)' }, blue: { bg: 'var(--blue-50)', fg: 'var(--blue-700)', solid: 'var(--blue-500)' }, green: { bg: 'var(--green-50)', fg: 'var(--green-700)', solid: 'var(--green-500)' }, ink: { bg: 'var(--ink-50)', fg: 'var(--ink-700)', solid: 'var(--ink-900)' }, }; /* Emma's work items — one awaiting her OK, one already on the record */ const WORKITEMS = [ { id: 'w1', type: 'filter', label: 'Filter replacement', date: 'Sat, Jun 14', cost: 18000, state: 'pending', note: 'Cartridge filters wear out every 6 months — this keeps the water (and the pump) healthy.' }, { id: 'w2', type: 'salt', label: 'Salt cell clean', date: 'Thu, Apr 24', cost: 9000, state: 'done', by: 'Marco', note: 'Descaled and inspected — cell is good for another season.' }, ]; /* mono record-mark for work states */ function WorkState({ state }) { const m = { pending: ['yellow', 'NEEDS YOUR OK'], sent: ['yellow', 'WAITING ON APPROVAL'], approved: ['green', 'APPROVED'], done: ['blue', 'DONE · ON THE RECORD'], }[state] || ['ink', state.toUpperCase()]; return {m[1]}; } /* ─── HOMEOWNER · upcoming approval card (top of the feed) ──────────── */ function HomeWorkUpcoming({ w = WORKITEMS[0], toast }) { const [state, setState] = React.useState(w.state); // pending | approved const t = WORK_TYPES.find(x => x.key === w.type) || WORK_TYPES[0]; const tone = WORK_TONE[t.tone]; return (
{w.label}
{w.date} · {fmtMoney(w.cost)}{t.every ? ` · ${t.every.toLowerCase()}` : ''}
Diego scheduled this — {w.note} Nothing happens (and nothing is billed) until you approve.
{state === 'pending' ? (
) : (
Approved — it'll appear in your timeline like any visit. The {fmtMoney(w.cost)} lands on your invoice only after it's done.
)}
); } /* ─── HOMEOWNER · completed work card (inside the timeline) ─────────── */ function HomeWorkDone({ w = WORKITEMS[1] }) { const t = WORK_TYPES.find(x => x.key === w.type) || WORK_TYPES[0]; const tone = WORK_TONE[t.tone]; return (
{w.label}
{w.date} · {w.by} · approved by you
Done
{w.note}
{fmtMoney(w.cost)} billed on your May invoice RECORDED
); } /* ─── PRO · scheduled work on his day ───────────────────────────────── */ function ProWorkCard({ toast }) { const [state, setState] = React.useState('approved'); // approved | done const w = WORKITEMS[0]; const t = WORK_TYPES.find(x => x.key === w.type); return (
approved by the homeowner}>Scheduled work
Crestline Ave pool · {w.label}
{w.date} · part you need: C-7468 cartridge
{state === 'done' ? Done : APPROVED}
{state === 'approved' ? ( <>
Homeowner approved it in advance — do the swap, snap a photo, mark it done.
) : (
On the record — it's already in the homeowner's timeline.
)}
); } /* ─── OWNER · "Work & repairs" card on customer detail (mobile) ─────── */ function OwnerWorkCardM({ c, go }) { const w = WORKITEMS[0]; return (
{w.label} · {fmtMoney(w.cost)}
{w.date} · waiting on {c.name.split(' ')[0]}'s OK
SENT
); } /* ─── OWNER · schedule-work screen (mobile) ─────────────────────────── */ function OwnerScheduleWorkM({ go, params, toast }) { const c = CUSTOMERS.find(x => x.id === (params && params.id)) || CUSTOMERS[1]; const equip = (window.equipFor ? window.equipFor(c.id) : []); const [eqId, setEqId] = React.useState((params && params.equip) || null); const [eqFree, setEqFree] = React.useState(''); const eqSel = equip.find(e => e.id === eqId) || null; const [type, setType] = React.useState('filter'); const [custom, setCustom] = React.useState(''); const [date, setDate] = React.useState('Sat, Jun 14'); const [cost, setCost] = React.useState(18000); const [sent, setSent] = React.useState(false); const t = WORK_TYPES.find(x => x.key === type); const pick = (k) => { setType(k); const wt = WORK_TYPES.find(x => x.key === k); if (wt.cents) setCost(wt.cents); }; const first = c.name.split(' ')[0]; if (sent) { return ( }> go('o_customer', { id: c.id })} />

Sent for approval

{first} just got it — {type === 'custom' ? (custom || 'Custom work') : t.label}, {date}, {fmtMoney(cost)}. Nothing happens until they approve, and the price they approve is the price they pay. No surprises at the invoice.

{eqSel && (
Linked to {(window.EQUIP_TYPES[eqSel.type] || {}).label} · {eqSel.make} — once it's done, it appends to that item's service history.
)}
); } return ( }> go('o_customer', { id: c.id })} /> {/* customer */}
{c.name}
{c.addr}, {c.city} · {c.svc}
{/* what */}
What's the work?
{WORK_TYPES.map(wt => { const on = type === wt.key; const tn = WORK_TONE[wt.tone]; return ( ); })}
{type === 'custom' && ( setCustom(e.target.value)} style={{ marginTop: 8, padding: '12px 13px', fontSize: 14 }} /> )}
{/* against which equipment — ties the repair to a tracked part */} {equip.length > 0 && (
Against which equipment? · optional
{equip.map(e => { const on = eqId === e.id; const et = window.EQUIP_TYPES[e.type] || {}; return ( ); })} {/* freeform — something not on the record yet */}
{eqId === 'free' && (
setEqFree(e.target.value)} placeholder="Name the part — e.g. waterline tile, deck drain" style={{ padding: '12px 13px', fontSize: 14 }} />
)} {eqSel &&
When done, this appends to {eqSel.make} {(window.EQUIP_TYPES[eqSel.type] || {}).label.toLowerCase()}'s service history.
} {eqId === 'free' && eqFree.trim() &&
Logged against "{eqFree.trim()}" — you can add it to the equipment record later.
}
)} {/* when */}
When?
{['Sat, Jun 14', 'Tue, Jun 17', 'Sat, Jun 21'].map(dd => ( ))}
{/* cost */}
Cost — {first} approves exactly this
$ setCost(Math.round((+e.target.value.replace(/[^0-9]/g, '') || 0) * 100))} inputMode="numeric" style={{ flex: 1, border: 'none', outline: 'none', padding: '13px 0', fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 20, background: 'transparent', width: '100%' }} /> parts & labor
{first} approves before anyone shows up; it bills only after the pro marks it done. That's the whole point — no end-of-year surprises.
); } /* ─── OWNER · desktop “Work & repairs” card (inline schedule form) ──── */ function OwnerWorkCardDesk({ c, toast }) { const [open, setOpen] = React.useState(false); const [type, setType] = React.useState('filter'); const [date, setDate] = React.useState('Sat, Jun 14'); const [cost, setCost] = React.useState(18000); const [items, setItems] = React.useState([{ ...WORKITEMS[0] }]); const first = c.name.split(' ')[0]; const send = () => { const t = WORK_TYPES.find(x => x.key === type); setItems(it => [{ id: 'w' + (it.length + 9), type, label: t.label, date, cost, state: 'sent' }, ...it]); setOpen(false); toast && toast(`Sent to ${first} for approval — ${t.label}, ${fmtMoney(cost)}`, 'aqua'); }; return (
Work & repairs
{items.map(w => (
x.key === w.type) || WORK_TYPES[0]).icon} tone="aqua" size={34} />
{w.label} · {fmtMoney(w.cost)}
{w.date}
WAITING ON {first.toUpperCase()}
))}
{!open ? ( ) : (
{WORK_TYPES.map(wt => { const on = type === wt.key; const tn = WORK_TONE[wt.tone]; return ; })}
{['Sat, Jun 14', 'Tue, Jun 17'].map(dd => ( ))} $ setCost(Math.round((+e.target.value.replace(/[^0-9]/g, '') || 0) * 100))} style={{ width: 56, border: 'none', outline: 'none', padding: '8px 0', fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 15, background: 'transparent' }} />
Bills only after it's done — the price {first} approves is the price {first} pays.
)}
); } Object.assign(window, { WORK_TYPES, WORK_TONE, WORKITEMS, WorkState, HomeWorkUpcoming, HomeWorkDone, ProWorkCard, OwnerWorkCardM, OwnerScheduleWorkM, OwnerWorkCardDesk });