import React, { useState } from "react";
import { Button } from "./button";
import { Separator } from "./separator";
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from "./accordion";
import {
PropertyCard,
DebtCard,
OtherLiabilityCard,
AlertCard,
type PropertyCardProps,
type DebtCardProps,
type OtherLiabilityCardProps,
type AlertCardProps,
type AlertActionType,
type AlertSeverity,
} from "./financial-cards";
import {
FinancialCardHeader,
FinancialDetailField,
FinancialLineItem,
FinancialSectionLabel,
FinancialSubtotalFrame,
FinancialSubtotalBlock,
} from "./financial-primitives";
/**
* Financial section components — WealthX DS (Level 4)
*
* Composed from Level 3 cards into full page sections.
* Used inside the Summary Report Drawer and Opportunity Details Drawer.
*
* ```
* Level 2 → FinancialDetailField, FinancialLvrBar … (financial-primitives)
* Level 3 → PropertyCard, DebtCard, AlertCard … (financial-cards)
* Level 4 → PropertyHoldingsSection, DebtSection … ← here
* Level 5 → SummaryReportDrawer, OpportunityDetailsDrawer …
* ```
*
* Component inventory:
* FinancialViewSection — bordered card wrapping income + properties + current liabilities
* PropertyHoldingsSection — grid of PropertyCards under a section header
* DebtSection — grid of DebtCards under a section header
* OtherLiabilitiesSection — grid of OtherLiabilityCards under a section header
* IncomeExpenseSection — 3-column income / expenses / future-payments grid
* AlertAccordion — collapsible accordion wrapping a grid of AlertCards
* LoanScenarioSection — read-only Loan Quiz card (4-column grid)
* FinancialBottomSummary — 3 stacked summary cards: cashflow / assets / net position
*/
// ---------------------------------------------------------------------------
// PropertyHoldingsSection
// ---------------------------------------------------------------------------
export interface PropertyHoldingsSectionProps {
/** Section heading. Defaults to "Property Holdings". */
title?: string;
/** Each item maps 1:1 to a PropertyCard. */
items: PropertyCardProps[];
/**
* When true renders columns without individual card borders, separated by a 1px divider.
* Use inside `FinancialViewSection` to match the Figma Financial View card layout.
*/
borderless?: boolean;
}
/** Two-column grid of PropertyCards under a section header. */
export function PropertyHoldingsSection({
title = "Property Holdings",
items,
borderless = false,
}: PropertyHoldingsSectionProps) {
if (items.length === 0) return null;
return (
{title}
{borderless ? (
{items.map((item, i) => (
))}
) : (
{items.map((item, i) => (
))}
)}
);
}
// ---------------------------------------------------------------------------
// DebtSection
// ---------------------------------------------------------------------------
export interface DebtSectionProps {
/** Section heading. Defaults to "Mortgages & Investment Loans". */
title?: string;
/** Each item maps 1:1 to a DebtCard. */
items: DebtCardProps[];
}
/** Two-column grid of DebtCards under a section header. */
export function DebtSection({
title = "Mortgages & Investment Loans",
items,
}: DebtSectionProps) {
if (items.length === 0) return null;
return (
{title}
{items.map((item, i) => (
))}
);
}
// ---------------------------------------------------------------------------
// OtherLiabilitiesSection
// ---------------------------------------------------------------------------
export interface OtherLiabilitiesSectionProps {
/** Section heading. Defaults to "Other Liabilities". */
title?: string;
/** Each item maps 1:1 to an OtherLiabilityCard. */
items: OtherLiabilityCardProps[];
}
/** Two-column grid of OtherLiabilityCards under a section header. */
export function OtherLiabilitiesSection({
title = "Other Liabilities",
items,
}: OtherLiabilitiesSectionProps) {
if (items.length === 0) return null;
return (
{title}
{items.map((item, i) => (
))}
);
}
// ---------------------------------------------------------------------------
// IncomeExpenseSection
// ---------------------------------------------------------------------------
export interface SummaryLineItem {
label: string;
/** Formatted value string e.g. "$9,500". Renders "—" when falsy. */
value?: string | null;
/**
* When true renders the value in `text-destructive`.
* Use for expenses, liabilities, or negative cashflow rows.
*/
destructive?: boolean;
}
export interface SummaryColumn {
/** Column heading override. */
label?: string;
items: SummaryLineItem[];
/** Formatted monthly average displayed in the tinted footer. */
monthlyAverage?: string;
/** Formatted 12-month total displayed in the tinted footer. */
totalLast12Months?: string;
}
export interface IncomeExpenseSectionProps {
income?: SummaryColumn;
expenses?: SummaryColumn;
futurePayments?: SummaryColumn;
/** Override the "Future Payments" column heading. */
futurePaymentsHeading?: string;
}
/**
* Three-column layout: income | expenses | future payments.
*
* Each column has a header, a scrollable list of `FinancialLineItem` rows,
* and a brand-tinted `FinancialSubtotalFrame` footer.
*/
export function IncomeExpenseSection({
income,
expenses,
futurePayments,
futurePaymentsHeading = "Future Payments",
}: IncomeExpenseSectionProps) {
return (
{/* Income */}
{income?.label ?? "Average Monthly Income"}
{(income?.items ?? []).map((item) => (
))}
{/* Expenses */}
{expenses?.label ?? "Monthly Expenses and Commitments"}
{(expenses?.items ?? []).map((item) => (
))}
{/* Future Payments */}
{futurePayments?.label ?? futurePaymentsHeading}
{(futurePayments?.items ?? []).map((item) => (
))}
);
}
// ---------------------------------------------------------------------------
// AlertAccordion
// ---------------------------------------------------------------------------
const SEVERITY_BG: Record = {
NEED_ACTION: "bg-destructive text-destructive-foreground",
WATCH: "bg-warning text-warning-foreground",
INSIGHT: "bg-success text-success-foreground",
};
export interface AlertAccordionSavePayload {
alertId: string;
action: AlertActionType;
}
export interface AlertAccordionProps {
/** List of alerts — each maps 1:1 to an AlertCard. */
alerts: AlertCardProps[];
/**
* Called when the user clicks Save.
* Receives only alerts with a pending dismiss or snooze action selected.
* When omitted the Save button is never shown.
*/
onSaveActions?: (actions: AlertAccordionSavePayload[]) => void;
/** When `true` the Save button shows "Saving…" and is disabled. */
isSaving?: boolean;
}
/**
* Collapsible accordion wrapping a 3-column grid of AlertCards.
*
* - The trigger header shows the section title + severity count badges.
* - A Save button appears when the user has pending dismiss/snooze selections
* and `onSaveActions` is provided.
* - Returns `null` when `alerts` is empty.
*/
export function AlertAccordion({
alerts,
onSaveActions,
isSaving = false,
}: AlertAccordionProps) {
const [actionMap, setActionMap] = useState>(
{},
);
const handleActionChange = (id: string) => (action: AlertActionType) => {
setActionMap((prev) => ({ ...prev, [id]: action }));
};
const handleSave = (e: React.MouseEvent) => {
e.stopPropagation();
onSaveActions?.(
Object.entries(actionMap).map(([alertId, action]) => ({
alertId,
action,
})),
);
};
const countBySeverity = alerts.reduce>((acc, a) => {
acc[a.severityCode] = (acc[a.severityCode] || 0) + 1;
return acc;
}, {});
const hasPendingActions = Object.keys(actionMap).length > 0;
if (alerts.length === 0) return null;
return (
Alerts
{(["INSIGHT", "WATCH", "NEED_ACTION"] as const).map((code) =>
countBySeverity[code] ? (
{countBySeverity[code]}
) : null,
)}
{hasPendingActions && onSaveActions && (
)}
{alerts.map((alert) => (
))}
);
}
// ---------------------------------------------------------------------------
// LoanScenarioSection
// ---------------------------------------------------------------------------
export interface LoanScenarioSectionProps {
/** Override the section heading. Defaults to "Loan Scenario (Loan Quiz)". */
title?: string;
/** "Lending Type" — e.g. "Home Loan", "Investment Loan" */
lendingType?: string;
purposeOfLoan?: string;
loanAmount?: string;
propertyEstimate?: string;
estLvr?: string;
/** Cash or deposit available e.g. "$120,000" */
cashDeposit?: string;
propertyAddress?: string;
duration?: string;
importantFeatures?: string;
topThreePriorities?: string;
}
/**
* Read-only Loan Quiz card — 4-column grid matching the LoanQuiz component in backoffice.
*
* Row 1: Lending Type | Purpose of Loan | Loan Amount | Property Estimate
* Row 2: EST LVR | Cash/Deposit | Property Address | Duration
* Row 3: Important Features | Top Three Priorities
*
* Mirrors `LoanQuiz.tsx` in backoffice OpportunityDetailsDrawer.
*/
export function LoanScenarioSection({
title = "Loan Scenario (Loan Quiz)",
lendingType,
purposeOfLoan,
loanAmount,
propertyEstimate,
estLvr,
cashDeposit,
propertyAddress,
duration,
importantFeatures,
topThreePriorities,
}: LoanScenarioSectionProps) {
return (
{title}
{/* Row 1 */}
{/* Row 2 */}
{/* Row 3 */}
);
}
// ---------------------------------------------------------------------------
// FinancialBottomSummary
// ---------------------------------------------------------------------------
export interface BottomSummaryLineItem {
label: string;
value?: string | null;
destructive?: boolean;
}
export interface FinancialBottomSummaryProps {
/** Incoming vs Outgoing Summary card items. */
cashflowItems?: BottomSummaryLineItem[];
/** Net surplus/deficit value e.g. "$1,050". */
netSurplus?: string;
/** Whether net surplus is negative (renders in destructive color). */
netSurplusDestructive?: boolean;
/** Assets card items. */
assetItems?: BottomSummaryLineItem[];
/** Total assets value e.g. "$3,630,000". */
totalAssets?: string;
/** Total liabilities value e.g. "$1,263,200". Renders in destructive. */
totalLiabilities?: string;
/** Net position value e.g. "$2,366,800". */
netPosition?: string;
/** Whether net position is negative. */
netPositionDestructive?: boolean;
}
/**
* Three summary cards stacked vertically:
* 1. Incoming vs Outgoing Summary — cashflow line items + net surplus
* 2. Assets — asset line items + total
* 3. Net Position — total liabilities + assets + net position
*
* Mirrors `financial-bottom-summary.tsx` in backoffice.
*/
export function FinancialBottomSummary({
cashflowItems = [],
netSurplus,
netSurplusDestructive = false,
assetItems = [],
totalAssets,
totalLiabilities,
netPosition,
netPositionDestructive = false,
}: FinancialBottomSummaryProps) {
return (
{/* Card 1: Incoming vs Outgoing Summary */}
Incoming vs Outgoing Summary
{cashflowItems.map((item) => (
))}
{/* Card 2: Assets */}
Assets
{assetItems.map((item) => (
))}
{/* Card 3: Net Position */}
);
}
// ---------------------------------------------------------------------------
// FinancialViewSection
// ---------------------------------------------------------------------------
export interface FinancialViewSectionProps {
/** Override the "Financial View" section label. */
title?: string;
income?: IncomeExpenseSectionProps["income"];
expenses?: IncomeExpenseSectionProps["expenses"];
futurePayments?: IncomeExpenseSectionProps["futurePayments"];
futurePaymentsHeading?: string;
/** Section heading for property columns. Defaults to "Property Assets and Liabilities". */
propertyTitle?: string;
/** Each item renders as a borderless property column with a 1px divider. */
properties?: PropertyCardProps[];
/** Section heading for current liabilities. Defaults to "Current Liabilities". */
currentLiabilitiesTitle?: string;
/** Each item maps 1:1 to a DebtCard. */
currentLiabilities?: DebtCardProps[];
}
/**
* Composite Financial View card — wraps income, property and current liabilities
* inside a single bordered container, matching the Figma Financial View design.
*
* Property columns render without individual card borders (borderless),
* separated by a 1px divider — same visual language as the income columns.
* Current Liabilities (DebtCards) retain their individual borders.
*/
export function FinancialViewSection({
title = "Financial View",
income,
expenses,
futurePayments,
futurePaymentsHeading,
propertyTitle = "Property Assets and Liabilities",
properties = [],
currentLiabilitiesTitle = "Current Liabilities",
currentLiabilities = [],
}: FinancialViewSectionProps) {
return (
{title}
{properties.length > 0 && (
<>
>
)}
{currentLiabilities.length > 0 && (
<>
>
)}
);
}