diff --git a/src/common/components/TransactionList.tsx b/src/common/components/TransactionList.tsx index d62bf0d..fc4eccd 100644 --- a/src/common/components/TransactionList.tsx +++ b/src/common/components/TransactionList.tsx @@ -14,7 +14,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { formatCurrency } from "../../../react-openapi"; import type { ExpenseItem, TxnFieldConfigs } from "../types"; -import { groupByMonth } from "../utils/transactions"; +import { groupByDate, groupByMonth } from "../utils/transactions"; import { monthLabel } from "../utils/dates"; import { TransactionRow } from "./TransactionRow"; @@ -160,13 +160,48 @@ export function TransactionList({ items, fields }: TransactionListProps) { - {group.items.map((item) => ( - + {groupByDate(group.items).map((dateGroup) => ( + + + + {dateGroup.label} + + + + {dateGroup.items.length} transaction{dateGroup.items.length === 1 ? "" : "s"} + + + + {dateGroup.items.map((item) => ( + + ))} + + ))} diff --git a/src/common/utils/dates.ts b/src/common/utils/dates.ts index d6b5e83..5758b3d 100644 --- a/src/common/utils/dates.ts +++ b/src/common/utils/dates.ts @@ -63,4 +63,9 @@ export function monthLabel(key: string): string { if (!y || !m) return key; const d = new Date(y, m - 1, 1); return d.toLocaleDateString("en-IN", { month: "long", year: "numeric" }); +} + +export function dateLabel(value: string): string { + const d = parseOccurredAt(value); + return d.toLocaleDateString("en-IN", { day: "numeric", month: "short", year: "numeric" }); } \ No newline at end of file diff --git a/src/common/utils/transactions.ts b/src/common/utils/transactions.ts index 9538088..6da2894 100644 --- a/src/common/utils/transactions.ts +++ b/src/common/utils/transactions.ts @@ -1,10 +1,34 @@ import type { ExpenseItem, GroupedMonth } from "../types"; -import { monthKey, parseOccurredAt } from "./dates"; +import { dateLabel, monthKey, parseOccurredAt } from "./dates"; export function isExpense(item: ExpenseItem): boolean { return (item.amount ?? 0) < 0; } +export interface DateGroup { + date: string; + label: string; + items: ExpenseItem[]; +} + +export function groupByDate(items: ExpenseItem[]): DateGroup[] { + const sorted = [...items].sort( + (a, b) => parseOccurredAt(b.occurred_at).getTime() - parseOccurredAt(a.occurred_at).getTime(), + ); + const map = new Map(); + for (const item of sorted) { + const key = item.occurred_at ?? ""; + const list = map.get(key) ?? []; + list.push(item); + map.set(key, list); + } + return [...map.entries()].map(([date, list]) => ({ + date, + label: dateLabel(date), + items: list, + })); +} + export function groupByMonth(items: ExpenseItem[]): GroupedMonth[] { const map = new Map(); for (const item of items) {