Show Newbook fetch timestamp with staleness warning on Daily Cash Up

Staff were seeing false 'out of balance' readings when the Newbook
totals shown were fetched at page load (or last manual click) and
had gone stale during the count, then balanced once re-checked
later against fresh Newbook data. Surfacing when the data was last
fetched, with a warning past 3 minutes, lets staff catch this before
submitting instead of assuming a real shortfall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-11 17:31:20 +00:00
parent b4810aa2c1
commit b750004714

View file

@ -49,6 +49,7 @@ export function DailyCashUp({ user }: Props) {
const [notes, setNotes] = useState('')
const [attachments, setAttachments] = useState<Attachment[]>([])
const [newbookTotals, setNewbookTotals] = useState<PaymentTotals | null>(null)
const [newbookFetchedAt, setNewbookFetchedAt] = useState<Date | null>(null)
const [tillPayments, setTillPayments] = useState<TillPayment[]>([])
const [transactionBreakdown, setTransactionBreakdown] = useState<TransactionBreakdown | null>(null)
const [checkedItems, setCheckedItems] = useState<Set<string>>(new Set())
@ -174,6 +175,7 @@ export function DailyCashUp({ user }: Props) {
setNotes('')
setAttachments([])
setNewbookTotals(null)
setNewbookFetchedAt(null)
setTillPayments([])
setTransactionBreakdown(null)
setCheckedItems(new Set())
@ -193,6 +195,7 @@ export function DailyCashUp({ user }: Props) {
'/newbook/payments', { date }
).then(data => {
setNewbookTotals(data.totals)
setNewbookFetchedAt(new Date())
setTillPayments(data.till_payments || [])
setTransactionBreakdown(data.transaction_breakdown || null)
}).catch(() => { /* silently ignore — button still available */ })
@ -230,6 +233,7 @@ export function DailyCashUp({ user }: Props) {
count: number; totals: PaymentTotals; till_payments: TillPayment[]; transaction_breakdown: TransactionBreakdown
}>('/newbook/payments', { date })
setNewbookTotals(data.totals)
setNewbookFetchedAt(new Date())
setTillPayments(data.till_payments || [])
setTransactionBreakdown(data.transaction_breakdown || null)
flash(`Fetched ${data.count} payment(s) from Newbook.`)
@ -492,9 +496,21 @@ export function DailyCashUp({ user }: Props) {
<Card style={{ marginBottom: '1rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h2 style={{ fontSize: '1rem', fontWeight: 700 }}>Newbook Reconciliation</h2>
<Btn onClick={fetchNewbook} disabled={fetching || isFinal} small>
{fetching ? <><Loader size={13} style={{ animation: 'spin 1s linear infinite' }} /> Fetching</> : <><RefreshCw size={13} /> Fetch Payments</>}
</Btn>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
{newbookFetchedAt && (() => {
const ageMin = (Date.now() - newbookFetchedAt.getTime()) / 60000
const stale = ageMin >= 3
return (
<span style={{ fontSize: '0.75rem', color: stale ? '#b45309' : 'var(--text-mid)', fontWeight: stale ? 600 : 400 }}>
Fetched {newbookFetchedAt.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}
{stale ? ' — re-fetch before submitting' : ''}
</span>
)
})()}
<Btn onClick={fetchNewbook} disabled={fetching || isFinal} small>
{fetching ? <><Loader size={13} style={{ animation: 'spin 1s linear infinite' }} /> Fetching</> : <><RefreshCw size={13} /> Fetch Payments</>}
</Btn>
</div>
</div>
{newbookTotals && (