import * as React from "react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { LoanOptionGroup } from "@/components/ui/loan-option-group"; import { CurrencyInputWithSlider, AddressAutocomplete, } from "@/components/ui/form-primitives"; import { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "@/components/ui/accordion"; import { IncomeBarChart } from "@/components/ui/income-bar-chart"; import type { IncomeBarChartData } from "@/components/ui/income-bar-chart"; import { FREQUENCY_OPTIONS } from "@/lib/loan-constants"; import { formatCurrency } from "@/lib/format-currency"; // ─── Constants ──────────────────────────────────────────────────────────────── const EMPLOYMENT_TYPE_OPTIONS = [ { value: "payg", label: "PAYG" }, { value: "self-employed", label: "Self Employed" }, { value: "unemployed", label: "Unemployed" }, { value: "retired", label: "Retired" }, ]; const EMPLOYMENT_STATUS_OPTIONS = [ { value: "primary", label: "Primary" }, { value: "secondary", label: "Secondary" }, ]; const EMPLOYMENT_BASIS_OPTIONS = [ { value: "contract", label: "Contract" }, { value: "temporary", label: "Temporary" }, { value: "casual", label: "Casual" }, ]; // ─── Types ──────────────────────────────────────────────────────────────────── export type IncomeEmploymentType = | "payg" | "self-employed" | "unemployed" | "retired"; export type IncomeEmploymentStatus = "primary" | "secondary"; export type IncomeEmploymentBasis = | "contract" | "temporary" | "casual" | "full-time" | "part-time"; export type IncomeFrequency = "monthly" | "weekly"; export type IncomeCompanyType = "public" | "private"; export type IncomeWorkSource = { id: string; /** Accordion header label — defaults to "Main Income Source" */ label?: string; employmentType?: IncomeEmploymentType; employmentStatus?: IncomeEmploymentStatus; employmentBasis?: IncomeEmploymentBasis; jobTitle?: string; startDate?: string; /** Whether still in this position — when false, endDate is required */ stillInPosition?: boolean; endDate?: string; companyName?: string; companyAddress?: string; incomeAmount?: number; incomeFrequency?: IncomeFrequency; companyType?: IncomeCompanyType; }; export type IncomeWorkDetailsProps = { /** Rendered as "{applicantName} Income" in the heading */ applicantName: string; /** Total from Open Banking — shown in summary card above the chart */ totalIncome?: number; /** Monthly income data for the IncomeBarChart */ incomeData?: IncomeBarChartData | null; /** Work Details accordion sources */ sources: IncomeWorkSource[]; onSourceChange?: (id: string, updates: Partial) => void; onAddSource?: () => void; /** "Add More Account +" button in the summary card */ onConnectMore?: () => void; className?: string; }; // ─── Income source form ─────────────────────────────────────────────────────── function IncomeSourceForm({ source, onChange, }: { source: IncomeWorkSource; onChange: (updates: Partial) => void; }) { return (
{/* Employment type */} { if (typeof v === "string") onChange({ employmentType: v as IncomeEmploymentType }); }} /> {/* Employment Status */} { if (typeof v === "string") onChange({ employmentStatus: v as IncomeEmploymentStatus }); }} /> {/* Employment Type (basis) */} { if (typeof v === "string") onChange({ employmentBasis: v as IncomeEmploymentBasis }); }} /> {/* Job Title */}
onChange({ jobTitle: e.target.value })} placeholder="e.g. Software Engineer" />
{/* Start Date + optional End Date (shown side-by-side when not still in position) */}
onChange({ startDate: e.target.value })} />
{source.stillInPosition === false && (
onChange({ endDate: e.target.value })} />
)}
{/* Still in position checkbox */}
onChange({ stillInPosition: checked === true }) } />
{/* Company Name */}
onChange({ companyName: e.target.value })} placeholder="e.g. Acme Corporation" />
{/* Company Address — autocomplete */}
onChange({ companyAddress: v })} />
{/* Income Amount + Frequency — input/slider left, toggle right */}
onChange({ incomeAmount: v })} /> { if (typeof v === "string") onChange({ incomeFrequency: v as IncomeFrequency }); }} />
{/* Type of Company */}
Type of Company onChange({ companyType: v as IncomeCompanyType }) } className="flex flex-row gap-4" >
); } // ─── Component ──────────────────────────────────────────────────────────────── /** * IncomeWorkDetails — Income section of the loan application wizard. * * Shows: * 1. "{applicantName} Income" heading * 2. Open Banking summary card — total, mini bar chart, "Add More Account +" * 3. Income Sources — "Total Income" indicator bar * 4. Work Details — accordion of employment source forms * * Figma: WealthX-Backoffice---Mobile-App — node 19308:53126 */ export function IncomeWorkDetails({ applicantName, totalIncome, incomeData, sources, onSourceChange, onAddSource, onConnectMore, className, }: IncomeWorkDetailsProps) { const [openItems, setOpenItems] = React.useState( sources.length > 0 ? [sources[0].id] : [], ); // Auto-open any source that was added after initial render React.useEffect(() => { setOpenItems((current) => { const currentSet = new Set(current); const newIds = sources .map((s) => s.id) .filter((id) => !currentSet.has(id)); return newIds.length > 0 ? [...current, ...newIds] : current; }); }, [sources]); return (
{/* Heading */}

{applicantName}'s Income

{/* Open Banking summary — left card + right chart */} {totalIncome !== undefined && (
{/* Left: bordered card — amount + button */}
{formatCurrency(totalIncome)} Total latest 12 months
{/* Right: bare bar chart with Y-axis + X-axis */} {incomeData && ( )}
)} {/* Income Sources */}

Income Sources

Total Income {/* Full-width indicator bar — green when totalIncome > 0, muted otherwise */}
{/* Work Details */}

Work Details

{sources.map((source) => ( {source.label ?? "Main Income Source"} onSourceChange?.(source.id, updates)} /> ))}
); }