/* ============ Signings ============ */ const SIGNING_DOC_TYPES = ['Refinance','Purchase','HELOC','Reverse Mortgage','POA','Will / Trust','Affidavit','Acknowledgment','Jurat','Apostille prep','Other']; const SIGNING_STATUSES = ['Scheduled','Completed','Cancelled','No-show']; function Signings({ search }) { const { signings, clients } = useStore(); const [view, setView] = React.useState('upcoming'); // upcoming | all | completed const [editing, setEditing] = React.useState(null); const today = todayISO(); const filtered = React.useMemo(() => { let list = signings; if (view === 'upcoming') list = list.filter(s => s.date >= today && s.status === 'Scheduled'); if (view === 'completed') list = list.filter(s => s.status === 'Completed'); list = [...list].sort((a,b) => (b.date + (b.time||'')).localeCompare(a.date + (a.time||''))); if (view === 'upcoming') list = list.reverse(); const q = (search || '').toLowerCase().trim(); if (q) { list = list.filter(s => { const client = clients.find(c => c.id === s.clientId); const hay = `${s.title} ${s.location} ${s.docType} ${client?.name || ''}`.toLowerCase(); return hay.includes(q); }); } return list; }, [signings, clients, view, search]); const upcomingCount = signings.filter(s => s.date >= today && s.status === 'Scheduled').length; const completedCount = signings.filter(s => s.status === 'Completed').length; const revenueClosed = signings.filter(s => s.status === 'Completed').reduce((s,x) => s + (parseFloat(x.fee)||0), 0); return (
{filtered.length === 0 ? ( setEditing({ _new: true })}>New signing} /> ) : (
{groupByDate(filtered).map(g => (
{dateGroupLabel(g.date)}
{g.items.map((s, i) => ( c.id === s.clientId)} last={i === g.items.length - 1} onEdit={() => setEditing(s)} /> ))}
))}
)} {editing && setEditing(null)} />}
); } function groupByDate(list) { const map = new Map(); list.forEach(s => { if (!map.has(s.date)) map.set(s.date, []); map.get(s.date).push(s); }); return Array.from(map.entries()).map(([date, items]) => ({ date, items })); } function dateGroupLabel(iso) { const d = new Date(iso + 'T00:00:00'); const today = new Date(); today.setHours(0,0,0,0); const diff = Math.round((d - today) / 86400000); if (diff === 0) return 'Today · ' + d.toLocaleDateString('en-US', { weekday:'long', month:'short', day:'numeric' }); if (diff === 1) return 'Tomorrow · ' + d.toLocaleDateString('en-US', { weekday:'long', month:'short', day:'numeric' }); if (diff === -1) return 'Yesterday · ' + d.toLocaleDateString('en-US', { weekday:'long', month:'short', day:'numeric' }); return d.toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric', year: diff > 365 || diff < -365 ? 'numeric' : undefined }); } function SigningRow({ signing, client, last, onEdit }) { return (
{fmt.time(signing.time)}
{signing.durationMin || 60}m
{signing.title || signing.docType}
{signing.status}
{client && {client.name}} {signing.location && {signing.location}} {signing.docType}
{fmt.money(signing.fee)}
); } function SigningEditor({ draft, clients, onClose }) { const isNew = !!draft._new; const [s, setS] = React.useState(isNew ? { title: '', clientId: null, date: todayISO(), time: '10:00', durationMin: 60, location: '', docType: 'Refinance', fee: Store.getState().settings.defaultFee, status: 'Scheduled', notes: '', } : draft); const toast = useToast(); const set = (patch) => setS(prev => ({ ...prev, ...patch })); const save = () => { if (isNew) { Store.Signings.add(s); toast('Signing added'); } else { Store.Signings.update(draft.id, s); toast('Signing updated'); } onClose(); }; const del = () => { if (confirm('Delete this signing?')) { Store.Signings.remove(draft.id); toast('Signing deleted'); onClose(); } }; return ( {!isNew && }
}> set({ title: e.target.value })} placeholder="Loan signing — Refinance" autoFocus/>
set({ date: e.target.value })}/> set({ time: e.target.value })}/>
set({ durationMin: parseInt(e.target.value)||0 })}/> set({ fee: parseFloat(e.target.value)||0 })}/>
set({ status: e.target.value })} options={SIGNING_STATUSES}/>
set({ location: e.target.value })} placeholder="Address or meeting place"/>