// flows2.jsx — Support workers (pay run) and Plan-review prep const { useState: uS3, useEffect: uE3 } = React; // ══════════════════════════════════════════════════════════════════ // SUPPORT WORKERS + PAY RUN // ══════════════════════════════════════════════════════════════════ function Workers() { const S = useHaven(); const sheets = S.doc.timesheets; const [running, setRunning] = uS3(false); const [addingTs, setAddingTs] = uS3(false); const worker = (id) => S.doc.workers.find((w) => w.id === id) || { rate: 0, name: '(removed worker)', initials: '?' }; const pending = sheets.filter((s) => s.status === 'pending'); const pendingTotal = pending.reduce((s, t) => s + t.hours * worker(t.worker).rate, 0); const pendingHours = pending.reduce((s, t) => s + t.hours, 0); const approve = (id) => S.approveTimesheet(id); const approved = sheets.filter((s) => s.status === 'approved'); const readyTotal = approved.reduce((s, t) => s + t.hours * worker(t.worker).rate, 0); return (
The people you employ directly. Approve their hours, run a pay, and Haven keeps the records the ATO and NDIS want.} right={} /> {S.doc.workers.length === 0 && No support workers yet — add the people you employ in Settings and their timesheets and pay runs will live here.} {/* worker cards */}
{S.doc.workers.map((w) => { const wh = sheets.filter((s) => s.worker === w.id).reduce((a, s) => a + s.hours, 0); return (
{w.name}
{w.role} · ${w.rate.toFixed(2)}/hr
{wh}h
this plan period
{fmtMoney(wh * w.rate)}
paid + pending
); })}
{/* timesheets to approve */} setAddingTs(true)} style={{ opacity: S.doc.workers.length ? 1 : 0.5, pointerEvents: S.doc.workers.length ? 'auto' : 'none' }}>Add timesheet}> Timesheets to approve {pending.length > 0 && · {pending.length}} {pending.length === 0 && approved.length === 0 && All caught up — no timesheets waiting.}
{[...pending, ...approved].map((t) => { const w = worker(t.worker); const isApproved = t.status === 'approved'; return (
{w.name} · {t.hours}h
{t.note}
{fmtDate(t.date)}
{fmtMoney(t.hours * w.rate, 2)}
{isApproved ? Approved : }
); })}
{/* paid history */} {sheets.some((s) => s.status === 'paid') && ( <> Recently paid {sheets.filter((s) => s.status === 'paid').map((t, i, arr) => { const w = worker(t.worker); return (
{w.name} · {t.hours}h
{fmtDate(t.date)} · {t.note}
{fmtMoney(t.hours * w.rate, 2)}
); })}
)} setRunning(false)} onDone={() => S.payApproved()} /> setAddingTs(false)} onDone={(f) => { S.addTimesheet(f); setAddingTs(false); }} />
); } // ── add a timesheet ── function AddTimesheet({ open, onClose, onDone, workers }) { const blankTs = { worker: '', date: '', hours: '', note: '' }; const [f, setF] = uS3(blankTs); uE3(() => { if (open) setF({ worker: (workers[0] || {}).id || '', date: TODAY.toISOString().slice(0, 10), hours: '', note: '' }); }, [open]); const set = (k, v) => setF((x) => ({ ...x, [k]: v })); const inputStyle = { width: '100%', padding: '11px 13px', fontSize: 14.5, fontFamily: 'var(--sans)', border: '1px solid var(--line-2)', borderRadius: 11, background: 'var(--surface)', color: 'var(--ink)', outline: 'none', boxSizing: 'border-box' }; const labelStyle = { fontSize: 13, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 6, display: 'block' }; const w = workers.find((x) => x.id === f.worker); const amt = (parseFloat(f.hours) || 0) * ((w || {}).rate || 0); const canSave = f.worker && parseFloat(f.hours) > 0; return (
set('date', e.target.value)} />
set('hours', e.target.value)} />
set('note', e.target.value)} />
{w && parseFloat(f.hours) > 0 && {f.hours}h × ${w.rate.toFixed(2)} = {fmtMoney(amt, 2)}}
); } function PayRun({ open, onClose, onDone, sheets, worker, total }) { const [done, setDone] = uS3(false); uE3(() => { if (open) setDone(false); }, [open]); const tax = total * 0.18; // rough PAYG illustration const superAmt = total * 0.12; return ( {!done ? (
{sheets.map((t) => { const w = worker(t.worker); return (
{w.name} · {t.hours}h × ${w.rate.toFixed(2)}{fmtMoney(t.hours * w.rate, 2)}
); })}
Gross pay{fmtMoney(total, 2)}
As an employer, set aside roughly PAYG tax (~{fmtMoney(tax, 0)}) and super (~{fmtMoney(superAmt, 0)}) — rough guides only, not real tax tables. Haven records the pay against your budget; payslips and Single Touch Payroll still happen in your payroll software.
) : (

Pay run done

Pay recorded — the hours and amount have come off your Core budget. Remember payslips and Single Touch Payroll in your payroll software.

)}
); } // ══════════════════════════════════════════════════════════════════ // PLAN-REVIEW PREP // ══════════════════════════════════════════════════════════════════ function Review() { const S = useHaven(); const rv = S.doc.review; const checks = [ { id: 'k1', text: 'Spending summary for each category', done: true, auto: true }, { id: 'k2', text: 'Progress notes against each goal', done: true, auto: true }, { id: 'k3', text: 'Invoices & receipts gathered', done: true, auto: true }, { id: 'k4', text: 'List of providers & support workers', done: true, auto: true }, { id: 'k5', text: 'Note what worked well this year', done: rv.notes.worked.trim().length > 0 }, { id: 'k6', text: "Note what you'd change or need more of", done: rv.notes.change.trim().length > 0 }, { id: 'k7', text: 'Gather any new reports from therapists', done: rv.k7, toggle: true }, ]; const [exported, setExported] = uS3(false); const done = checks.filter((c) => c.done).length; const ready = done / checks.length; return (
Your is coming up. Haven has already pulled together most of what you'll need — add your own notes and you're ready.} /> {/* hero countdown + readiness */}
{`Review around ${fmtDate(PLAN_END)}`}

{PLAN_DAYS_LEFT} days to go

A great review is really just good records plus your story. You've got the records — let's add the story.

{Math.round(ready * 100)}% ready
{/* left: checklist */}
Your checklist {checks.map((c, i) => (
c.toggle && S.setReviewK7(!rv.k7)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '13px 13px', borderBottom: i < checks.length - 1 ? '1px solid var(--line)' : 'none', cursor: c.toggle ? 'pointer' : 'default' }}> {c.done && } {c.text} {c.auto && Auto}
))}
The two notes below are the bits only you can write — they carry the most weight in a review, and they go straight into your report.