diff --git a/src/app/statements/page.tsx b/src/app/statements/page.tsx index b28b1b9..a52bb5c 100644 --- a/src/app/statements/page.tsx +++ b/src/app/statements/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState, useMemo } from "react"; import Link from "next/link"; import { useStatements, useParticipants, useUpdateStatement } from "@/lib/hooks"; @@ -30,24 +31,114 @@ function formatAmount(n: number | null): string { }).format(Number(n)); } +const selectCls = + "bg-zinc-900 border border-zinc-700 rounded text-xs px-2 py-1.5 text-zinc-300 cursor-pointer hover:border-zinc-600 focus:outline-none focus:border-indigo-500"; + export default function StatementsPage() { const { data: statements, isLoading } = useStatements(); const { data: participants } = useParticipants(); const updateStatement = useUpdateStatement(); + const [bankFilter, setBankFilter] = useState(""); + const [typeFilter, setTypeFilter] = useState<"all" | "card" | "bank">("all"); + const [ownerFilter, setOwnerFilter] = useState(""); + const [yearFilter, setYearFilter] = useState(""); + + const banks = useMemo( + () => [...new Set((statements ?? []).map((s) => s.bank_name))].sort(), + [statements] + ); + const years = useMemo( + () => + [ + ...new Set( + (statements ?? []) + .map((s) => s.billing_end_date?.slice(0, 4)) + .filter(Boolean) as string[] + ), + ].sort((a, b) => b.localeCompare(a)), + [statements] + ); + + const filtered = useMemo(() => { + if (!statements) return []; + return statements.filter((s) => { + const isCard = s.statement_type?.toLowerCase().includes("card") ?? false; + if (bankFilter && s.bank_name !== bankFilter) return false; + if (typeFilter === "card" && !isCard) return false; + if (typeFilter === "bank" && isCard) return false; + if (ownerFilter && String(s.owner_id) !== ownerFilter) return false; + if (yearFilter && s.billing_end_date?.slice(0, 4) !== yearFilter) return false; + return true; + }); + }, [statements, bankFilter, typeFilter, ownerFilter, yearFilter]); + + const hasFilters = bankFilter || typeFilter !== "all" || ownerFilter || yearFilter; + return (
-

Statements

+
+

Statements

+ {!isLoading && statements && ( + + {hasFilters ? `${filtered.length} of ${statements.length}` : statements.length} statements + + )} +
+ + {/* Filters */} + {!isLoading && statements && ( +
+ + + + + {participants && participants.length > 1 && ( + + )} + + + + {hasFilters && ( + + )} +
+ )} {isLoading ? (

Loading...

- ) : !statements?.length ? ( -

No statements found

+ ) : !filtered.length ? ( +

{hasFilters ? "No statements match filters" : "No statements found"}

) : (
+ @@ -60,11 +151,10 @@ export default function StatementsPage() { - {statements.map((s) => { + {filtered.map((s, idx) => { const isCreditCard = s.statement_type?.toLowerCase().includes("card") ?? false; const displayAmount = isCreditCard ? s.total_amount_due : s.closing_balance; const amount = Number(displayAmount); - // CC: red (you owe). Bank: green if positive balance, red if overdraft. const amountColor = isCreditCard ? "text-red-400" : amount >= 0 @@ -73,6 +163,7 @@ export default function StatementsPage() { return ( +
# Bank Account Period
{idx + 1}
{s.bank_name} @@ -88,18 +179,14 @@ export default function StatementsPage() { {formatPeriod(s.billing_start_date, s.billing_end_date)}
- {isCreditCard - ? formatDate(s.payment_due_date) - : formatDate(s.billing_end_date)} + {isCreditCard ? formatDate(s.payment_due_date) : formatDate(s.billing_end_date)} {s.currency} {displayAmount !== null && displayAmount !== undefined ? ( - - {formatAmount(displayAmount)} - + {formatAmount(displayAmount)} ) : ( )} @@ -117,9 +204,7 @@ export default function StatementsPage() { className="bg-zinc-800 border border-zinc-700 rounded text-xs px-2 py-1 text-zinc-300 cursor-pointer hover:border-zinc-600 focus:outline-none focus:border-indigo-500" > {participants.map((p) => ( - + ))} ) : (