From f90ba332bd93baf2cbf04e11a086533566edd03c Mon Sep 17 00:00:00 2001
From: siddharthd
Date: Mon, 9 Mar 2026 12:03:39 +1100
Subject: [PATCH] feat(statements): table layout + statement-scoped transaction
view
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Statements page: replace card grid with compact table showing bank,
account, period, due date, currency, amount (due for CC / balance for
bank), transaction count, and View button
- Transactions page: wrap in Suspense, read statement_id from URL search
params on load; show a dismissible indigo banner with bank name and
billing period when filtering by statement; × Clear filter removes it
---
src/app/statements/page.tsx | 132 ++++++++++++++++++++++------------
src/app/transactions/page.tsx | 39 +++++++++-
2 files changed, 124 insertions(+), 47 deletions(-)
diff --git a/src/app/statements/page.tsx b/src/app/statements/page.tsx
index 50a222f..9d0cfae 100644
--- a/src/app/statements/page.tsx
+++ b/src/app/statements/page.tsx
@@ -4,7 +4,7 @@ import Link from "next/link";
import { useStatements } from "@/lib/hooks";
function formatDate(d: string | null) {
- if (!d) return "-";
+ if (!d) return "—";
return new Date(d).toLocaleDateString("en-AU", {
day: "2-digit",
month: "short",
@@ -12,12 +12,22 @@ function formatDate(d: string | null) {
});
}
-function formatCurrency(amount: number | null, currency = "AUD") {
- if (amount === null || amount === undefined) return "-";
+function formatPeriod(start: string | null, end: string | null) {
+ if (!start && !end) return "—";
+ const fmt = (d: string) =>
+ new Date(d).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "2-digit" });
+ if (!start) return `until ${fmt(end!)}`;
+ if (!end) return `from ${fmt(start)}`;
+ return `${fmt(start)} – ${fmt(end)}`;
+}
+
+function formatAmount(n: number | null): string {
+ if (n === null || n === undefined) return "—";
return new Intl.NumberFormat("en-AU", {
style: "currency",
- currency,
- }).format(amount);
+ currency: "AUD",
+ minimumFractionDigits: 2,
+ }).format(Number(n));
}
export default function StatementsPage() {
@@ -28,48 +38,80 @@ export default function StatementsPage() {
Statements
{isLoading ? (
- Loading...
+ Loading...
) : !statements?.length ? (
- No statements found
+ No statements found
) : (
-
- {statements.map((s) => (
-
-
-
{s.bank_name}
- {s.currency}
-
- {s.card_name && (
-
{s.card_name}
- )}
-
-
Account: {s.account_number}
-
- Period: {formatDate(s.billing_start_date)} - {formatDate(s.billing_end_date)}
-
-
Due: {formatDate(s.payment_due_date)}
-
-
-
-
- {formatCurrency(s.total_amount_due, s.currency)}
-
-
- {s.transaction_count} transactions
-
-
-
- View
-
-
-
- ))}
+
+
+
+
+ | Bank |
+ Account |
+ Period |
+ Due / Updated |
+ Ccy |
+ Amount |
+ Txns |
+ |
+
+
+
+ {statements.map((s) => {
+ const isCreditCard = Number(s.credit_limit) > 0 || s.payment_due_date != null;
+ const displayAmount = isCreditCard ? s.total_amount_due : s.closing_balance;
+ const amountLabel = isCreditCard ? "due" : "balance";
+
+ return (
+
+ |
+
+ {s.bank_name}
+
+ {s.card_name && (
+ {s.card_name}
+ )}
+ |
+
+ {s.account_number}
+ |
+
+ {formatPeriod(s.billing_start_date, s.billing_end_date)}
+ |
+
+ {isCreditCard
+ ? formatDate(s.payment_due_date)
+ : formatDate(s.billing_end_date)}
+ |
+
+ {s.currency}
+ |
+
+ {displayAmount !== null && displayAmount !== undefined ? (
+
+ {formatAmount(displayAmount)}
+
+ ) : (
+ —
+ )}
+ {amountLabel}
+ |
+
+ {s.transaction_count}
+ |
+
+
+ View →
+
+ |
+
+ );
+ })}
+
+
)}
diff --git a/src/app/transactions/page.tsx b/src/app/transactions/page.tsx
index b56cb09..7c3fba4 100644
--- a/src/app/transactions/page.tsx
+++ b/src/app/transactions/page.tsx
@@ -1,7 +1,8 @@
"use client";
-import { useState, useCallback } from "react";
-import { useTransactions, useBanks, useUpdateTransaction, useBulkAction, useTags } from "@/lib/hooks";
+import { useState, useCallback, Suspense } from "react";
+import { useSearchParams } from "next/navigation";
+import { useTransactions, useBanks, useUpdateTransaction, useBulkAction, useTags, useStatement } from "@/lib/hooks";
import { CATEGORIES, formatCategory } from "@/lib/categories";
import { SplitModal } from "@/components/split-modal";
import { TagPicker } from "@/components/tag-picker";
@@ -98,12 +99,24 @@ function InlineEdit({
}
export default function TransactionsPage() {
+ return (
+ Loading...
}>
+
+
+ );
+}
+
+function TransactionsContent() {
+ const searchParams = useSearchParams();
+ const initialStatementId = searchParams.get("statement_id") || "";
+
const [filters, setFilters] = useState({
from: "",
to: "",
category: "",
bank_name: "",
search: "",
+ statement_id: initialStatementId,
tag_id: "",
sort_by: "transaction_date",
sort_dir: "desc",
@@ -118,6 +131,7 @@ export default function TransactionsPage() {
const { data, isLoading } = useTransactions(filters);
const { data: banks } = useBanks();
const { data: tags } = useTags();
+ const { data: statementInfo } = useStatement(parseInt(filters.statement_id) || 0);
const updateTxn = useUpdateTransaction();
const bulkAction = useBulkAction();
@@ -161,6 +175,27 @@ export default function TransactionsPage() {
Transactions
+ {/* Statement context banner */}
+ {filters.statement_id && statementInfo && (
+
+ {statementInfo.bank_name}
+ {statementInfo.billing_start_date && statementInfo.billing_end_date && (
+
+ {new Date(statementInfo.billing_start_date).toLocaleDateString("en-AU", { day: "2-digit", month: "short" })}
+ {" – "}
+ {new Date(statementInfo.billing_end_date).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "numeric" })}
+
+ )}
+ {statementInfo.transaction_count} transactions
+
+
+ )}
+
{/* Filter bar */}