/* ============ Mileage ============ */ function Mileage({ search }) { const { mileage, clients, settings } = useStore(); const [editing, setEditing] = React.useState(null); const [range, setRange] = React.useState('all'); // all | mtd | ytd const today = new Date(); const rangeStart = React.useMemo(() => { if (range === 'mtd') return new Date(today.getFullYear(), today.getMonth(), 1).toISOString().slice(0,10); if (range === 'ytd') return new Date(today.getFullYear(), 0, 1).toISOString().slice(0,10); return '0000-01-01'; }, [range]); const filtered = React.useMemo(() => { const q = (search || '').toLowerCase().trim(); return [...mileage] .filter(m => m.date >= rangeStart) .filter(m => { if (!q) return true; const client = clients.find(c => c.id === m.clientId); const hay = `${m.purpose} ${m.from} ${m.to} ${client?.name || ''}`.toLowerCase(); return hay.includes(q); }) .sort((a,b) => b.date.localeCompare(a.date) || (b.createdAt||'').localeCompare(a.createdAt||'')); }, [mileage, clients, rangeStart, search]); const tripMiles = (m) => (parseFloat(m.miles)||0) * (m.roundTrip ? 2 : 1); const totalMiles = filtered.reduce((s,m) => s + tripMiles(m), 0); const totalDeduction = totalMiles * (settings.mileageRate || 0); const tripCount = filtered.length; const avgPerTrip = tripCount ? totalMiles / tripCount : 0; return (
m.roundTrip).length} delta={`of ${tripCount} entries`} iconName="arrowRt" iconColor="var(--orange)" />
{filtered.length === 0 ? ( setEditing({ _new: true })}>Log trip} /> ) : (
{filtered.map((m, i) => { const client = clients.find(c => c.id === m.clientId); const miles = tripMiles(m); const ded = miles * (settings.mileageRate || 0); return ( setEditing(m)}> ); })}
Date Purpose From → To Client Miles R/T Deduction
{String(i+1).padStart(2,'0')} {fmt.dateShort(m.date)} {m.purpose || } {m.from || '—'} {m.to || '—'} {client ? {client.name} : } {miles.toFixed(1)} {m.roundTrip ? R/T : One-way} {fmt.money(ded, 2)}
Totals {totalMiles.toFixed(1)} {fmt.money(totalDeduction, 2)}
)}
{editing && setEditing(null)} />}
); } function MileageEditor({ draft, clients, settings, onClose }) { const isNew = !!draft._new; const [m, setM] = React.useState(isNew ? { date: todayISO(), clientId: null, purpose: '', from: settings.baseAddress || 'Office', to: '', miles: 0, roundTrip: true, notes: '', } : draft); const toast = useToast(); const set = (patch) => setM(prev => ({ ...prev, ...patch })); const totalMiles = (parseFloat(m.miles)||0) * (m.roundTrip ? 2 : 1); const deduction = totalMiles * (settings.mileageRate || 0); const save = () => { if (isNew) { Store.Mileage.add(m); toast('Trip logged'); } else { Store.Mileage.update(draft.id, m); toast('Trip updated'); } onClose(); }; const del = () => { if (confirm('Delete this trip?')) { Store.Mileage.remove(draft.id); toast('Trip deleted'); onClose(); } }; return ( {!isNew && }
}>
set({ date: e.target.value })}/>
set({ purpose: e.target.value })} placeholder="Loan signing, courthouse run…"/>
set({ from: e.target.value })} placeholder="Office"/> set({ to: e.target.value })} placeholder="Client address"/>
set({ miles: parseFloat(e.target.value)||0 })}/> set({ roundTrip: v === 'rt' })} options={[ { value: 'rt', label: 'Round trip' }, { value: 'ow', label: 'One-way' }, ]}/>
Total distance
{totalMiles.toFixed(1)} mi
Tax deduction
{fmt.money(deduction, 2)}