From d2bca486465876d2d732f9cff7bb160ca4ab0c71 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 10 Apr 2026 11:17:56 +0800 Subject: [PATCH] feat(web): journal entries page shows double-entry debit/credit lines --- web/src/pages/JournalEntriesPage.tsx | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- web/src/types/api.ts | 9 +++++++++ 2 files changed, 96 insertions(+), 25 deletions(-) diff --git a/web/src/pages/JournalEntriesPage.tsx b/web/src/pages/JournalEntriesPage.tsx index fffc4e1..e66cb07 100644 --- a/web/src/pages/JournalEntriesPage.tsx +++ b/web/src/pages/JournalEntriesPage.tsx @@ -4,13 +4,13 @@ import type { JournalEntry } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' -import { DataTable, type Column } from '@/components/DataTable' import { StatusBadge } from '@/components/StatusBadge' export function JournalEntriesPage() { const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) + const [expanded, setExpanded] = useState>(new Set()) useEffect(() => { finance @@ -22,38 +22,100 @@ export function JournalEntriesPage() { .finally(() => setLoading(false)) }, []) - const columns: Column[] = [ - { - header: 'Posted', - key: 'postedAt', - render: (r) => (r.postedAt ? new Date(r.postedAt).toLocaleString() : '—'), - }, - { header: 'Code', key: 'code', render: (r) => {r.code} }, - { header: 'Type', key: 'type' }, - { header: 'Status', key: 'status', render: (r) => }, - { header: 'Order', key: 'orderCode', render: (r) => {r.orderCode} }, - { header: 'Partner', key: 'partnerCode', render: (r) => {r.partnerCode} }, - { - header: 'Amount', - key: 'amount', - render: (r) => ( - - {Number(r.amount).toLocaleString(undefined, { minimumFractionDigits: 2 })}{' '} - {r.currencyCode} - - ), - }, - ] + const toggle = (id: string) => { + setExpanded((prev) => { + const next = new Set(prev) + if (next.has(id)) next.delete(id) + else next.add(id) + return next + }) + } return (
{loading && } {error && } - {!loading && !error && } + {!loading && !error && rows.length === 0 && ( +
No journal entries yet.
+ )} + {!loading && !error && rows.length > 0 && ( +
+ + + + + + + + + + + + + + {rows.map((je) => { + const isOpen = expanded.has(je.id) + return ( + <> + toggle(je.id)} + > + + + + + + + + + {isOpen && je.lines?.length > 0 && ( + + + + )} + + ) + })} + +
PostedTypeStatusOrderPartnerAmountLines
{je.postedAt ? new Date(je.postedAt).toLocaleString() : '—'}{je.type}{je.orderCode}{je.partnerCode} + {Number(je.amount).toLocaleString(undefined, { minimumFractionDigits: 2 })}{' '} + {je.currencyCode} + {je.lines?.length ?? 0}
+ + + + + + + + + + + + {je.lines.map((l) => ( + + + + + + + + ))} + +
#AccountDebitCreditDescription
{l.lineNo}{l.accountCode} + {Number(l.debit) > 0 ? Number(l.debit).toLocaleString(undefined, { minimumFractionDigits: 2 }) : ''} + + {Number(l.credit) > 0 ? Number(l.credit).toLocaleString(undefined, { minimumFractionDigits: 2 }) : ''} + {l.description ?? ''}
+
+
+ )}
) } diff --git a/web/src/types/api.ts b/web/src/types/api.ts index 32c13fe..f0963d4 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -264,6 +264,14 @@ export interface Account { export type JournalEntryType = 'AR' | 'AP' export type JournalEntryStatus = 'POSTED' | 'SETTLED' | 'REVERSED' +export interface JournalEntryLine { + lineNo: number + accountCode: string + debit: string | number + credit: string | number + description: string | null +} + export interface JournalEntry { id: string code: string @@ -274,4 +282,5 @@ export interface JournalEntry { amount: string | number currencyCode: string postedAt: string + lines: JournalEntryLine[] } -- libgit2 0.22.2