@wealthx/shadcn 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +235 -138
- package/CHANGELOG.md +12 -0
- package/README.md +82 -0
- package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
- package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
- package/dist/chunk-2SF672SZ.mjs +161 -0
- package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
- package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
- package/dist/chunk-3WMX6KWS.mjs +245 -0
- package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
- package/dist/chunk-4CX4SBRO.mjs +153 -0
- package/dist/chunk-4MN6UQHG.mjs +443 -0
- package/dist/chunk-5QQVZTVZ.mjs +233 -0
- package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
- package/dist/chunk-6FCGKSZX.mjs +268 -0
- package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
- package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
- package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
- package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
- package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
- package/dist/chunk-A56YQQHG.mjs +402 -0
- package/dist/chunk-AH52LG6N.mjs +315 -0
- package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
- package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
- package/dist/chunk-D4ILTPOG.mjs +293 -0
- package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
- package/dist/chunk-DOH3EHX7.mjs +378 -0
- package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
- package/dist/chunk-ERGGHC2V.mjs +185 -0
- package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
- package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
- package/dist/chunk-FMAXJ2SI.mjs +71 -0
- package/dist/chunk-FZIXGLMV.mjs +173 -0
- package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
- package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
- package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
- package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
- package/dist/chunk-KMCGSZTX.mjs +177 -0
- package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
- package/dist/chunk-LE6YFY6D.mjs +209 -0
- package/dist/{chunk-HUVTPUV2.mjs → chunk-LLVQKSU3.mjs} +23 -19
- package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
- package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
- package/dist/chunk-NLCKVHWB.mjs +161 -0
- package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
- package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
- package/dist/chunk-NXA3CZ7A.mjs +248 -0
- package/dist/chunk-OGOYQ7BG.mjs +150 -0
- package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
- package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
- package/dist/chunk-PCPLO5HT.mjs +671 -0
- package/dist/chunk-PG6K5XEC.mjs +475 -0
- package/dist/chunk-PJHPSRYD.mjs +234 -0
- package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
- package/dist/chunk-PR6V5XKM.mjs +209 -0
- package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
- package/dist/chunk-QVKWW6KE.mjs +272 -0
- package/dist/chunk-RGU7HOEC.mjs +140 -0
- package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
- package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
- package/dist/chunk-RRBS6D63.mjs +163 -0
- package/dist/chunk-SMQ3DG25.mjs +285 -0
- package/dist/chunk-SPJ5KXW7.mjs +199 -0
- package/dist/chunk-SYOD63OZ.mjs +225 -0
- package/dist/chunk-UFYSFDER.mjs +42 -0
- package/dist/chunk-VACKZOMY.mjs +190 -0
- package/dist/chunk-VLQZANBF.mjs +42 -0
- package/dist/chunk-WA6O6EUR.mjs +1885 -0
- package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
- package/dist/chunk-WG6JGJXB.mjs +165 -0
- package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
- package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
- package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
- package/dist/chunk-Y6DWJSKZ.mjs +79 -0
- package/dist/chunk-YKPROFLB.mjs +161 -0
- package/dist/{chunk-K76E2TQU.mjs → chunk-ZRO5JO3H.mjs} +107 -67
- package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
- package/dist/components/ui/accordion.js +40 -4
- package/dist/components/ui/accordion.mjs +2 -2
- package/dist/components/ui/add-column-modal.js +789 -0
- package/dist/components/ui/add-column-modal.mjs +17 -0
- package/dist/components/ui/add-lead-modal.js +647 -0
- package/dist/components/ui/add-lead-modal.mjs +16 -0
- package/dist/components/ui/ai-assistant-drawer.js +686 -0
- package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
- package/dist/components/ui/alert-dialog.js +37 -5
- package/dist/components/ui/alert-dialog.mjs +4 -4
- package/dist/components/ui/alert.js +37 -11
- package/dist/components/ui/alert.mjs +2 -2
- package/dist/components/ui/avatar.js +36 -8
- package/dist/components/ui/avatar.mjs +2 -2
- package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
- package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
- package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
- package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
- package/dist/components/ui/badge.js +37 -2
- package/dist/components/ui/badge.mjs +2 -2
- package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
- package/dist/components/ui/button.js +35 -3
- package/dist/components/ui/button.mjs +2 -2
- package/dist/components/ui/calendar.js +43 -19
- package/dist/components/ui/calendar.mjs +3 -3
- package/dist/components/ui/card.js +40 -4
- package/dist/components/ui/card.mjs +2 -2
- package/dist/components/ui/cash-balance-line-chart.js +627 -0
- package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
- package/dist/components/ui/cashflow-bar-chart.js +650 -0
- package/dist/components/ui/cashflow-bar-chart.mjs +16 -0
- package/dist/components/ui/checkbox.js +36 -5
- package/dist/components/ui/checkbox.mjs +2 -3
- package/dist/components/ui/chip.js +37 -2
- package/dist/components/ui/chip.mjs +3 -3
- package/dist/components/ui/combobox.js +280 -0
- package/dist/components/ui/combobox.mjs +28 -0
- package/dist/components/ui/data-table.js +160 -88
- package/dist/components/ui/data-table.mjs +10 -11
- package/dist/components/ui/date-picker.js +44 -20
- package/dist/components/ui/date-picker.mjs +6 -7
- package/dist/components/ui/dialog.js +44 -12
- package/dist/components/ui/dialog.mjs +4 -4
- package/dist/components/ui/drawer.js +46 -10
- package/dist/components/ui/drawer.mjs +3 -3
- package/dist/components/ui/dropdown-menu.js +40 -16
- package/dist/components/ui/dropdown-menu.mjs +3 -3
- package/dist/components/ui/empty.js +41 -5
- package/dist/components/ui/empty.mjs +2 -2
- package/dist/components/ui/expense-bar-chart.js +642 -0
- package/dist/components/ui/expense-bar-chart.mjs +16 -0
- package/dist/components/ui/field.js +53 -21
- package/dist/components/ui/field.mjs +4 -4
- package/dist/components/ui/financial-cards.js +1002 -0
- package/dist/components/ui/financial-cards.mjs +24 -0
- package/dist/components/ui/financial-drawers.js +637 -0
- package/dist/components/ui/financial-drawers.mjs +17 -0
- package/dist/components/ui/financial-primitives.js +218 -0
- package/dist/components/ui/financial-primitives.mjs +22 -0
- package/dist/components/ui/financial-sections.js +1422 -0
- package/dist/components/ui/financial-sections.mjs +30 -0
- package/dist/components/ui/form-primitives.js +682 -0
- package/dist/components/ui/form-primitives.mjs +19 -0
- package/dist/components/ui/income-bar-chart.js +641 -0
- package/dist/components/ui/income-bar-chart.mjs +16 -0
- package/dist/components/ui/input-group.js +43 -7
- package/dist/components/ui/input-group.mjs +5 -5
- package/dist/components/ui/input-otp.js +39 -3
- package/dist/components/ui/input-otp.mjs +2 -2
- package/dist/components/ui/input.js +34 -2
- package/dist/components/ui/input.mjs +2 -2
- package/dist/components/ui/kanban-column.js +1143 -0
- package/dist/components/ui/kanban-column.mjs +20 -0
- package/dist/components/ui/label.js +35 -7
- package/dist/components/ui/label.mjs +2 -2
- package/dist/components/ui/opportunity-card.js +960 -0
- package/dist/components/ui/opportunity-card.mjs +20 -0
- package/dist/components/ui/opportunity-edit-modals.js +3360 -0
- package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
- package/dist/components/ui/opportunity-summary-tab.js +4365 -0
- package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
- package/dist/components/ui/pagination.js +35 -3
- package/dist/components/ui/pagination.mjs +3 -3
- package/dist/components/ui/pipeline-alerts.js +103 -0
- package/dist/components/ui/pipeline-alerts.mjs +8 -0
- package/dist/components/ui/pipeline-board.js +1408 -0
- package/dist/components/ui/pipeline-board.mjs +24 -0
- package/dist/components/ui/pipeline-chart.js +216 -0
- package/dist/components/ui/pipeline-chart.mjs +10 -0
- package/dist/components/ui/pipeline-dialogs.js +1183 -0
- package/dist/components/ui/pipeline-dialogs.mjs +23 -0
- package/dist/components/ui/pipeline-primitives.js +300 -0
- package/dist/components/ui/pipeline-primitives.mjs +11 -0
- package/dist/components/ui/popover.js +45 -4
- package/dist/components/ui/popover.mjs +3 -3
- package/dist/components/ui/progress.js +33 -1
- package/dist/components/ui/progress.mjs +2 -2
- package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
- package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
- package/dist/components/ui/radio-group.js +33 -1
- package/dist/components/ui/radio-group.mjs +2 -2
- package/dist/components/ui/select.js +66 -26
- package/dist/components/ui/select.mjs +3 -3
- package/dist/components/ui/separator.js +33 -1
- package/dist/components/ui/separator.mjs +2 -2
- package/dist/components/ui/sheet.js +37 -9
- package/dist/components/ui/sheet.mjs +3 -3
- package/dist/components/ui/skeleton.js +33 -1
- package/dist/components/ui/skeleton.mjs +2 -2
- package/dist/components/ui/slider.js +86 -102
- package/dist/components/ui/slider.mjs +2 -2
- package/dist/components/ui/spinner.js +33 -1
- package/dist/components/ui/spinner.mjs +2 -2
- package/dist/components/ui/stage-timeline.js +579 -0
- package/dist/components/ui/stage-timeline.mjs +15 -0
- package/dist/components/ui/switch.js +37 -4
- package/dist/components/ui/switch.mjs +2 -3
- package/dist/components/ui/table.js +37 -5
- package/dist/components/ui/table.mjs +2 -2
- package/dist/components/ui/tabs.js +36 -12
- package/dist/components/ui/tabs.mjs +2 -2
- package/dist/components/ui/textarea.js +34 -2
- package/dist/components/ui/textarea.mjs +2 -2
- package/dist/components/ui/toggle-group.js +35 -4
- package/dist/components/ui/toggle-group.mjs +3 -4
- package/dist/components/ui/toggle.js +35 -4
- package/dist/components/ui/toggle.mjs +2 -3
- package/dist/components/ui/tooltip.js +51 -22
- package/dist/components/ui/tooltip.mjs +3 -3
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
- package/dist/components/ui/transactions-income-expense-bar-chart.js +516 -0
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +16 -0
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
- package/dist/index.js +11613 -2868
- package/dist/index.mjs +377 -164
- package/dist/lib/theme-provider.js +10 -1
- package/dist/lib/theme-provider.mjs +1 -1
- package/dist/lib/typography.js +8 -0
- package/dist/lib/typography.mjs +3 -1
- package/dist/lib/utils.js +33 -1
- package/dist/lib/utils.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +169 -6
- package/src/components/index.tsx +323 -13
- package/src/components/ui/accordion.tsx +6 -3
- package/src/components/ui/add-column-modal.tsx +339 -0
- package/src/components/ui/add-lead-modal.tsx +290 -0
- package/src/components/ui/ai-assistant-drawer.tsx +408 -0
- package/src/components/ui/alert-dialog.tsx +80 -54
- package/src/components/ui/alert.tsx +28 -28
- package/src/components/ui/avatar.tsx +30 -29
- package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
- package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
- package/src/components/ui/badge.tsx +17 -15
- package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
- package/src/components/ui/button.tsx +30 -27
- package/src/components/ui/calendar.tsx +53 -67
- package/src/components/ui/card.tsx +27 -24
- package/src/components/ui/cash-balance-line-chart.tsx +302 -0
- package/src/components/ui/cashflow-bar-chart.tsx +363 -0
- package/src/components/ui/chart-shared.tsx +261 -0
- package/src/components/ui/checkbox.tsx +30 -26
- package/src/components/ui/combobox.tsx +223 -0
- package/src/components/ui/data-table.tsx +160 -99
- package/src/components/ui/date-picker.tsx +0 -2
- package/src/components/ui/dialog.tsx +70 -60
- package/src/components/ui/drawer.tsx +57 -48
- package/src/components/ui/dropdown-menu.tsx +90 -82
- package/src/components/ui/empty.tsx +31 -27
- package/src/components/ui/expense-bar-chart.tsx +296 -0
- package/src/components/ui/field.tsx +70 -62
- package/src/components/ui/financial-cards.tsx +830 -0
- package/src/components/ui/financial-drawers.tsx +339 -0
- package/src/components/ui/financial-primitives.tsx +331 -0
- package/src/components/ui/financial-sections.tsx +672 -0
- package/src/components/ui/form-primitives.tsx +536 -0
- package/src/components/ui/income-bar-chart.tsx +297 -0
- package/src/components/ui/input-group.tsx +41 -34
- package/src/components/ui/input-otp.tsx +29 -24
- package/src/components/ui/input.tsx +8 -8
- package/src/components/ui/kanban-column.tsx +333 -0
- package/src/components/ui/label.tsx +9 -12
- package/src/components/ui/opportunity-card.tsx +616 -0
- package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
- package/src/components/ui/opportunity-summary-tab.tsx +579 -0
- package/src/components/ui/pipeline-alerts.tsx +74 -0
- package/src/components/ui/pipeline-board.tsx +268 -0
- package/src/components/ui/pipeline-chart.tsx +173 -0
- package/src/components/ui/pipeline-dialogs.tsx +303 -0
- package/src/components/ui/pipeline-primitives.tsx +108 -0
- package/src/components/ui/popover.tsx +41 -36
- package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
- package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
- package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
- package/src/components/ui/select.tsx +65 -52
- package/src/components/ui/sheet.tsx +55 -52
- package/src/components/ui/slider.tsx +54 -77
- package/src/components/ui/stage-timeline.tsx +205 -0
- package/src/components/ui/switch.tsx +42 -29
- package/src/components/ui/table.tsx +28 -28
- package/src/components/ui/tabs.tsx +22 -28
- package/src/components/ui/textarea.tsx +8 -8
- package/src/components/ui/toggle-group.tsx +0 -2
- package/src/components/ui/toggle.tsx +13 -15
- package/src/components/ui/tooltip.tsx +30 -28
- package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
- package/src/components/ui/transactions-income-expense-bar-chart.tsx +205 -0
- package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
- package/src/lib/theme-provider.tsx +10 -0
- package/src/lib/typography.ts +9 -0
- package/src/lib/utils.ts +41 -3
- package/src/styles/globals.css +371 -124
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +32 -0
- package/dist/chunk-K74JRTJR.mjs +0 -105
- package/dist/chunk-V7CNWJT3.mjs +0 -10
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import { Sheet, SheetContent } from "./sheet";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
|
|
6
|
+
import { Spinner } from "./spinner";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Financial drawer shells — WealthX DS (Level 5)
|
|
10
|
+
*
|
|
11
|
+
* These are the outermost drawer containers. They compose the Level 4 sections
|
|
12
|
+
* into full-screen right-side panels used across backoffice.
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* Level 2 → FinancialDetailField, FinancialLvrBar … (financial-primitives)
|
|
16
|
+
* Level 3 → PropertyCard, DebtCard, AlertCard … (financial-cards)
|
|
17
|
+
* Level 4 → PropertyHoldingsSection, DebtSection … (financial-sections)
|
|
18
|
+
* Level 5 → SummaryReportDrawer, OpportunityDetailsDrawer … ← here
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Both components are **visual shells** — they own the chrome (header, tabs,
|
|
22
|
+
* scrollable viewport) but delegate all data and content to the consuming app
|
|
23
|
+
* via slot props.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// SummaryReportDrawer
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export type SummaryViewMode = "individual" | "joint";
|
|
31
|
+
export type SummaryJointSubTab = "joint" | "userA" | "userB";
|
|
32
|
+
|
|
33
|
+
export interface SummaryReportDrawerProps {
|
|
34
|
+
open: boolean;
|
|
35
|
+
onOpenChange: (open: boolean) => void;
|
|
36
|
+
/** Primary contact name shown in the header. */
|
|
37
|
+
contactName?: string;
|
|
38
|
+
/** Whether a secondary contact has been added for joint view. */
|
|
39
|
+
hasJointView?: boolean;
|
|
40
|
+
/** Active view mode — individual or joint. */
|
|
41
|
+
viewMode?: SummaryViewMode;
|
|
42
|
+
onViewModeChange?: (mode: SummaryViewMode) => void;
|
|
43
|
+
/** Active sub-tab when in joint + joint mode. */
|
|
44
|
+
jointSubTab?: SummaryJointSubTab;
|
|
45
|
+
onJointSubTabChange?: (tab: SummaryJointSubTab) => void;
|
|
46
|
+
/** Display name for the primary applicant in joint view. */
|
|
47
|
+
jointMainUserName?: string;
|
|
48
|
+
/** Display name for the secondary applicant in joint view. */
|
|
49
|
+
jointCoApplicantName?: string;
|
|
50
|
+
/** Called when "Create Joint View" is clicked (no joint view yet). */
|
|
51
|
+
onCreateJointView?: () => void;
|
|
52
|
+
/** Called when "Remove Joint View" is clicked. */
|
|
53
|
+
onRemoveJointView?: () => void;
|
|
54
|
+
/**
|
|
55
|
+
* Row of action buttons rendered below the header chrome.
|
|
56
|
+
* Typically: Export button, Send Report button, etc.
|
|
57
|
+
*/
|
|
58
|
+
actionButtons?: React.ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Alert accordion shown below the action buttons row.
|
|
61
|
+
* Pass an `<AlertAccordion>` from financial-sections when alerts exist.
|
|
62
|
+
*/
|
|
63
|
+
alerts?: React.ReactNode;
|
|
64
|
+
/**
|
|
65
|
+
* When true, hides `children` and `actionButtons`/`alerts` and shows a
|
|
66
|
+
* centered `<Spinner>` instead.
|
|
67
|
+
*/
|
|
68
|
+
isLoading?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The scrollable financial content — stack Level 4 sections and charts here.
|
|
71
|
+
* Recommended order (mirrors backoffice): charts → PropertyHoldings → Debt →
|
|
72
|
+
* OtherLiabilities → IncomeExpense → AlertAccordion.
|
|
73
|
+
*/
|
|
74
|
+
children?: React.ReactNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Right-side sheet shell for the Summary Report Drawer.
|
|
79
|
+
*
|
|
80
|
+
* Handles the header chrome (contact name, Individual/Joint view toggle,
|
|
81
|
+
* joint sub-tabs, close button) and a scrollable content viewport.
|
|
82
|
+
* All financial content is passed as `children`.
|
|
83
|
+
*/
|
|
84
|
+
export function SummaryReportDrawer({
|
|
85
|
+
open,
|
|
86
|
+
onOpenChange,
|
|
87
|
+
contactName = "Contact",
|
|
88
|
+
hasJointView = false,
|
|
89
|
+
viewMode = "individual",
|
|
90
|
+
onViewModeChange,
|
|
91
|
+
jointSubTab = "joint",
|
|
92
|
+
onJointSubTabChange,
|
|
93
|
+
jointMainUserName = "User A",
|
|
94
|
+
jointCoApplicantName = "User B",
|
|
95
|
+
onCreateJointView,
|
|
96
|
+
onRemoveJointView,
|
|
97
|
+
actionButtons,
|
|
98
|
+
alerts,
|
|
99
|
+
isLoading = false,
|
|
100
|
+
children,
|
|
101
|
+
}: SummaryReportDrawerProps) {
|
|
102
|
+
return (
|
|
103
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
104
|
+
<SheetContent
|
|
105
|
+
side="right"
|
|
106
|
+
showCloseButton={false}
|
|
107
|
+
className="w-[min(1080px,90vw)] max-w-none p-0"
|
|
108
|
+
>
|
|
109
|
+
<div className="flex max-h-[100dvh] min-h-0 flex-1 flex-col overflow-hidden px-8 py-6">
|
|
110
|
+
{/* ── Header chrome ── */}
|
|
111
|
+
<div className="shrink-0 space-y-4">
|
|
112
|
+
<div className="flex flex-col gap-3">
|
|
113
|
+
{/* Top row: view mode toggle + close */}
|
|
114
|
+
<div className="flex items-center justify-between">
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
{hasJointView ? (
|
|
117
|
+
<>
|
|
118
|
+
<div className="flex gap-1">
|
|
119
|
+
<Button
|
|
120
|
+
variant={
|
|
121
|
+
viewMode === "individual" ? "default" : "outline"
|
|
122
|
+
}
|
|
123
|
+
size="sm"
|
|
124
|
+
onClick={() => onViewModeChange?.("individual")}
|
|
125
|
+
>
|
|
126
|
+
Individual View
|
|
127
|
+
</Button>
|
|
128
|
+
<Button
|
|
129
|
+
variant={viewMode === "joint" ? "default" : "outline"}
|
|
130
|
+
size="sm"
|
|
131
|
+
onClick={() => onViewModeChange?.("joint")}
|
|
132
|
+
>
|
|
133
|
+
Joint View
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
<Button
|
|
137
|
+
variant="outline"
|
|
138
|
+
size="sm"
|
|
139
|
+
onClick={onRemoveJointView}
|
|
140
|
+
>
|
|
141
|
+
Remove Joint View
|
|
142
|
+
</Button>
|
|
143
|
+
</>
|
|
144
|
+
) : (
|
|
145
|
+
<>
|
|
146
|
+
<span className="text-label-medium text-foreground">
|
|
147
|
+
{contactName} View
|
|
148
|
+
</span>
|
|
149
|
+
<Button
|
|
150
|
+
variant="outline"
|
|
151
|
+
size="sm"
|
|
152
|
+
onClick={onCreateJointView}
|
|
153
|
+
>
|
|
154
|
+
Create Joint View
|
|
155
|
+
</Button>
|
|
156
|
+
</>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<Button
|
|
161
|
+
variant="ghost"
|
|
162
|
+
size="icon-sm"
|
|
163
|
+
onClick={() => onOpenChange(false)}
|
|
164
|
+
aria-label="Close drawer"
|
|
165
|
+
>
|
|
166
|
+
<X className="h-4 w-4" />
|
|
167
|
+
</Button>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Joint sub-tabs — default (pill) variant, only in joint+joint mode */}
|
|
171
|
+
{hasJointView && viewMode === "joint" && (
|
|
172
|
+
<Tabs
|
|
173
|
+
value={jointSubTab}
|
|
174
|
+
onValueChange={(v) =>
|
|
175
|
+
onJointSubTabChange?.(v as SummaryJointSubTab)
|
|
176
|
+
}
|
|
177
|
+
>
|
|
178
|
+
<TabsList>
|
|
179
|
+
<TabsTrigger value="joint">Joint View</TabsTrigger>
|
|
180
|
+
<TabsTrigger value="userA">
|
|
181
|
+
{jointMainUserName} View
|
|
182
|
+
</TabsTrigger>
|
|
183
|
+
<TabsTrigger value="userB">
|
|
184
|
+
{jointCoApplicantName} View
|
|
185
|
+
</TabsTrigger>
|
|
186
|
+
</TabsList>
|
|
187
|
+
</Tabs>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{/* Action buttons + alerts — hidden while loading */}
|
|
192
|
+
{!isLoading && actionButtons && <div>{actionButtons}</div>}
|
|
193
|
+
{!isLoading && alerts && <div>{alerts}</div>}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* ── Scrollable content viewport ── */}
|
|
197
|
+
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
198
|
+
{isLoading ? (
|
|
199
|
+
<div className="flex h-full min-h-[200px] items-center justify-center py-12">
|
|
200
|
+
<Spinner size="xl" className="text-primary" />
|
|
201
|
+
</div>
|
|
202
|
+
) : (
|
|
203
|
+
children
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</SheetContent>
|
|
208
|
+
</Sheet>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// OpportunityDetailsDrawer
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
export type OpportunityTab =
|
|
217
|
+
| "summary"
|
|
218
|
+
| "tasks"
|
|
219
|
+
| "bankStatement"
|
|
220
|
+
| "policyAI"
|
|
221
|
+
| "runServicing"
|
|
222
|
+
| "emailAndNotes"
|
|
223
|
+
| "syncLoanApp";
|
|
224
|
+
|
|
225
|
+
const OPPORTUNITY_TABS: { value: OpportunityTab; label: string }[] = [
|
|
226
|
+
{ value: "summary", label: "Summary" },
|
|
227
|
+
{ value: "tasks", label: "Tasks" },
|
|
228
|
+
{ value: "bankStatement", label: "Reports & Statements" },
|
|
229
|
+
{ value: "policyAI", label: "Policy AI" },
|
|
230
|
+
{ value: "runServicing", label: "Run Servicing" },
|
|
231
|
+
{ value: "emailAndNotes", label: "Email & Notes" },
|
|
232
|
+
{ value: "syncLoanApp", label: "Sync Loan App" },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
export interface OpportunityDetailsDrawerProps {
|
|
236
|
+
open: boolean;
|
|
237
|
+
onOpenChange: (open: boolean) => void;
|
|
238
|
+
/** Default active tab on open. Defaults to "summary". */
|
|
239
|
+
defaultTab?: OpportunityTab;
|
|
240
|
+
/**
|
|
241
|
+
* Content slots keyed by tab value.
|
|
242
|
+
* Pass only the tabs you want to render content for — unset tabs show nothing.
|
|
243
|
+
*
|
|
244
|
+
* ```tsx
|
|
245
|
+
* <OpportunityDetailsDrawer
|
|
246
|
+
* tabs={{
|
|
247
|
+
* summary: <SummaryTabContent />,
|
|
248
|
+
* tasks: <TasksTabContent />,
|
|
249
|
+
* }}
|
|
250
|
+
* />
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
tabs?: Partial<Record<OpportunityTab, React.ReactNode>>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Right-side sheet shell for the Opportunity Details Drawer.
|
|
258
|
+
*
|
|
259
|
+
* Renders a line-variant tab bar (Summary · Tasks · Reports & Statements ·
|
|
260
|
+
* Policy AI · Run Servicing · Email & Notes · Sync Loan App) with a close
|
|
261
|
+
* button, and a scrollable `TabsContent` panel for each active tab.
|
|
262
|
+
*
|
|
263
|
+
* Uses the shadcn `Tabs` component internally so each tab's content panel
|
|
264
|
+
* is properly connected to its trigger via Base UI's Tabs.Root context.
|
|
265
|
+
*/
|
|
266
|
+
export function OpportunityDetailsDrawer({
|
|
267
|
+
open,
|
|
268
|
+
onOpenChange,
|
|
269
|
+
defaultTab = "summary",
|
|
270
|
+
tabs,
|
|
271
|
+
}: OpportunityDetailsDrawerProps) {
|
|
272
|
+
return (
|
|
273
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
274
|
+
<SheetContent
|
|
275
|
+
side="right"
|
|
276
|
+
showCloseButton={false}
|
|
277
|
+
className="w-[min(1048px,100vw)] max-w-none p-0"
|
|
278
|
+
>
|
|
279
|
+
{/*
|
|
280
|
+
* Tabs.Root wraps both the header and content so that TabsContent panels
|
|
281
|
+
* are in scope and automatically shown/hidden by Base UI's tab context.
|
|
282
|
+
* gap-0 overrides the default gap-2 on Tabs.
|
|
283
|
+
*/}
|
|
284
|
+
<Tabs defaultValue={defaultTab} className="h-full gap-0">
|
|
285
|
+
{/*
|
|
286
|
+
* Tab header: no pt-3 so the header height hugs the tab section.
|
|
287
|
+
* TabsList gets group-data-[orientation=horizontal]/tabs:h-12 to
|
|
288
|
+
* override the built-in conditional h-9 at equal specificity via
|
|
289
|
+
* cascade order. Triggers use flex-none to hug their label width
|
|
290
|
+
* (overrides the built-in flex-1 that was stretching them equally).
|
|
291
|
+
* No overflow-x-auto wrapper — that forces overflow-y:auto (CSS spec)
|
|
292
|
+
* and clips the line-variant after:bottom-[-5px] indicator.
|
|
293
|
+
*/}
|
|
294
|
+
<div className="flex shrink-0 items-end border-b border-border">
|
|
295
|
+
<TabsList
|
|
296
|
+
variant="line"
|
|
297
|
+
className="flex-1 group-data-[orientation=horizontal]/tabs:h-auto justify-start rounded-none bg-transparent p-0"
|
|
298
|
+
>
|
|
299
|
+
{OPPORTUNITY_TABS.map((tab) => (
|
|
300
|
+
<TabsTrigger
|
|
301
|
+
key={tab.value}
|
|
302
|
+
value={tab.value}
|
|
303
|
+
className="flex-none h-auto py-3 px-4 text-sm"
|
|
304
|
+
>
|
|
305
|
+
{tab.label}
|
|
306
|
+
</TabsTrigger>
|
|
307
|
+
))}
|
|
308
|
+
</TabsList>
|
|
309
|
+
|
|
310
|
+
{/* Close button */}
|
|
311
|
+
<div className="flex shrink-0 self-center items-center pr-3">
|
|
312
|
+
<Button
|
|
313
|
+
variant="ghost"
|
|
314
|
+
size="icon-sm"
|
|
315
|
+
onClick={() => onOpenChange(false)}
|
|
316
|
+
aria-label="Close drawer"
|
|
317
|
+
>
|
|
318
|
+
<X className="h-4 w-4" />
|
|
319
|
+
</Button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* ── Tab content panels ── */}
|
|
324
|
+
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
325
|
+
{OPPORTUNITY_TABS.map((tab) => (
|
|
326
|
+
<TabsContent
|
|
327
|
+
key={tab.value}
|
|
328
|
+
value={tab.value}
|
|
329
|
+
className="mt-0 flex-none p-5 outline-none"
|
|
330
|
+
>
|
|
331
|
+
{tabs?.[tab.value]}
|
|
332
|
+
</TabsContent>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
</Tabs>
|
|
336
|
+
</SheetContent>
|
|
337
|
+
</Sheet>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Financial display primitives — WealthX DS
|
|
6
|
+
*
|
|
7
|
+
* Atomic building blocks for financial data display.
|
|
8
|
+
* Used inside summary report drawers, opportunity detail panels,
|
|
9
|
+
* and any financial data card in the backoffice and frontend.
|
|
10
|
+
*
|
|
11
|
+
* Compose these upward:
|
|
12
|
+
* Atom → FinancialDetailField, FinancialLineItem, FinancialLvrBar …
|
|
13
|
+
* Card → PropertyCard, DebtCard, ApplicantInfoCard …
|
|
14
|
+
* Section → PropertyHoldingsGrid, IncomeExpensesGrid …
|
|
15
|
+
* Drawer → SummaryReportDrawer, OpportunityDetailsDrawer …
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const NO_DATA = "—";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// FinancialSectionLabel
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface FinancialSectionLabelProps {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** All-caps muted section heading. Used above major sections inside a financial drawer. */
|
|
29
|
+
export function FinancialSectionLabel({
|
|
30
|
+
children,
|
|
31
|
+
}: FinancialSectionLabelProps) {
|
|
32
|
+
return (
|
|
33
|
+
<span className="text-label-small uppercase text-muted-foreground">
|
|
34
|
+
{children}
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// FinancialCardHeader
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export interface FinancialCardHeaderProps {
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** All-caps muted label at the top of a card — lighter than SectionLabel. */
|
|
48
|
+
export function FinancialCardHeader({ children }: FinancialCardHeaderProps) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="py-2.5">
|
|
51
|
+
<span className="text-label-small uppercase text-muted-foreground">
|
|
52
|
+
{children}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// FinancialSubsectionTitle
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
export interface FinancialSubsectionTitleProps {
|
|
63
|
+
children: React.ReactNode;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Uppercase bold title inside a card section (e.g. "Card Stats", "Loan Stats"). */
|
|
67
|
+
export function FinancialSubsectionTitle({
|
|
68
|
+
children,
|
|
69
|
+
}: FinancialSubsectionTitleProps) {
|
|
70
|
+
return (
|
|
71
|
+
<span className="text-label-medium uppercase text-foreground">
|
|
72
|
+
{children}
|
|
73
|
+
</span>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// FinancialDetailField
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
export type FinancialDetailFieldVariant = "caption" | "overline" | "footer";
|
|
82
|
+
|
|
83
|
+
export interface FinancialDetailFieldProps {
|
|
84
|
+
label: string;
|
|
85
|
+
/**
|
|
86
|
+
* The value to display. Accepts any renderable node — formatted strings,
|
|
87
|
+
* badges, or `null`/`undefined` (renders `—`).
|
|
88
|
+
*/
|
|
89
|
+
value?: React.ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* Controls label typography and layout density:
|
|
92
|
+
* - `caption` (default) — small 2-line min-height label; use in dense grids
|
|
93
|
+
* - `overline` — tighter gap; use in mid-density sections
|
|
94
|
+
* - `footer` — flex-col layout; use inside `FinancialSubtotalFrame`
|
|
95
|
+
*/
|
|
96
|
+
variant?: FinancialDetailFieldVariant;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Label + value vertical stack — the core data-display cell.
|
|
101
|
+
*
|
|
102
|
+
* | Variant | Label style | Use when |
|
|
103
|
+
* |-----------|------------------------|-----------------------------------|
|
|
104
|
+
* | caption | small, 2-line min-h | Dense grids (default) |
|
|
105
|
+
* | overline | overline, tighter gap | Mid-density sections |
|
|
106
|
+
* | footer | overline + flex-col | SubtotalFrame cells |
|
|
107
|
+
*/
|
|
108
|
+
export function FinancialDetailField({
|
|
109
|
+
label,
|
|
110
|
+
value,
|
|
111
|
+
variant = "caption",
|
|
112
|
+
}: FinancialDetailFieldProps) {
|
|
113
|
+
if (variant === "footer") {
|
|
114
|
+
return (
|
|
115
|
+
<div className="flex flex-col justify-between min-w-0">
|
|
116
|
+
<span className="text-overline text-muted-foreground">{label}</span>
|
|
117
|
+
<span className="text-label-medium text-foreground mt-1 break-words">
|
|
118
|
+
{value ?? NO_DATA}
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const labelClass =
|
|
125
|
+
variant === "caption"
|
|
126
|
+
? "block min-h-[2rem] text-caption text-muted-foreground"
|
|
127
|
+
: "min-h-[1.75rem] text-overline text-muted-foreground";
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
className={cn(
|
|
132
|
+
"min-w-0",
|
|
133
|
+
variant === "overline" && "flex flex-col gap-0.5",
|
|
134
|
+
)}
|
|
135
|
+
>
|
|
136
|
+
<span className={labelClass}>{label}</span>
|
|
137
|
+
<span className="text-label-medium text-foreground break-words">
|
|
138
|
+
{value ?? NO_DATA}
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// FinancialLineItem
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
export interface FinancialLineItemProps {
|
|
149
|
+
label: string;
|
|
150
|
+
/** Formatted value string e.g. "$9,500". Renders `—` when falsy. */
|
|
151
|
+
value?: string | null;
|
|
152
|
+
/**
|
|
153
|
+
* When `true`, renders the value in `text-destructive` (red).
|
|
154
|
+
* Use for expenses, liabilities, or negative cashflow values.
|
|
155
|
+
*/
|
|
156
|
+
destructive?: boolean;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Horizontal row: label on left, value on right.
|
|
161
|
+
* Use `destructive` for expenses, liabilities, or negative cashflow values.
|
|
162
|
+
*/
|
|
163
|
+
export function FinancialLineItem({
|
|
164
|
+
label,
|
|
165
|
+
value,
|
|
166
|
+
destructive,
|
|
167
|
+
}: FinancialLineItemProps) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="flex items-center justify-between gap-4 py-0.5">
|
|
170
|
+
<span className="text-body-small text-foreground">{label}</span>
|
|
171
|
+
<span
|
|
172
|
+
className={cn(
|
|
173
|
+
"text-label-medium whitespace-nowrap text-right",
|
|
174
|
+
destructive ? "text-destructive" : "text-foreground",
|
|
175
|
+
)}
|
|
176
|
+
>
|
|
177
|
+
{value || NO_DATA}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// FinancialLvrBar
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
export interface FinancialLvrBarProps {
|
|
188
|
+
/**
|
|
189
|
+
* LVR percentage (0–100). Drives colour threshold:
|
|
190
|
+
* - `< 70` → success (low risk)
|
|
191
|
+
* - `70–79` → warning (borderline)
|
|
192
|
+
* - `≥ 80` → destructive (high risk)
|
|
193
|
+
*/
|
|
194
|
+
percent?: number;
|
|
195
|
+
/** Human-readable label rendered on the right e.g. `"56% — Good"`. */
|
|
196
|
+
label?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* LVR progress bar with label.
|
|
201
|
+
*
|
|
202
|
+
* Colour thresholds map to semantic tokens:
|
|
203
|
+
* - < 70% → success (low risk)
|
|
204
|
+
* - 70–79% → warning (borderline)
|
|
205
|
+
* - ≥ 80% → destructive (high risk)
|
|
206
|
+
*/
|
|
207
|
+
export function FinancialLvrBar({
|
|
208
|
+
percent = 56,
|
|
209
|
+
label = "56% — Good",
|
|
210
|
+
}: FinancialLvrBarProps) {
|
|
211
|
+
const color =
|
|
212
|
+
percent >= 80 ? "destructive" : percent >= 70 ? "warning" : "success";
|
|
213
|
+
|
|
214
|
+
const textClass = {
|
|
215
|
+
success: "text-success",
|
|
216
|
+
warning: "text-warning",
|
|
217
|
+
destructive: "text-destructive",
|
|
218
|
+
}[color];
|
|
219
|
+
|
|
220
|
+
const trackClass = {
|
|
221
|
+
success: "bg-success/15",
|
|
222
|
+
warning: "bg-warning/15",
|
|
223
|
+
destructive: "bg-destructive/15",
|
|
224
|
+
}[color];
|
|
225
|
+
|
|
226
|
+
const fillClass = {
|
|
227
|
+
success: "bg-success",
|
|
228
|
+
warning: "bg-warning",
|
|
229
|
+
destructive: "bg-destructive",
|
|
230
|
+
}[color];
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="flex flex-col gap-2">
|
|
234
|
+
<div className="flex items-center justify-between">
|
|
235
|
+
<span className="text-body-small text-muted-foreground">LVR</span>
|
|
236
|
+
<span className={cn("text-label-medium", textClass)}>{label}</span>
|
|
237
|
+
</div>
|
|
238
|
+
<div className={cn("h-2.5 w-full overflow-hidden", trackClass)}>
|
|
239
|
+
<div
|
|
240
|
+
className={cn("h-full", fillClass)}
|
|
241
|
+
style={{ width: `${Math.min(percent, 100)}%` }}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// FinancialSubtotalFrame
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
export interface FinancialSubtotalFrameProps {
|
|
253
|
+
children: React.ReactNode;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Brand-tinted footer container — sits at the bottom of a property or debt card.
|
|
258
|
+
* Background uses the tenant primary color at 10 % opacity.
|
|
259
|
+
*/
|
|
260
|
+
export function FinancialSubtotalFrame({
|
|
261
|
+
children,
|
|
262
|
+
}: FinancialSubtotalFrameProps) {
|
|
263
|
+
return (
|
|
264
|
+
<div className="mt-auto border-t border-primary/20 bg-primary/10 px-4 py-3">
|
|
265
|
+
{children}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// FinancialSubtotalBlock
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
export interface FinancialSubtotalBlockProps {
|
|
275
|
+
/**
|
|
276
|
+
* Monthly average value e.g. `"$2,840"`.
|
|
277
|
+
* When provided without `totalLast12Months`, renders right-aligned as a single figure.
|
|
278
|
+
*/
|
|
279
|
+
monthlyAverage?: string;
|
|
280
|
+
/** Total for the last 12 months e.g. `"$34,080"`. Only shown when provided alongside `monthlyAverage`. */
|
|
281
|
+
totalLast12Months?: string;
|
|
282
|
+
/** Override the "Monthly Average" label (e.g. `"Net Surplus"`). */
|
|
283
|
+
label?: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* One or two summary values inside a SubtotalFrame.
|
|
288
|
+
*
|
|
289
|
+
* - Two values → Monthly Average (left) + Total Last 12 Months (right)
|
|
290
|
+
* - One value → right-aligned single figure (pass only `monthlyAverage`)
|
|
291
|
+
* - Custom label → override "Monthly Average" via `label` prop
|
|
292
|
+
*/
|
|
293
|
+
export function FinancialSubtotalBlock({
|
|
294
|
+
monthlyAverage,
|
|
295
|
+
totalLast12Months,
|
|
296
|
+
label,
|
|
297
|
+
}: FinancialSubtotalBlockProps) {
|
|
298
|
+
const isSingle = monthlyAverage && !totalLast12Months;
|
|
299
|
+
return (
|
|
300
|
+
<div
|
|
301
|
+
className={cn(
|
|
302
|
+
"flex flex-row items-center",
|
|
303
|
+
isSingle ? "justify-end" : "justify-between",
|
|
304
|
+
)}
|
|
305
|
+
>
|
|
306
|
+
<div
|
|
307
|
+
className={cn(
|
|
308
|
+
"flex flex-col gap-0.5",
|
|
309
|
+
isSingle ? "text-right" : "text-left",
|
|
310
|
+
)}
|
|
311
|
+
>
|
|
312
|
+
<span className="min-h-[1.75rem] text-overline text-muted-foreground">
|
|
313
|
+
{label ?? "Monthly Average"}
|
|
314
|
+
</span>
|
|
315
|
+
<span className="text-label-medium text-foreground">
|
|
316
|
+
{monthlyAverage ?? NO_DATA}
|
|
317
|
+
</span>
|
|
318
|
+
</div>
|
|
319
|
+
{!isSingle && (
|
|
320
|
+
<div className="flex flex-col gap-0.5 text-right">
|
|
321
|
+
<span className="min-h-[1.75rem] text-overline text-muted-foreground">
|
|
322
|
+
Total Last 12 Months
|
|
323
|
+
</span>
|
|
324
|
+
<span className="text-label-medium text-foreground">
|
|
325
|
+
{totalLast12Months ?? NO_DATA}
|
|
326
|
+
</span>
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
}
|