import * as React from "react"; import { useState } from "react"; import { Pencil, Plus, Trash2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; import { Field, FieldLabel } from "@/components/ui/field"; import { LoanOptionGroup } from "@/components/ui/loan-option-group"; import { InterestRateInput } from "@/components/ui/interest-rate-input"; import { AddressAutocomplete, CurrencyInputWithSlider, OwnershipSplit, type AddressOption, type OwnershipOwner, } from "@/components/ui/form-primitives"; import { FREQUENCY_OPTIONS, YES_NO_OPTIONS } from "@/lib/loan-constants"; import { formatCurrency } from "@/lib/format-currency"; // ─── Types ────────────────────────────────────────────────────────────────── export type AssetOwner = OwnershipOwner; export type AssetItem = { id: string; /** Summary label shown in the collapsed row (address for a property). */ description: string; estimatedValue: number; /** Secondary line in the collapsed row (e.g. "Owner Occupied"). */ subLabel?: string; // ── Property / asset detail fields (edited via the inline form) ── address?: string; category?: "owner-occupier" | "investment" | ""; rentalIncome?: number; rentalIncomeFrequency?: "monthly" | "weekly"; hasMortgage?: boolean; mortgageOwing?: number; mortgageOriginal?: number; mortgageRepayment?: number; mortgageRepaymentFrequency?: "monthly" | "weekly"; mortgageInterestRate?: number; owners?: AssetOwner[]; }; export type AssetGroup = { assetType: string; label: string; items: AssetItem[]; }; export type AssetAccordionProps = { groups: AssetGroup[]; /** Which group values are expanded (controlled). */ openGroups?: string[]; onToggleGroup?: (assetType: string) => void; /** Create/update an item. `item.id === ""` means a newly-added item. */ onSaveItem?: (assetType: string, item: AssetItem) => void; onDeleteItem?: (assetType: string, itemId: string) => void; /** Applicants used to seed the ownership split on a new item. */ owners?: AssetOwner[]; /** Address suggestions for the property autocomplete. */ addressSuggestions?: AddressOption[]; /** Accessible label for the accordion landmark region. */ "aria-label"?: string; className?: string; }; const NEW_ITEM_ID = "__new__"; const CATEGORY_OPTIONS = [ { value: "owner-occupier", label: "Owner Occupier" }, { value: "investment", label: "Investment" }, ]; function blankAssetItem(assetType: string, owners: AssetOwner[]): AssetItem { return { id: "", description: "", estimatedValue: assetType === "property" ? 600_000 : 0, address: "", category: "", rentalIncome: 0, rentalIncomeFrequency: "monthly", hasMortgage: false, mortgageOwing: 0, mortgageOriginal: 0, mortgageRepayment: 0, mortgageRepaymentFrequency: "monthly", mortgageInterestRate: 0, owners, }; } // ─── Component ────────────────────────────────────────────────────────────── export function AssetAccordion({ groups, openGroups, onToggleGroup, onSaveItem, onDeleteItem, owners = [{ id: "main", name: "Main Applicant", share: 100 }], addressSuggestions, "aria-label": ariaLabel = "Asset list", className, }: AssetAccordionProps) { // Which item is currently open in the inline edit form (or a new draft). const [editing, setEditing] = useState<{ assetType: string; itemId: string; } | null>(null); const [draft, setDraft] = useState(null); const totalAssets = groups .flatMap((g) => g.items) .reduce((sum, item) => sum + item.estimatedValue, 0); const startAdd = (assetType: string) => { setDraft(blankAssetItem(assetType, owners)); setEditing({ assetType, itemId: NEW_ITEM_ID }); }; const startEdit = (assetType: string, item: AssetItem) => { // Pre-fill the form from the row summary for items that predate the // detailed form (e.g. seed mock data only has description + value). setDraft({ ...item, address: item.address ?? (assetType === "property" ? item.description : ""), category: item.category ?? "", owners: item.owners ?? owners, }); setEditing({ assetType, itemId: item.id }); }; const cancel = () => { setEditing(null); setDraft(null); }; const save = (assetType: string, item: AssetItem) => { onSaveItem?.(assetType, item); cancel(); }; return (

Assets

Total: {formatCurrency(totalAssets)}
{ const prev = new Set(openGroups ?? []); const next = new Set(vals as string[]); for (const v of next) { if (!prev.has(v)) onToggleGroup?.(v); } for (const v of prev) { if (!next.has(v)) onToggleGroup?.(v); } }} > {groups.map((group) => { const isAdding = editing?.assetType === group.assetType && editing.itemId === NEW_ITEM_ID; // One form element per group — at most one is shown at a time // (editing an existing item, or the new draft). const form = editing?.assetType === group.assetType && draft ? ( save(group.assetType, updated)} onCancel={cancel} /> ) : null; return (
{group.label} {group.items.length}{" "} {group.items.length === 1 ? "item" : "items"} ·{" "} {formatCurrency( group.items.reduce((s, i) => s + i.estimatedValue, 0), )}
{group.items.length === 0 && !isAdding ? (

No {group.label.toLowerCase()} added yet.

) : ( group.items.map((item) => editing?.assetType === group.assetType && editing.itemId === item.id && form ? ( React.cloneElement(form, { key: item.id }) ) : ( startEdit(group.assetType, item)} onDelete={() => onDeleteItem?.(group.assetType, item.id) } /> ), ) )} {isAdding && form} {!isAdding && ( )}
); })}
); } // ─── Collapsed summary row ────────────────────────────────────────────────── function AssetItemRow({ item, onEdit, onDelete, }: { item: AssetItem; onEdit?: () => void; onDelete?: () => void; }) { return (
{item.description || "Untitled asset"} {item.subLabel && ( {item.subLabel} )} {formatCurrency(item.estimatedValue)}
); } // ─── Inline add / edit form ───────────────────────────────────────────────── function AssetItemForm({ assetType, initial, addressSuggestions, onSave, onCancel, }: { assetType: string; initial: AssetItem; addressSuggestions?: AddressOption[]; onSave: (item: AssetItem) => void; onCancel: () => void; }) { const [item, setItem] = useState(initial); const set = (patch: Partial) => setItem((prev) => ({ ...prev, ...patch })); const isProperty = assetType === "property"; const estimateLabel = isProperty ? "Property estimate" : "Value estimate"; const canSave = isProperty ? Boolean(item.address && item.category) && item.estimatedValue > 0 : item.estimatedValue > 0; const handleSave = () => { // Keep the collapsed-row summary in sync with the form values. const subLabel = isProperty ? item.category === "investment" ? "Investment" : item.category === "owner-occupier" ? "Owner Occupied" : undefined : item.subLabel; onSave({ ...item, description: isProperty ? item.address || item.description : item.description, subLabel, }); }; return (
{isProperty && ( Property Address set({ address: v })} onSelect={(opt) => set({ address: opt.label })} /> )} {isProperty && ( How is the property used? set({ category: v as AssetItem["category"] })} /> )} {isProperty && item.category === "investment" && ( How much is your rental income?
set({ rentalIncome: n })} /> set({ rentalIncomeFrequency: v as AssetItem["rentalIncomeFrequency"], }) } />
)} {estimateLabel} set({ estimatedValue: n })} /> {isProperty && ( Does this property have a mortgage? set({ hasMortgage: v === "yes" })} /> )} {isProperty && item.hasMortgage && (
Current amount owing set({ mortgageOwing: n })} /> Original loan amount set({ mortgageOriginal: n })} /> How much are your loan repayments?
set({ mortgageRepayment: n })} /> set({ mortgageRepaymentFrequency: v as AssetItem["mortgageRepaymentFrequency"], }) } />
Interest rate set({ mortgageInterestRate: n })} />
)} {item.owners && item.owners.length > 1 && ( Ownership set({ owners: ow })} /> )}
); }