/* ============ 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}
);
}
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({ location: e.target.value })} placeholder="Address or meeting place"/>
);
}
window.Signings = Signings;