import * as React from "react"; import { useState, useCallback } from "react"; import { Baby, Car, DollarSign, Gift, Home, Info, ShoppingCart, Shield, Smartphone, type LucideIcon, } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { CurrencyInputWithSlider } from "@/components/ui/form-primitives"; // --------------------------------------------------------------------------- // SharedEqualToggle — "No [Switch] Yes" row + label, used both as the // per-item toggle here and the global toggle in ExpenseWorkDetails. // --------------------------------------------------------------------------- export type SharedEqualToggleProps = { checked: boolean; onCheckedChange?: (checked: boolean) => void; label?: string; ariaLabel?: string; /** "stacked" = label above the switch row (per-item); "inline" = all on one row (global toggle). */ layout?: "inline" | "stacked"; }; export function SharedEqualToggle({ checked, onCheckedChange, label = "Expense shared equally", ariaLabel, layout = "inline", }: SharedEqualToggleProps) { const toggleRow = (
No Yes
); if (layout === "stacked") { return (
{label} {toggleRow}
); } return (
{label} {toggleRow}
); } // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export type ExpenseType = | "housing" | "transport" | "groceries" | "childcare" | "insurance" | "entertainment" | "utilities" | "other"; export type ExpenseDetailItemProps = { /** Expense category type — drives the icon. Empty means "not chosen yet", * which renders the category picker instead of the icon + label. */ type: ExpenseType | string; /** Display label for the expense */ label: string; /** Categories still available to pick — only used while `type` is unset. */ categoryOptions?: Array<{ type: ExpenseType | string; label: string }>; /** Called when a category is chosen for a not-yet-typed item. */ onTypeSelect?: (type: string) => void; /** Placeholder for the category picker. */ categoryPlaceholder?: string; /** Optional tooltip description (shown via ⓘ icon) */ description?: string; /** Current monthly expense amount */ amount: number; /** Total of all expenses — used to calculate the progress bar ratio */ totalExpenses?: number; /** Whether there is a co-applicant — shows the shared toggle when true */ hasCoApplicant?: boolean; /** Whether this expense is shared between applicants */ isShared?: boolean; /** Percentage attributed to the main applicant (0–100) */ sharedPercent?: number; onAmountChange?: (amount: number) => void; onSharedChange?: (shared: boolean) => void; onSharedPercentChange?: (percent: number) => void; onDelete?: () => void; className?: string; }; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const EXPENSE_ICON_MAP: Record = { housing: Home, transport: Car, groceries: ShoppingCart, childcare: Baby, insurance: Shield, entertainment: Gift, utilities: Smartphone, // phone / internet / subscriptions other: DollarSign, }; // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- /** * ExpenseDetailItem — a single expense row in the loan wizard Expenses Details section. * * Left side: category icon + label + ⓘ, progress bar, optional shared toggle + percent. * Right side: currency input with slider. * * Figma: WealthX-Backoffice---Mobile-App — node 19308:53715 */ export function ExpenseDetailItem({ type, label, categoryOptions = [], onTypeSelect, categoryPlaceholder = "Choose Your Expense Item", description, amount, totalExpenses, hasCoApplicant = false, isShared = false, sharedPercent: initialPercent = 50, onAmountChange, onSharedChange, onSharedPercentChange, onDelete, className, }: ExpenseDetailItemProps) { // A freshly added row has no category yet — it shows a picker until one is chosen. const isNewItem = !type; const Icon = EXPENSE_ICON_MAP[type] ?? DollarSign; const fillPct = totalExpenses && totalExpenses > 0 ? Math.min(100, (amount / totalExpenses) * 100) : 0; const [percent, setPercent] = useState(String(initialPercent)); const handlePercentBlur = useCallback(() => { const parsed = Math.max(0, Math.min(100, parseInt(percent, 10) || 0)); setPercent(String(parsed)); onSharedPercentChange?.(parsed); }, [percent, onSharedPercentChange]); return (
{isNewItem ? ( ) : (
{label} {description && ( )}
)} {/* Full-width on mobile; capped at 274px (Figma left-column width) from sm+ */}
{hasCoApplicant && (
{isShared && (
setPercent(e.target.value)} onBlur={handlePercentBlur} className="pr-6 text-body-small" aria-label={`${label} shared percent`} /> %
)}
)}
{onDelete && ( )}
); }