import { useState, useEffect, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { api } from '../api' import { PageHeader, Card, Btn, StatusBadge } from '../components/Layout' import { fmtGBP, today, can } from '../types' import type { CashUp, User } from '../types' export function History({ user }: { user: User }) { const navigate = useNavigate() const canFinalise = can(user, 'finalise') const [rows, setRows] = useState([]) const [total, setTotal] = useState(0) const [offset, setOffset] = useState(0) const [status, setStatus] = useState('all') const [from, setFrom] = useState('') const [to, setTo] = useState(today()) const [loading, setLoading] = useState(false) const [selected, setSelected] = useState>(new Set()) const [msg, setMsg] = useState<{ text: string; ok: boolean } | null>(null) const limit = 20 const flash = (text: string, ok = true) => { setMsg({ text, ok }) setTimeout(() => setMsg(null), 4000) } const load = useCallback(async () => { setLoading(true) try { const params = new URLSearchParams({ offset: String(offset), limit: String(limit) }) if (status !== 'all') params.set('status', status) if (from) params.set('from', from) if (to) params.set('to', to) const data = await api.get<{ rows: CashUp[]; total: number }>(`/cashup/history?${params}`) setRows(data.rows) setTotal(data.total) } finally { setLoading(false) } }, [offset, status, from, to]) useEffect(() => { load() }, [load]) function toggleSelect(id: number) { setSelected(prev => { const next = new Set(prev) next.has(id) ? next.delete(id) : next.add(id) return next }) } async function deleteDraft(id: number) { if (!confirm('Delete this draft cash up?')) return await api.delete(`/cashup/${id}`) flash('Deleted.') load() } async function unfinalise(id: number) { if (!confirm('Revert this cash up to draft so it can be edited?')) return await api.post(`/cashup/${id}/unfinalise`, {}) flash('Reverted to draft.') load() } async function bulkFinalize() { if (!selected.size) return if (!confirm(`Finalise ${selected.size} draft(s)?`)) return const { success, failed_count } = await api.post<{ success: number; failed_count: number }>( '/cashup/bulk-finalize', { ids: Array.from(selected) } ) flash(`${success} finalised${failed_count ? `, ${failed_count} failed` : ''}.`, !failed_count) setSelected(new Set()) load() } return (
{msg && (
{msg.text}
)} {/* Filters */}
{ setFrom(e.target.value); setOffset(0) }} style={inpSt} />
{ setTo(e.target.value); setOffset(0) }} style={inpSt} />
{ setOffset(0); load() }} small>Filter {canFinalise && selected.size > 0 && ( Finalise {selected.size} selected )}
{loading ? (
Loading…
) : rows.length === 0 ? (

No cash ups found.

) : ( {rows.map(row => ( ))}
Date Status Cash Float Created By Submitted
{canFinalise && row.status === 'draft' && ( toggleSelect(row.id)} /> )} {new Date(row.session_date.slice(0, 10) + 'T12:00:00').toLocaleDateString('en-GB', { weekday: 'short', day: '2-digit', month: 'short', year: 'numeric' })} {fmtGBP(row.total_cash_counted)} {fmtGBP(row.total_float_counted)} {row.created_by} {row.submitted_at ? new Date(row.submitted_at).toLocaleDateString('en-GB') : '—'} navigate(`/daily?date=${row.session_date.slice(0, 10)}`)}> {row.status === 'draft' ? 'Edit' : 'View'} {canFinalise && row.status === 'draft' && ( deleteDraft(row.id)}>Delete )} {user.is_admin && row.status === 'final' && ( unfinalise(row.id)}>Unfinalise )}
)} {/* Pagination */} {total > limit && (
setOffset(Math.max(0, offset - limit))} disabled={offset === 0} small variant="secondary">Prev {offset + 1}–{Math.min(offset + limit, total)} of {total} setOffset(offset + limit)} disabled={offset + limit >= total} small variant="secondary">Next
)}
) } const thSt: React.CSSProperties = { padding: '0.6rem 0.75rem', fontWeight: 600, color: 'var(--text-mid)', fontSize: '0.8rem' } const tdSt: React.CSSProperties = { padding: '0.6rem 0.75rem' } const selSt: React.CSSProperties = { border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.4rem 0.6rem', fontSize: '0.875rem' } const inpSt: React.CSSProperties = { border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.4rem 0.6rem', fontSize: '0.875rem' }