/* ============================================================================ MOWDEW — The Visit Recap Mowdew's answer to the beloved photo+chemistry service email — but in-app, two-way, and SHAREABLE. It's the canonical object an invoice line points to. A keepsake of "look what got done", not a receipt. • HOMEOWNER VisitRecap (primary) — share → branded, EXIF-stripped image • OWNER/PRO the SAME recap surface (viewer flag; name-hidden for pro) Reuses the kit + ChemVerdictHero (chemistry feature). Loads after the homeowner screens; exposed on window. ========================================================================= */ /* per-visit recap content. Pool visits carry chemistry; garden visits don't. */ const RECAPS = { v2: { svc: 'pool', date: 'Mon, May 5', in: '10:04a', out: '10:41a', dur: '37 min', pro: 'Diego', label: 'Crestline Ave pool', photos: true, before: 'pool-before', after: 'pool-clean', tasks: ['Skimmed surface & emptied baskets', 'Brushed walls & steps', 'Vacuumed floor', 'Backwashed filter', 'Tested & balanced water'], chem: { ph: 7.4, cl: 1.8, alk: 100, cya: 45, ch: 300, temp: 80, date: 'Mon, May 5' }, note: 'Filter looking good. Saw the gate latch is a little loose — flagged it for the office.' }, v3: { svc: 'pool', date: 'Mon, Apr 28', in: '9:58a', out: '10:25a', dur: '27 min', pro: 'Diego', label: 'Crestline Ave pool', photos: true, before: 'pool-before', after: 'pool-clean', tasks: ['Skimmed surface', 'Brushed walls', 'Emptied baskets', 'Tested water — corrected low chlorine'], chem: { ph: 7.2, cl: 0.8, alk: 95, cya: 53, ch: 295, temp: 78, date: 'Mon, Apr 28', handled: 'cl' }, note: 'Chlorine was a touch low — added tablets, all set now.' }, v4: { svc: 'pool', date: 'Mon, Apr 21', in: '10:20a', out: '10:42a', dur: '22 min', pro: 'Diego', label: 'Crestline Ave pool', photos: false, before: 'pool-before', after: 'pool-clean', tasks: ['Skimmed surface', 'Brushed walls', 'Emptied baskets', 'Tested & balanced water'], chem: { ph: 7.5, cl: 2.0, alk: 105, cya: 50, ch: 305, temp: 77, date: 'Mon, Apr 21' }, note: '' }, g1: { svc: 'mow', date: 'Mon, May 12', in: '9:06a', out: '9:41a', dur: '35 min', pro: 'Diego', label: 'Oak Ridge Ct garden', photos: true, before: 'lawn-before', after: 'lawn-after', tasks: ['Mowed front & back', 'Edged all borders', 'Blew down paths & patio', 'Bagged clippings'], chem: null, note: 'Lawn\u2019s filling in nicely with the warmer weather. Bay-friendly mow height for summer.' }, }; function getRecap(id) { return RECAPS[id] || RECAPS.v2; } /* ─── Before/After compare — draggable divider (keepsake, not a receipt) ─ */ function BeforeAfter({ before, after, height = 230 }) { const [pct, setPct] = React.useState(52); return (
{/* after = base layer (full) */}
{/* before = clipped from the left to pct */}
{/* labels */} BEFORE AFTER {/* divider */}
setPct(+e.target.value)} aria-label="Drag to compare before and after" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, cursor: 'ew-resize', margin: 0 }} />
); } /* ─── The recap surface ─────────────────────────────────────────────── */ function VisitRecap({ go, params, toast, viewer = 'homeowner' }) { const id = (params && params.id) || 'v2'; const r = getRecap(id); const isPool = r.svc === 'pool'; const nameHidden = viewer === 'pro'; const [share, setShare] = React.useState(false); const [shared, setShared] = React.useState(false); const back = viewer === 'owner' ? () => go('o_customer', { id: 'c2' }) : viewer === 'pro' ? () => go('pro_today') : () => go('h_feed'); const accent = isPool ? 'aqua' : 'green'; return ( }> Shared : null} /> {/* header — date, time on-site, verified, pro */}
{nameHidden ? : }
{r.date}
{nameHidden ? r.label : `${r.pro} · ${isPool ? 'pool service' : 'garden service'}`}
Verified
{r.in}–{r.out} {r.dur} on-site 40 FT
{/* before / after */}
Before & after {r.photos ? ( ) : (
No photos this visit
{r.pro} didn't snap photos this time — the verified check-in & tasks below are still on the record.
)}
{/* tasks completed */}
What got done
{r.tasks.map((t, i) => (
{t}
))}
{/* chemistry verdict — pool only */} {isPool && r.chem && (
Water
)} {/* pro note */} {r.note && (
Note from {nameHidden ? 'the pro' : r.pro}
{nameHidden ? : }

{r.note}

)} {/* keepsake footer */}
{viewer === 'homeowner' ? 'Proud of the pool? Share this recap with family — or post it.' : 'This is exactly what the homeowner sees — every visit, on the record.'}
{viewer === 'homeowner' && }
setShare(false)} r={r} onShared={() => { setShared(true); setShare(false); toast && toast('Recap shared · location data removed', 'green'); }} /> {/* review capture at peak trust — homeowner only, after a good recap */} {viewer === 'homeowner' && r.photos && (!isPool || (r.chem && !r.chem.handled)) && (
One quick thing
)}
); } /* ─── Share sheet — branded, EXIF-stripped shareable card ────────────── */ function ShareRecapSheet({ open, onClose, r, onShared }) { const isPool = r.svc === 'pool'; return (
A clean, branded card — safe to text family or post anywhere.
{/* the shareable preview */}
AFTER · {r.date.toUpperCase()} Verified on-site
{isPool ? 'Pool' : 'Garden'} service · {r.dur}
Marta Lawn & Pool · on Mowdew
{isPool && r.chem && Balanced}
Location data (EXIF) is stripped — your address never travels with the photo.
); } Object.assign(window, { RECAPS, getRecap, BeforeAfter, VisitRecap, ShareRecapSheet });