fix(ui): Client 360 shows documents + payments newest-first

The ledger returns rows oldest-first, so 'Recent documents' sliced the OLDEST 5 and
the Documents/Payments tables listed oldest→newest. Sort newest-first in the view
(by date then doc-no for docs; received-on then id for payments) so recent transactions
lead. Frontend-only; no shared query/test touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 2 days ago
parent 074e810415
commit 432587c7a1

@ -86,6 +86,10 @@ export function ClientDetail() {
modules.data?.find((m) => m.id === moduleId)?.name ?? moduleId modules.data?.find((m) => m.id === moduleId)?.name ?? moduleId
const docs = ledger.data?.documents ?? [] const docs = ledger.data?.documents ?? []
// The ledger is oldest-first; the register + "recent" views want newest first (by date, then doc no).
const docsNewest = [...docs]
.sort((a, b) => (a.docDate === b.docDate ? (b.docNo ?? '').localeCompare(a.docNo ?? '') : b.docDate.localeCompare(a.docDate)))
const recentDocs = docsNewest.slice(0, 5)
const openDocs = docs.filter((d) => d.status === 'draft' || d.status === 'sent' || d.status === 'part_paid') const openDocs = docs.filter((d) => d.status === 'draft' || d.status === 'sent' || d.status === 'part_paid')
const activeModules = (cms.data ?? []).filter((m) => m.active) const activeModules = (cms.data ?? []).filter((m) => m.active)
const lastInteraction = interactions.data?.[0]?.onDate const lastInteraction = interactions.data?.[0]?.onDate
@ -228,8 +232,8 @@ export function ClientDetail() {
{ key: 'date', label: 'Date' }, { key: 'payable', label: 'Payable', numeric: true }, { key: 'date', label: 'Date' }, { key: 'payable', label: 'Payable', numeric: true },
{ key: 'status', label: 'Status' }, { key: 'status', label: 'Status' },
]} ]}
onRowClick={(_row, i) => nav(`/documents/${docs[i]!.id}`)} onRowClick={(_row, i) => nav(`/documents/${recentDocs[i]!.id}`)}
rows={docs.slice(0, 5).map((d) => ({ rows={recentDocs.map((d) => ({
no: d.docNo ?? 'draft', no: d.docNo ?? 'draft',
type: <Badge tone={DOC_TYPE_TONE[d.docType]}>{DOC_TYPE_LABEL[d.docType]}</Badge>, type: <Badge tone={DOC_TYPE_TONE[d.docType]}>{DOC_TYPE_LABEL[d.docType]}</Badge>,
date: d.docDate, date: d.docDate,
@ -359,15 +363,15 @@ export function ClientDetail() {
<> <>
{ledger.error !== undefined && <ErrorState message={ledger.error} onRetry={ledger.reload} />} {ledger.error !== undefined && <ErrorState message={ledger.error} onRetry={ledger.reload} />}
{ledger.data === undefined && ledger.error === undefined ? <Skeleton /> {ledger.data === undefined && ledger.error === undefined ? <Skeleton />
: docs.length === 0 ? <EmptyState>No documents yet compose one from New Document.</EmptyState> : ( : docsNewest.length === 0 ? <EmptyState>No documents yet compose one from New Document.</EmptyState> : (
<DataTable <DataTable
columns={[ columns={[
{ key: 'no', label: 'No', mono: true }, { key: 'type', label: 'Type' }, { key: 'no', label: 'No', mono: true }, { key: 'type', label: 'Type' },
{ key: 'date', label: 'Date' }, { key: 'payable', label: 'Payable', numeric: true }, { key: 'date', label: 'Date' }, { key: 'payable', label: 'Payable', numeric: true },
{ key: 'status', label: 'Status' }, { key: 'status', label: 'Status' },
]} ]}
onRowClick={(_row, i) => nav(`/documents/${docs[i]!.id}`)} onRowClick={(_row, i) => nav(`/documents/${docsNewest[i]!.id}`)}
rows={docs.map((d) => ({ rows={docsNewest.map((d) => ({
no: d.docNo ?? <Badge tone="warn">draft</Badge>, no: d.docNo ?? <Badge tone="warn">draft</Badge>,
type: <Badge tone={DOC_TYPE_TONE[d.docType]}>{DOC_TYPE_LABEL[d.docType]}</Badge>, type: <Badge tone={DOC_TYPE_TONE[d.docType]}>{DOC_TYPE_LABEL[d.docType]}</Badge>,
date: d.docDate, date: d.docDate,
@ -395,7 +399,9 @@ export function ClientDetail() {
{ key: 'amount', label: 'Amount', numeric: true }, { key: 'amount', label: 'Amount', numeric: true },
{ key: 'tds', label: 'TDS', numeric: true }, { key: 'tds', label: 'TDS', numeric: true },
]} ]}
rows={ledger.data.payments.map((p) => ({ rows={[...ledger.data.payments]
.sort((a, b) => (a.receivedOn === b.receivedOn ? (a.id < b.id ? 1 : -1) : b.receivedOn.localeCompare(a.receivedOn)))
.map((p) => ({
on: p.receivedOn, mode: p.mode, ref: p.reference !== '' ? p.reference : '—', on: p.receivedOn, mode: p.mode, ref: p.reference !== '' ? p.reference : '—',
amount: inr(p.amountPaise), tds: p.tdsPaise > 0 ? inr(p.tdsPaise) : '—', amount: inr(p.amountPaise), tds: p.tdsPaise > 0 ? inr(p.tdsPaise) : '—',
}))} }))}

Loading…
Cancel
Save