From 9a7ec5a4cf8512b94ae6fbd25cc9a58314c97776 Mon Sep 17 00:00:00 2001 From: Marjose Darang <18107626+MarJose123@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:19:20 +0800 Subject: [PATCH 1/2] feat: account badges, balance card, and interactive activity pie chart - Remove the color badge mode from add/edit account forms (Logo + Custom only) - Show the account badge (logo/initials) on dashboard recent transactions - Wrap the dashboard total balance in a card with this month's expense/income - Replace the weekly bar chart with an interactive earned/spent donut pie; tapping a segment lists the per-account breakdown (badge, name, amount) - Add getIncomeByAccount / getExpenseByAccount repo queries with tests --- .../components/DashboardComponents.test.tsx | 121 ++++++++ .../repositories/transactions.test.ts | 107 +++++++ src/app/(app)/dashboard.tsx | 61 ++-- src/app/add-account.tsx | 139 ++------- src/app/edit-account.tsx | 150 ++-------- src/components/dashboard/TransactionRow.tsx | 78 ++++- .../dashboard/WeeklyActivityCard.tsx | 275 +++++++++++++----- src/hooks/useDashboardData.ts | 12 +- src/lib/db/repositories/transactions.ts | 103 ++++++- src/types/hooks.ts | 7 +- src/types/transaction.ts | 19 ++ 11 files changed, 721 insertions(+), 351 deletions(-) diff --git a/src/__tests__/components/DashboardComponents.test.tsx b/src/__tests__/components/DashboardComponents.test.tsx index d209c60..9c1e5e3 100644 --- a/src/__tests__/components/DashboardComponents.test.tsx +++ b/src/__tests__/components/DashboardComponents.test.tsx @@ -19,6 +19,8 @@ import { TransactionRow } from "@/components/dashboard/TransactionRow"; import { AccountSwitcher } from "@/components/dashboard/AccountSwitcher"; import { EmptyAccountsCard } from "@/components/dashboard/EmptyAccountsCard"; import { CategoryRingCard } from "@/components/dashboard/CategoryRingCard"; +import { WeeklyActivityCard } from "@/components/dashboard/WeeklyActivityCard"; +import { HIDDEN_AMOUNT } from "@/lib/format"; // Mock nativewind jest.mock("nativewind", () => ({ @@ -95,6 +97,9 @@ describe("TransactionRow", () => { amountCents: -15000, occurredAt: Date.now() - 3600000, accountId: "acc-1", + accountName: "BPI Savings", + accountLogoKey: null, + accountColorHex: "#C87B54", categoryName: "Dining", categoryIcon: "🍽", categoryKind: "expense" as const, @@ -126,6 +131,23 @@ describe("TransactionRow", () => { const json = JSON.stringify(renderer.toJSON()); expect(json).toContain("5,000"); }); + + it("renders the account badge (initials fallback when no logo)", () => { + const renderer = render(); + const json = JSON.stringify(renderer.toJSON()); + // Initials of "BPI Savings" → "BS" + expect(json).toContain("BS"); + // Badge fallback background color + expect(json).toContain("#C87B54"); + }); + + it("renders the account logo image when a logoKey is present", () => { + const withLogo = { ...baseTx, accountLogoKey: "bank/bpi.png" }; + const renderer = render(); + // Bundled logo asset resolves via require — the image element renders + // without the initials fallback. + expect(JSON.stringify(renderer.toJSON())).not.toContain("BS"); + }); }); // ─── AccountSwitcher ─────────────────────────────────────────────────────── @@ -211,3 +233,102 @@ describe("CategoryRingCard", () => { expect(JSON.stringify(renderer.toJSON())).toContain("\u2014"); }); }); + +// ─── WeeklyActivityCard ───────────────────────────────────────────────────── + +describe("WeeklyActivityCard", () => { + const earned = [ + { + accountId: "acc-1", + accountName: "BPI", + accountLogoKey: "bank/bpi.png", + accountColorHex: "#C87B54", + amountCents: 30000, + }, + ]; + const spent = [ + { + accountId: "acc-2", + accountName: "Wallet", + accountLogoKey: null, + accountColorHex: "#7FA06B", + amountCents: 10000, + }, + { + accountId: "acc-3", + accountName: "GCash", + accountLogoKey: "bank/gcash.png", + accountColorHex: "#6B8D58", + amountCents: 5000, + }, + ]; + + it("shows earned and spent totals by default", () => { + const renderer = render( + , + ); + const json = JSON.stringify(renderer.toJSON()); + expect(json).toContain("Earned"); + expect(json).toContain("Spent"); + expect(json).toContain("₱300.00"); + expect(json).toContain("₱150.00"); + }); + + it("lists accounts with badges and amounts when the spent arc is tapped", () => { + const renderer = render( + , + ); + act(() => { + renderer.root.findByProps({ testID: "segment-spent" }).props.onPress(); + }); + const json = JSON.stringify(renderer.toJSON()); + expect(json).toContain("Spent by account"); + expect(json).toContain("Wallet"); + expect(json).toContain("GCash"); + expect(json).toContain("₱100.00"); + expect(json).toContain("₱50.00"); + expect(json).not.toContain("Earned"); + }); + + it("lists accounts when the earned arc is tapped", () => { + const renderer = render( + , + ); + act(() => { + renderer.root.findByProps({ testID: "segment-earned" }).props.onPress(); + }); + const json = JSON.stringify(renderer.toJSON()); + expect(json).toContain("Earned by account"); + expect(json).toContain("BPI"); + expect(json).toContain("₱300.00"); + }); + + it("tapping the same segment again returns to the totals", () => { + const renderer = render( + , + ); + act(() => { + renderer.root.findByProps({ testID: "segment-earned" }).props.onPress(); + }); + act(() => { + renderer.root.findByProps({ testID: "segment-earned" }).props.onPress(); + }); + const json = JSON.stringify(renderer.toJSON()); + expect(json).toContain("Earned"); + expect(json).toContain("Spent"); + }); + + it("masks amounts when hidden", () => { + const renderer = render( +