feat(insights): analytics drill-down, fee tracking, and category improvements

- Monthly spend chart with category breakdown drill-down
- Merchant frequency and spend analytics with per-merchant history
- Subscription detection and recurring charge tracking
- Fee and interest analytics endpoint
- Expanded category list with formatCategory display helper
This commit is contained in:
2026-03-14 20:06:24 +11:00
parent 9f90d8726f
commit 278e57354c
8 changed files with 291 additions and 50 deletions
+6 -7
View File
@@ -3,12 +3,11 @@ import { getCurrentUser } from "@/lib/auth";
import { queryRaw } from "@/lib/db";
// Split-adjusted amount helper (positive for spend, negative for refunds)
const MY_AMOUNT = `CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 WHEN o.my_share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * o.my_share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END`;
const SPEND_EXPR = `
CASE
WHEN t.transaction_type IN ('refund', 'credit') THEN
-(CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END)
ELSE
(CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END)
WHEN t.transaction_type IN ('refund', 'credit') THEN -(${MY_AMOUNT})
ELSE (${MY_AMOUNT})
END
`;
@@ -44,18 +43,18 @@ export async function GET(req: NextRequest) {
COUNT(*) FILTER (WHERE t.transaction_type IN ('refund', 'credit'))::int as refund_count,
COALESCE(SUM(
CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN
CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END
${MY_AMOUNT}
ELSE 0 END
), 0)::numeric(12,2) as gross_spend,
COALESCE(SUM(
CASE WHEN t.transaction_type IN ('refund', 'credit') THEN
CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END
${MY_AMOUNT}
ELSE 0 END
), 0)::numeric(12,2) as total_refunds,
SUM(${SPEND_EXPR})::numeric(12,2) as net_spend,
AVG(
CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN
CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END
${MY_AMOUNT}
END
)::numeric(10,2) as avg_debit,
MIN(t.transaction_date)::text as first_seen,