import * as React from "react"; import { useCallback, useMemo, useState } from "react"; import { cn } from "@/lib/utils"; import { formatCurrency } from "@/lib/format-currency"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ExpenseBarChart } from "@/components/ui/expense-bar-chart"; import type { ExpenseBarChartData } from "@/components/ui/expense-bar-chart"; import { ExpenseDetailItem, SharedEqualToggle, } from "@/components/ui/expense-detail-item"; import type { ExpenseType } from "@/components/ui/expense-detail-item"; import { RotateCcw } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; // ─── Types ──────────────────────────────────────────────────────────────────── export type { ExpenseType }; export type ExpenseItem = { id: string; /** Expense category type — drives the icon in ExpenseDetailItem */ type: ExpenseType | string; /** Display label */ label: string; /** Optional tooltip description */ description?: string; /** Current monthly expense amount */ amount: number; /** Whether this expense is shared between applicants */ isShared?: boolean; /** Percentage attributed to the main applicant (0–100) */ sharedPercent?: number; }; export type ExpenseItemUpdates = Pick< ExpenseItem, "type" | "label" | "amount" | "isShared" | "sharedPercent" >; export type ExpenseWorkDetailsProps = { /** Rendered as "{applicantName}'s Expenses" in the heading */ applicantName: string; /** Total from Open Banking — shown in the summary card above the chart */ totalExpenses?: number; /** Monthly expense data for the ExpenseBarChart */ expenseData?: ExpenseBarChartData | null; /** Expense items shown in the Expenses Details section */ items: ExpenseItem[]; /** When true, shows "Expense shared equally" toggle on each item and in the section header */ hasCoApplicant?: boolean; onItemChange?: (id: string, updates: Partial) => void; /** Called when the global "shared equally" toggle is changed — receives the new value */ onAllSharedChange?: (shared: boolean) => void; /** "Add More +" — appends an empty row; the applicant picks the category in the row. */ onAddItem?: () => void; /** Re-seed the expenses from the connected bank data. */ onResyncFromBank?: () => void; /** "Add More Account +" button in the summary card */ onConnectMore?: () => void; className?: string; }; // ─── Constants ──────────────────────────────────────────────────────────────── export const EXPENSE_CATEGORIES: Array<{ type: ExpenseType; label: string }> = [ { type: "housing", label: "Housing" }, { type: "transport", label: "Transport" }, { type: "groceries", label: "Groceries" }, { type: "childcare", label: "Childcare" }, { type: "insurance", label: "Insurance" }, { type: "entertainment", label: "Recreation & Entertainment" }, { type: "utilities", label: "Utilities" }, { type: "other", label: "Other" }, ]; // ─── Component ──────────────────────────────────────────────────────────────── /** * ExpenseWorkDetails — Expenses section of the loan application wizard. * * Shows: * 1. "{applicantName}'s Expenses" heading * 2. Open Banking summary card — total + "Add More Account +" + bare bar chart * 3. Expenses Details — list of ExpenseDetailItems with icon, progress bar, * per-item shared toggle (when hasCoApplicant), and currency+slider input * 4. "Add More +" dropdown to select a new expense category * * Shared controls are only shown when hasCoApplicant=true (joint application). * * Figma: WealthX-Backoffice---Mobile-App — node 19308:53437 */ export function ExpenseWorkDetails({ applicantName, totalExpenses, expenseData, items, hasCoApplicant = false, onItemChange, onAllSharedChange, onAddItem, onResyncFromBank, onConnectMore, className, }: ExpenseWorkDetailsProps) { const [globalShared, setGlobalShared] = useState(false); const itemsTotal = useMemo( () => items.reduce((sum, i) => sum + i.amount, 0), [items], ); // A category can only be used once, so a new row only offers what is left. const remainingCategories = useMemo(() => { const used = new Set(items.map((i) => i.type).filter(Boolean)); return EXPENSE_CATEGORIES.filter((c) => !used.has(c.type)); }, [items]); const handleGlobalSharedChange = useCallback( (next: boolean) => { setGlobalShared(next); items.forEach((item) => onItemChange?.(item.id, { isShared: next })); onAllSharedChange?.(next); }, [items, onItemChange, onAllSharedChange], ); return (

{applicantName}'s Expenses

{totalExpenses !== undefined && (
{formatCurrency(totalExpenses)} Total latest 12 months
{expenseData && ( )}
)}

Expenses Details

{onResyncFromBank && ( } > Re-sync from bank details )}
{hasCoApplicant && ( )}
{items.map((item) => ( onItemChange?.(item.id, { type, label: EXPENSE_CATEGORIES.find((c) => c.type === type)?.label ?? "Other", }) } onAmountChange={(amount) => onItemChange?.(item.id, { amount })} onSharedChange={(isShared) => onItemChange?.(item.id, { isShared }) } onSharedPercentChange={(sharedPercent) => onItemChange?.(item.id, { sharedPercent }) } /> ))}
); }