@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.
Files changed (300) hide show
  1. package/.turbo/turbo-build.log +235 -138
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +82 -0
  4. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  5. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  6. package/dist/chunk-2SF672SZ.mjs +161 -0
  7. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  8. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  9. package/dist/chunk-3WMX6KWS.mjs +245 -0
  10. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  11. package/dist/chunk-4CX4SBRO.mjs +153 -0
  12. package/dist/chunk-4MN6UQHG.mjs +443 -0
  13. package/dist/chunk-5QQVZTVZ.mjs +233 -0
  14. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  15. package/dist/chunk-6FCGKSZX.mjs +268 -0
  16. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  17. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  18. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  19. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  20. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  21. package/dist/chunk-A56YQQHG.mjs +402 -0
  22. package/dist/chunk-AH52LG6N.mjs +315 -0
  23. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  24. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  25. package/dist/chunk-D4ILTPOG.mjs +293 -0
  26. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  27. package/dist/chunk-DOH3EHX7.mjs +378 -0
  28. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  29. package/dist/chunk-ERGGHC2V.mjs +185 -0
  30. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  31. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  32. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  33. package/dist/chunk-FZIXGLMV.mjs +173 -0
  34. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  35. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  36. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  37. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  38. package/dist/chunk-KMCGSZTX.mjs +177 -0
  39. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  40. package/dist/chunk-LE6YFY6D.mjs +209 -0
  41. package/dist/{chunk-HUVTPUV2.mjs → chunk-LLVQKSU3.mjs} +23 -19
  42. package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
  43. package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
  44. package/dist/chunk-NLCKVHWB.mjs +161 -0
  45. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  46. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  47. package/dist/chunk-NXA3CZ7A.mjs +248 -0
  48. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  49. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  50. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  51. package/dist/chunk-PCPLO5HT.mjs +671 -0
  52. package/dist/chunk-PG6K5XEC.mjs +475 -0
  53. package/dist/chunk-PJHPSRYD.mjs +234 -0
  54. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  55. package/dist/chunk-PR6V5XKM.mjs +209 -0
  56. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  57. package/dist/chunk-QVKWW6KE.mjs +272 -0
  58. package/dist/chunk-RGU7HOEC.mjs +140 -0
  59. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  60. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  61. package/dist/chunk-RRBS6D63.mjs +163 -0
  62. package/dist/chunk-SMQ3DG25.mjs +285 -0
  63. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  64. package/dist/chunk-SYOD63OZ.mjs +225 -0
  65. package/dist/chunk-UFYSFDER.mjs +42 -0
  66. package/dist/chunk-VACKZOMY.mjs +190 -0
  67. package/dist/chunk-VLQZANBF.mjs +42 -0
  68. package/dist/chunk-WA6O6EUR.mjs +1885 -0
  69. package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
  70. package/dist/chunk-WG6JGJXB.mjs +165 -0
  71. package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
  72. package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
  73. package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
  74. package/dist/chunk-Y6DWJSKZ.mjs +79 -0
  75. package/dist/chunk-YKPROFLB.mjs +161 -0
  76. package/dist/{chunk-K76E2TQU.mjs → chunk-ZRO5JO3H.mjs} +107 -67
  77. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  78. package/dist/components/ui/accordion.js +40 -4
  79. package/dist/components/ui/accordion.mjs +2 -2
  80. package/dist/components/ui/add-column-modal.js +789 -0
  81. package/dist/components/ui/add-column-modal.mjs +17 -0
  82. package/dist/components/ui/add-lead-modal.js +647 -0
  83. package/dist/components/ui/add-lead-modal.mjs +16 -0
  84. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  85. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  86. package/dist/components/ui/alert-dialog.js +37 -5
  87. package/dist/components/ui/alert-dialog.mjs +4 -4
  88. package/dist/components/ui/alert.js +37 -11
  89. package/dist/components/ui/alert.mjs +2 -2
  90. package/dist/components/ui/avatar.js +36 -8
  91. package/dist/components/ui/avatar.mjs +2 -2
  92. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  95. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  96. package/dist/components/ui/badge.js +37 -2
  97. package/dist/components/ui/badge.mjs +2 -2
  98. package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
  99. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  100. package/dist/components/ui/button.js +35 -3
  101. package/dist/components/ui/button.mjs +2 -2
  102. package/dist/components/ui/calendar.js +43 -19
  103. package/dist/components/ui/calendar.mjs +3 -3
  104. package/dist/components/ui/card.js +40 -4
  105. package/dist/components/ui/card.mjs +2 -2
  106. package/dist/components/ui/cash-balance-line-chart.js +627 -0
  107. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  108. package/dist/components/ui/cashflow-bar-chart.js +650 -0
  109. package/dist/components/ui/cashflow-bar-chart.mjs +16 -0
  110. package/dist/components/ui/checkbox.js +36 -5
  111. package/dist/components/ui/checkbox.mjs +2 -3
  112. package/dist/components/ui/chip.js +37 -2
  113. package/dist/components/ui/chip.mjs +3 -3
  114. package/dist/components/ui/combobox.js +280 -0
  115. package/dist/components/ui/combobox.mjs +28 -0
  116. package/dist/components/ui/data-table.js +160 -88
  117. package/dist/components/ui/data-table.mjs +10 -11
  118. package/dist/components/ui/date-picker.js +44 -20
  119. package/dist/components/ui/date-picker.mjs +6 -7
  120. package/dist/components/ui/dialog.js +44 -12
  121. package/dist/components/ui/dialog.mjs +4 -4
  122. package/dist/components/ui/drawer.js +46 -10
  123. package/dist/components/ui/drawer.mjs +3 -3
  124. package/dist/components/ui/dropdown-menu.js +40 -16
  125. package/dist/components/ui/dropdown-menu.mjs +3 -3
  126. package/dist/components/ui/empty.js +41 -5
  127. package/dist/components/ui/empty.mjs +2 -2
  128. package/dist/components/ui/expense-bar-chart.js +642 -0
  129. package/dist/components/ui/expense-bar-chart.mjs +16 -0
  130. package/dist/components/ui/field.js +53 -21
  131. package/dist/components/ui/field.mjs +4 -4
  132. package/dist/components/ui/financial-cards.js +1002 -0
  133. package/dist/components/ui/financial-cards.mjs +24 -0
  134. package/dist/components/ui/financial-drawers.js +637 -0
  135. package/dist/components/ui/financial-drawers.mjs +17 -0
  136. package/dist/components/ui/financial-primitives.js +218 -0
  137. package/dist/components/ui/financial-primitives.mjs +22 -0
  138. package/dist/components/ui/financial-sections.js +1422 -0
  139. package/dist/components/ui/financial-sections.mjs +30 -0
  140. package/dist/components/ui/form-primitives.js +682 -0
  141. package/dist/components/ui/form-primitives.mjs +19 -0
  142. package/dist/components/ui/income-bar-chart.js +641 -0
  143. package/dist/components/ui/income-bar-chart.mjs +16 -0
  144. package/dist/components/ui/input-group.js +43 -7
  145. package/dist/components/ui/input-group.mjs +5 -5
  146. package/dist/components/ui/input-otp.js +39 -3
  147. package/dist/components/ui/input-otp.mjs +2 -2
  148. package/dist/components/ui/input.js +34 -2
  149. package/dist/components/ui/input.mjs +2 -2
  150. package/dist/components/ui/kanban-column.js +1143 -0
  151. package/dist/components/ui/kanban-column.mjs +20 -0
  152. package/dist/components/ui/label.js +35 -7
  153. package/dist/components/ui/label.mjs +2 -2
  154. package/dist/components/ui/opportunity-card.js +960 -0
  155. package/dist/components/ui/opportunity-card.mjs +20 -0
  156. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  157. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  158. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  159. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  160. package/dist/components/ui/pagination.js +35 -3
  161. package/dist/components/ui/pagination.mjs +3 -3
  162. package/dist/components/ui/pipeline-alerts.js +103 -0
  163. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  164. package/dist/components/ui/pipeline-board.js +1408 -0
  165. package/dist/components/ui/pipeline-board.mjs +24 -0
  166. package/dist/components/ui/pipeline-chart.js +216 -0
  167. package/dist/components/ui/pipeline-chart.mjs +10 -0
  168. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  169. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  170. package/dist/components/ui/pipeline-primitives.js +300 -0
  171. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  172. package/dist/components/ui/popover.js +45 -4
  173. package/dist/components/ui/popover.mjs +3 -3
  174. package/dist/components/ui/progress.js +33 -1
  175. package/dist/components/ui/progress.mjs +2 -2
  176. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  177. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  179. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
  181. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  182. package/dist/components/ui/radio-group.js +33 -1
  183. package/dist/components/ui/radio-group.mjs +2 -2
  184. package/dist/components/ui/select.js +66 -26
  185. package/dist/components/ui/select.mjs +3 -3
  186. package/dist/components/ui/separator.js +33 -1
  187. package/dist/components/ui/separator.mjs +2 -2
  188. package/dist/components/ui/sheet.js +37 -9
  189. package/dist/components/ui/sheet.mjs +3 -3
  190. package/dist/components/ui/skeleton.js +33 -1
  191. package/dist/components/ui/skeleton.mjs +2 -2
  192. package/dist/components/ui/slider.js +86 -102
  193. package/dist/components/ui/slider.mjs +2 -2
  194. package/dist/components/ui/spinner.js +33 -1
  195. package/dist/components/ui/spinner.mjs +2 -2
  196. package/dist/components/ui/stage-timeline.js +579 -0
  197. package/dist/components/ui/stage-timeline.mjs +15 -0
  198. package/dist/components/ui/switch.js +37 -4
  199. package/dist/components/ui/switch.mjs +2 -3
  200. package/dist/components/ui/table.js +37 -5
  201. package/dist/components/ui/table.mjs +2 -2
  202. package/dist/components/ui/tabs.js +36 -12
  203. package/dist/components/ui/tabs.mjs +2 -2
  204. package/dist/components/ui/textarea.js +34 -2
  205. package/dist/components/ui/textarea.mjs +2 -2
  206. package/dist/components/ui/toggle-group.js +35 -4
  207. package/dist/components/ui/toggle-group.mjs +3 -4
  208. package/dist/components/ui/toggle.js +35 -4
  209. package/dist/components/ui/toggle.mjs +2 -3
  210. package/dist/components/ui/tooltip.js +51 -22
  211. package/dist/components/ui/tooltip.mjs +3 -3
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  213. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  214. package/dist/components/ui/transactions-income-expense-bar-chart.js +516 -0
  215. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +16 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  217. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  218. package/dist/index.js +11613 -2868
  219. package/dist/index.mjs +377 -164
  220. package/dist/lib/theme-provider.js +10 -1
  221. package/dist/lib/theme-provider.mjs +1 -1
  222. package/dist/lib/typography.js +8 -0
  223. package/dist/lib/typography.mjs +3 -1
  224. package/dist/lib/utils.js +33 -1
  225. package/dist/lib/utils.mjs +1 -1
  226. package/dist/styles.css +1 -1
  227. package/package.json +169 -6
  228. package/src/components/index.tsx +323 -13
  229. package/src/components/ui/accordion.tsx +6 -3
  230. package/src/components/ui/add-column-modal.tsx +339 -0
  231. package/src/components/ui/add-lead-modal.tsx +290 -0
  232. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  233. package/src/components/ui/alert-dialog.tsx +80 -54
  234. package/src/components/ui/alert.tsx +28 -28
  235. package/src/components/ui/avatar.tsx +30 -29
  236. package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
  237. package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
  238. package/src/components/ui/badge.tsx +17 -15
  239. package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
  240. package/src/components/ui/button.tsx +30 -27
  241. package/src/components/ui/calendar.tsx +53 -67
  242. package/src/components/ui/card.tsx +27 -24
  243. package/src/components/ui/cash-balance-line-chart.tsx +302 -0
  244. package/src/components/ui/cashflow-bar-chart.tsx +363 -0
  245. package/src/components/ui/chart-shared.tsx +261 -0
  246. package/src/components/ui/checkbox.tsx +30 -26
  247. package/src/components/ui/combobox.tsx +223 -0
  248. package/src/components/ui/data-table.tsx +160 -99
  249. package/src/components/ui/date-picker.tsx +0 -2
  250. package/src/components/ui/dialog.tsx +70 -60
  251. package/src/components/ui/drawer.tsx +57 -48
  252. package/src/components/ui/dropdown-menu.tsx +90 -82
  253. package/src/components/ui/empty.tsx +31 -27
  254. package/src/components/ui/expense-bar-chart.tsx +296 -0
  255. package/src/components/ui/field.tsx +70 -62
  256. package/src/components/ui/financial-cards.tsx +830 -0
  257. package/src/components/ui/financial-drawers.tsx +339 -0
  258. package/src/components/ui/financial-primitives.tsx +331 -0
  259. package/src/components/ui/financial-sections.tsx +672 -0
  260. package/src/components/ui/form-primitives.tsx +536 -0
  261. package/src/components/ui/income-bar-chart.tsx +297 -0
  262. package/src/components/ui/input-group.tsx +41 -34
  263. package/src/components/ui/input-otp.tsx +29 -24
  264. package/src/components/ui/input.tsx +8 -8
  265. package/src/components/ui/kanban-column.tsx +333 -0
  266. package/src/components/ui/label.tsx +9 -12
  267. package/src/components/ui/opportunity-card.tsx +616 -0
  268. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  269. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  270. package/src/components/ui/pipeline-alerts.tsx +74 -0
  271. package/src/components/ui/pipeline-board.tsx +268 -0
  272. package/src/components/ui/pipeline-chart.tsx +173 -0
  273. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  274. package/src/components/ui/pipeline-primitives.tsx +108 -0
  275. package/src/components/ui/popover.tsx +41 -36
  276. package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
  277. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
  278. package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
  279. package/src/components/ui/select.tsx +65 -52
  280. package/src/components/ui/sheet.tsx +55 -52
  281. package/src/components/ui/slider.tsx +54 -77
  282. package/src/components/ui/stage-timeline.tsx +205 -0
  283. package/src/components/ui/switch.tsx +42 -29
  284. package/src/components/ui/table.tsx +28 -28
  285. package/src/components/ui/tabs.tsx +22 -28
  286. package/src/components/ui/textarea.tsx +8 -8
  287. package/src/components/ui/toggle-group.tsx +0 -2
  288. package/src/components/ui/toggle.tsx +13 -15
  289. package/src/components/ui/tooltip.tsx +30 -28
  290. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
  291. package/src/components/ui/transactions-income-expense-bar-chart.tsx +205 -0
  292. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
  293. package/src/lib/theme-provider.tsx +10 -0
  294. package/src/lib/typography.ts +9 -0
  295. package/src/lib/utils.ts +41 -3
  296. package/src/styles/globals.css +371 -124
  297. package/src/styles/styles-css.ts +1 -1
  298. package/tsup.config.ts +32 -0
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,830 @@
1
+ import * as React from "react";
2
+ import {
3
+ Baby,
4
+ Building2,
5
+ Car,
6
+ ChevronDown,
7
+ CreditCard,
8
+ Dumbbell,
9
+ GraduationCap,
10
+ HeartPulse,
11
+ Landmark,
12
+ Receipt,
13
+ RefreshCw,
14
+ Shield,
15
+ ShoppingCart,
16
+ Shirt,
17
+ Tv,
18
+ UtensilsCrossed,
19
+ Zap,
20
+ } from "lucide-react";
21
+ import type { LucideIcon } from "lucide-react";
22
+ import { cn } from "@/lib/utils";
23
+ import { Badge } from "./badge";
24
+ import { RadioGroup, RadioGroupItem } from "./radio-group";
25
+ import {
26
+ FinancialDetailField,
27
+ FinancialLineItem,
28
+ FinancialLvrBar,
29
+ FinancialSubsectionTitle,
30
+ FinancialSubtotalBlock,
31
+ FinancialSubtotalFrame,
32
+ } from "./financial-primitives";
33
+
34
+ /**
35
+ * Financial card molecules — WealthX DS (Level 3)
36
+ *
37
+ * Composed from financial primitives (Level 2) + shadcn atoms.
38
+ * Used inside summary report drawers, opportunity detail panels,
39
+ * and any financial data section in the backoffice.
40
+ *
41
+ * Component inventory:
42
+ * PropertyCard — property holding with optional bank link + expandable loan detail
43
+ * DebtCard — mortgage / investment-loan breakdown
44
+ * OtherLiabilityCard — credit card or personal loan card (discriminated by `type`)
45
+ * AlertCard — single alert item with severity indicator + dismiss/snooze actions
46
+ */
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // PropertyCard
50
+ // ---------------------------------------------------------------------------
51
+
52
+ export interface PropertyCardProps {
53
+ /** Street address or property name */
54
+ address: string;
55
+ /** Badge label: "Owner Occupier", "Investment", etc. */
56
+ type?: string;
57
+ /** Estimated property value e.g. "$1,200,000" */
58
+ estimated: string;
59
+ /**
60
+ * When true removes the outer card border.
61
+ * Use inside `FinancialViewSection` where the card is already inside a bordered container.
62
+ */
63
+ borderless?: boolean;
64
+
65
+ /** Whether a bank mortgage account is linked to this property */
66
+ isLinkedToBank?: boolean;
67
+ /** Outstanding loan amount — only shown when isLinkedToBank */
68
+ loanAmount?: string;
69
+ /** Equity = estimated − loan — only shown when isLinkedToBank */
70
+ equity?: string;
71
+ /** LVR label e.g. "56% — Good" — only shown when isLinkedToBank */
72
+ lvr?: string;
73
+ /** LVR numeric percentage (drives color thresholds) */
74
+ lvrPercent?: number;
75
+
76
+ // — Expandable loan detail (visible only when isLinkedToBank && expanded) —
77
+ lenderName?: string;
78
+ interestRate?: string;
79
+ yearsRemaining?: string;
80
+ minRepayments?: string;
81
+ averageRepayments?: string;
82
+ redrawAmount?: string;
83
+ offsetAccount?: string;
84
+
85
+ // — SubtotalFrame footer —
86
+ totalMinRepayments?: string;
87
+ totalExtraRepayments?: string;
88
+ interestCharged?: string;
89
+ principlePaidOff?: string;
90
+ }
91
+
92
+ export function PropertyCard({
93
+ address,
94
+ type,
95
+ estimated,
96
+ isLinkedToBank = false,
97
+ borderless = false,
98
+ loanAmount,
99
+ equity,
100
+ lvr,
101
+ lvrPercent = 0,
102
+ lenderName,
103
+ interestRate,
104
+ yearsRemaining,
105
+ minRepayments,
106
+ averageRepayments,
107
+ redrawAmount,
108
+ offsetAccount,
109
+ totalMinRepayments,
110
+ totalExtraRepayments,
111
+ interestCharged,
112
+ principlePaidOff,
113
+ }: PropertyCardProps) {
114
+ const [expanded, setExpanded] = React.useState(false);
115
+
116
+ return (
117
+ <div
118
+ className={cn(
119
+ "overflow-hidden flex flex-col",
120
+ !borderless && "border border-border",
121
+ )}
122
+ >
123
+ {/* Header row */}
124
+ {isLinkedToBank ? (
125
+ <button
126
+ type="button"
127
+ aria-expanded={expanded}
128
+ onClick={() => setExpanded((prev) => !prev)}
129
+ className="flex items-center gap-1.5 px-5 py-3 text-left cursor-pointer hover:opacity-85"
130
+ >
131
+ <span className="text-label-medium text-foreground">{address}</span>
132
+ {type && <Badge variant="outline">{type}</Badge>}
133
+ <span className="flex-1" />
134
+ <ChevronDown
135
+ className={cn(
136
+ "h-5 w-5 shrink-0 text-muted-foreground transition-transform duration-200",
137
+ expanded && "rotate-180",
138
+ )}
139
+ />
140
+ </button>
141
+ ) : (
142
+ <div className="flex items-center gap-1.5 px-5 py-3">
143
+ <span className="text-label-medium text-foreground">{address}</span>
144
+ {type && <Badge variant="outline">{type}</Badge>}
145
+ </div>
146
+ )}
147
+
148
+ {!isLinkedToBank && (
149
+ <p className="px-5 pb-2 text-xs italic text-muted-foreground">
150
+ No mortgage account linked to this property
151
+ </p>
152
+ )}
153
+
154
+ {/* Static fields — always visible */}
155
+ <div
156
+ className={cn(
157
+ "grid gap-5 px-5 py-[15px]",
158
+ isLinkedToBank ? "grid-cols-3" : "grid-cols-1",
159
+ )}
160
+ >
161
+ <FinancialDetailField label="Estimated Value" value={estimated} />
162
+ {isLinkedToBank && (
163
+ <>
164
+ <FinancialDetailField label="Loan Amount" value={loanAmount} />
165
+ <FinancialDetailField label="Equity" value={equity} />
166
+ </>
167
+ )}
168
+ </div>
169
+
170
+ {isLinkedToBank && (
171
+ <div className="px-5 pb-[15px]">
172
+ <FinancialLvrBar
173
+ percent={lvrPercent}
174
+ label={lvr ?? `${lvrPercent}%`}
175
+ />
176
+ </div>
177
+ )}
178
+
179
+ {/* Expandable loan detail — CSS grid transition for smooth open/close */}
180
+ {isLinkedToBank && (
181
+ <div
182
+ className={cn(
183
+ "grid transition-[grid-template-rows] duration-200 ease-in-out",
184
+ expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
185
+ )}
186
+ >
187
+ <div className="overflow-hidden">
188
+ <div className="flex flex-col gap-5 border-t border-border px-5 py-[15px]">
189
+ <FinancialSubsectionTitle>Loan</FinancialSubsectionTitle>
190
+ <div className="grid grid-cols-4 gap-x-4 gap-y-4">
191
+ <FinancialDetailField
192
+ label="Name of Lender"
193
+ value={lenderName}
194
+ />
195
+ <FinancialDetailField
196
+ label="Current Loan Amount"
197
+ value={loanAmount}
198
+ />
199
+ <FinancialDetailField
200
+ label="Interest Rate"
201
+ value={interestRate}
202
+ />
203
+ <FinancialDetailField
204
+ label="Years Remaining"
205
+ value={yearsRemaining}
206
+ />
207
+ </div>
208
+ <div className="grid grid-cols-4 gap-x-4 gap-y-4">
209
+ <FinancialDetailField
210
+ label="Min Repayments"
211
+ value={minRepayments}
212
+ />
213
+ <FinancialDetailField
214
+ label="Average Repayments"
215
+ value={averageRepayments}
216
+ />
217
+ <FinancialDetailField
218
+ label="Redraw Amount"
219
+ value={redrawAmount}
220
+ />
221
+ <FinancialDetailField
222
+ label="Offset Account"
223
+ value={offsetAccount}
224
+ />
225
+ </div>
226
+ </div>
227
+ <FinancialSubtotalFrame>
228
+ <div className="grid grid-cols-4 gap-x-4 gap-y-1">
229
+ <FinancialDetailField
230
+ variant="footer"
231
+ label="Total Min Repayments"
232
+ value={totalMinRepayments}
233
+ />
234
+ <FinancialDetailField
235
+ variant="footer"
236
+ label="Total Extra Repayments"
237
+ value={totalExtraRepayments}
238
+ />
239
+ <FinancialDetailField
240
+ variant="footer"
241
+ label="Interest Charge"
242
+ value={interestCharged}
243
+ />
244
+ <FinancialDetailField
245
+ variant="footer"
246
+ label="Principle Paid Off"
247
+ value={principlePaidOff}
248
+ />
249
+ </div>
250
+ </FinancialSubtotalFrame>
251
+ </div>
252
+ </div>
253
+ )}
254
+ </div>
255
+ );
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // DebtCard
260
+ // ---------------------------------------------------------------------------
261
+
262
+ export interface DebtCardProps {
263
+ // — Header row —
264
+ lenderName?: string;
265
+ currentLoanAmount?: string;
266
+ /** Current annual interest rate e.g. `"6.04% p.a."` */
267
+ interestRate?: string;
268
+ // — Loan Stats section —
269
+ originalLoanAmount?: string;
270
+ /** Original term at drawdown e.g. `"30 years"` */
271
+ originalLoanTerm?: string;
272
+ /** Minimum scheduled monthly repayment */
273
+ monthlyRepayments?: string;
274
+ /** Available redraw balance */
275
+ redrawAmount?: string;
276
+ /** Linked offset account balance */
277
+ offsetAmount?: string;
278
+ /** Additional voluntary repayments per month */
279
+ extraLoanRepayments?: string;
280
+ // — SubtotalFrame footer —
281
+ /** Cumulative repayments made within the selected date range */
282
+ totalRepaymentsMade?: string;
283
+ /** Cumulative interest paid within the selected date range */
284
+ totalInterestPaid?: string;
285
+ /** Remaining loan term e.g. `"25 years"` */
286
+ yearsRemaining?: string;
287
+ }
288
+
289
+ export function DebtCard({
290
+ lenderName,
291
+ currentLoanAmount,
292
+ interestRate,
293
+ originalLoanAmount,
294
+ originalLoanTerm,
295
+ monthlyRepayments,
296
+ redrawAmount,
297
+ offsetAmount,
298
+ extraLoanRepayments,
299
+ totalRepaymentsMade,
300
+ totalInterestPaid,
301
+ yearsRemaining,
302
+ }: DebtCardProps) {
303
+ return (
304
+ <div className="border border-border overflow-hidden flex flex-col h-full">
305
+ {/* Header row — fixed min-h so two cards in a grid row have aligned headers */}
306
+ <div className="grid grid-cols-3 gap-5 px-5 py-[15px] border-b border-border min-h-[7rem] items-start">
307
+ <FinancialDetailField label="Name of Lender" value={lenderName} />
308
+ <FinancialDetailField
309
+ label="Current Loan Amount"
310
+ value={currentLoanAmount}
311
+ />
312
+ <FinancialDetailField
313
+ label="Current Interest Rate"
314
+ value={interestRate}
315
+ />
316
+ </div>
317
+
318
+ {/* Loan Stats section */}
319
+ <div className="px-5 py-[15px] flex flex-col gap-5 flex-1">
320
+ <FinancialSubsectionTitle>Loan Stats</FinancialSubsectionTitle>
321
+ <div className="grid grid-cols-3 gap-5">
322
+ <FinancialDetailField
323
+ label="Original Loan Amount"
324
+ value={originalLoanAmount}
325
+ />
326
+ <FinancialDetailField
327
+ label="Original Loan Term"
328
+ value={originalLoanTerm}
329
+ />
330
+ <FinancialDetailField
331
+ label="Monthly Repayments"
332
+ value={monthlyRepayments}
333
+ />
334
+ </div>
335
+ <div className="grid grid-cols-3 gap-5">
336
+ <FinancialDetailField label="Redraw Amount" value={redrawAmount} />
337
+ <FinancialDetailField label="Offset Account" value={offsetAmount} />
338
+ <FinancialDetailField
339
+ label="Extra Loan Repayments"
340
+ value={extraLoanRepayments}
341
+ />
342
+ </div>
343
+ </div>
344
+
345
+ {/* Footer */}
346
+ <FinancialSubtotalFrame>
347
+ <div className="grid grid-cols-3 gap-x-4 gap-y-1">
348
+ <FinancialDetailField
349
+ variant="footer"
350
+ label="Total Repayments Made"
351
+ value={totalRepaymentsMade}
352
+ />
353
+ <FinancialDetailField
354
+ variant="footer"
355
+ label="Total Interest Paid"
356
+ value={totalInterestPaid}
357
+ />
358
+ <FinancialDetailField
359
+ variant="footer"
360
+ label="Years Remaining"
361
+ value={yearsRemaining}
362
+ />
363
+ </div>
364
+ </FinancialSubtotalFrame>
365
+ </div>
366
+ );
367
+ }
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // OtherLiabilityCard
371
+ // ---------------------------------------------------------------------------
372
+
373
+ export type OtherLiabilityType = "credit_card" | "personal_loan";
374
+
375
+ export interface OtherLiabilityCardProps {
376
+ /**
377
+ * Discriminates the field set and footer layout:
378
+ * - `"credit_card"` → Card Stats (Credit Limit, Min Payment, Annual Fee)
379
+ * - `"personal_loan"` → Loan Stats (Original Amount, Term, Monthly Repayments)
380
+ */
381
+ type: OtherLiabilityType;
382
+ lenderName?: string;
383
+ /** Annual interest rate e.g. `"19.99% p.a."` */
384
+ interestRate?: string;
385
+ // ── Credit card fields ──
386
+ /** Current outstanding balance on the card */
387
+ currentBalance?: string;
388
+ /** Approved credit limit */
389
+ creditLimit?: string;
390
+ /** Minimum required monthly payment */
391
+ minMonthlyPayment?: string;
392
+ annualFee?: string;
393
+ /** Pre-computed available credit: `creditLimit − currentBalance` */
394
+ availableCredit?: string;
395
+ // ── Personal loan fields ──
396
+ /** Current outstanding loan balance */
397
+ currentLoanAmount?: string;
398
+ originalLoanAmount?: string;
399
+ /** Original term at drawdown e.g. `"5 years"` */
400
+ originalLoanTerm?: string;
401
+ /** Minimum scheduled monthly repayment */
402
+ monthlyRepayments?: string;
403
+ /** Remaining term e.g. `"2.5 years"` */
404
+ currentLoanTermLeft?: string;
405
+ // ── Shared footer ──
406
+ /** Cumulative repayments made within the selected date range */
407
+ totalRepaymentsMade?: string;
408
+ /** Cumulative interest paid within the selected date range */
409
+ totalInterestPaid?: string;
410
+ }
411
+
412
+ export function OtherLiabilityCard({
413
+ type,
414
+ lenderName,
415
+ interestRate,
416
+ currentBalance,
417
+ creditLimit,
418
+ minMonthlyPayment,
419
+ annualFee,
420
+ availableCredit,
421
+ currentLoanAmount,
422
+ originalLoanAmount,
423
+ originalLoanTerm,
424
+ monthlyRepayments,
425
+ currentLoanTermLeft,
426
+ totalRepaymentsMade,
427
+ totalInterestPaid,
428
+ }: OtherLiabilityCardProps) {
429
+ const isCreditCard = type === "credit_card";
430
+
431
+ return (
432
+ <div className="border border-border overflow-hidden flex flex-col h-full">
433
+ {/* Header row — fixed min-h so two cards in a grid row have aligned headers */}
434
+ <div className="grid grid-cols-3 gap-5 px-4 pt-5 pb-4 min-h-[7rem] items-start">
435
+ <FinancialDetailField label="Lender" value={lenderName} />
436
+ {isCreditCard ? (
437
+ <FinancialDetailField
438
+ label="Current Balance"
439
+ value={currentBalance}
440
+ />
441
+ ) : (
442
+ <FinancialDetailField
443
+ label="Current Loan Amount"
444
+ value={currentLoanAmount}
445
+ />
446
+ )}
447
+ <FinancialDetailField label="Interest Rate" value={interestRate} />
448
+ </div>
449
+
450
+ {/* Stats section */}
451
+ <div className="px-4 pt-4 pb-5 flex flex-col gap-5 border-t border-border flex-1">
452
+ <FinancialSubsectionTitle>
453
+ {isCreditCard ? "Card Stats" : "Loan Stats"}
454
+ </FinancialSubsectionTitle>
455
+ {isCreditCard ? (
456
+ <div className="grid grid-cols-3 gap-5">
457
+ <FinancialDetailField label="Credit Limit" value={creditLimit} />
458
+ <FinancialDetailField
459
+ label="Min Monthly Payment"
460
+ value={minMonthlyPayment}
461
+ />
462
+ <FinancialDetailField label="Annual Fee" value={annualFee} />
463
+ </div>
464
+ ) : (
465
+ <div className="grid grid-cols-3 gap-5">
466
+ <FinancialDetailField
467
+ label="Original Loan Amount"
468
+ value={originalLoanAmount}
469
+ />
470
+ <FinancialDetailField
471
+ label="Original Loan Term"
472
+ value={originalLoanTerm}
473
+ />
474
+ <FinancialDetailField
475
+ label="Monthly Repayments"
476
+ value={monthlyRepayments}
477
+ />
478
+ </div>
479
+ )}
480
+ </div>
481
+
482
+ {/* Footer */}
483
+ <FinancialSubtotalFrame>
484
+ {isCreditCard ? (
485
+ <div className="grid grid-cols-2 gap-x-4 gap-y-1">
486
+ <FinancialDetailField
487
+ variant="footer"
488
+ label="Total Interest Paid"
489
+ value={totalInterestPaid}
490
+ />
491
+ <FinancialDetailField
492
+ variant="footer"
493
+ label="Available Credit"
494
+ value={availableCredit}
495
+ />
496
+ </div>
497
+ ) : (
498
+ <div className="grid grid-cols-3 gap-x-4 gap-y-1">
499
+ <FinancialDetailField
500
+ variant="footer"
501
+ label="Total Repayments Made"
502
+ value={totalRepaymentsMade}
503
+ />
504
+ <FinancialDetailField
505
+ variant="footer"
506
+ label="Total Interest Paid"
507
+ value={totalInterestPaid}
508
+ />
509
+ <FinancialDetailField
510
+ variant="footer"
511
+ label="Years Remaining"
512
+ value={currentLoanTermLeft}
513
+ />
514
+ </div>
515
+ )}
516
+ </FinancialSubtotalFrame>
517
+ </div>
518
+ );
519
+ }
520
+
521
+ // ---------------------------------------------------------------------------
522
+ // AlertCard
523
+ // ---------------------------------------------------------------------------
524
+
525
+ export type AlertSeverity = "NEED_ACTION" | "WATCH" | "INSIGHT";
526
+ export type AlertActionType = "DISMISS" | "SNOOZE";
527
+
528
+ const SEVERITY_CLASSES: Record<AlertSeverity, { dot: string; border: string }> =
529
+ {
530
+ NEED_ACTION: { dot: "bg-destructive", border: "border-destructive" },
531
+ WATCH: { dot: "bg-warning", border: "border-warning" },
532
+ INSIGHT: { dot: "bg-success", border: "border-success" },
533
+ };
534
+
535
+ export interface AlertCardProps {
536
+ id: string;
537
+ name: string;
538
+ severityCode: AlertSeverity;
539
+ /** Whether this alert has an active ignore period */
540
+ ignored?: boolean;
541
+ /** Human-readable date string e.g. "31/12/2025" */
542
+ ignoredUntil?: string;
543
+ /** Currently selected action, controlled externally */
544
+ selectedAction?: AlertActionType | null;
545
+ onActionChange?: (action: AlertActionType) => void;
546
+ }
547
+
548
+ export function AlertCard({
549
+ id,
550
+ name,
551
+ severityCode,
552
+ ignored = false,
553
+ ignoredUntil,
554
+ selectedAction = null,
555
+ onActionChange,
556
+ }: AlertCardProps) {
557
+ const { dot, border } = SEVERITY_CLASSES[severityCode];
558
+
559
+ return (
560
+ <div className={cn("flex flex-col gap-1.5 border p-2.5 px-3", border)}>
561
+ {/* Alert name row */}
562
+ <div className="flex items-center gap-1.5">
563
+ <span className={cn("h-[9px] w-[9px] shrink-0 rounded-full", dot)} />
564
+ <span
565
+ className={cn(
566
+ "text-sm font-medium leading-tight",
567
+ ignored ? "text-muted-foreground line-through" : "text-foreground",
568
+ )}
569
+ >
570
+ {name}
571
+ </span>
572
+ </div>
573
+
574
+ {ignored ? (
575
+ <span className="ml-[15px] text-xs italic text-muted-foreground/60">
576
+ Ignored until {ignoredUntil}
577
+ </span>
578
+ ) : (
579
+ <RadioGroup
580
+ value={selectedAction ?? ""}
581
+ onValueChange={(val) => onActionChange?.(val as AlertActionType)}
582
+ className="ml-[15px] flex flex-col gap-1"
583
+ >
584
+ <div className="flex items-center gap-2">
585
+ <RadioGroupItem value="DISMISS" id={`dismiss-${id}`} />
586
+ <label
587
+ htmlFor={`dismiss-${id}`}
588
+ className="cursor-pointer select-none text-sm leading-none text-muted-foreground"
589
+ >
590
+ Mark as done (will reset in 6 months)
591
+ </label>
592
+ </div>
593
+ <div className="flex items-center gap-2">
594
+ <RadioGroupItem value="SNOOZE" id={`snooze-${id}`} />
595
+ <label
596
+ htmlFor={`snooze-${id}`}
597
+ className="cursor-pointer select-none text-sm leading-none text-muted-foreground"
598
+ >
599
+ Snooze for 30 days
600
+ </label>
601
+ </div>
602
+ </RadioGroup>
603
+ )}
604
+ </div>
605
+ );
606
+ }
607
+
608
+ // ---------------------------------------------------------------------------
609
+ // AboutCard
610
+ // ---------------------------------------------------------------------------
611
+
612
+ export interface AboutCardProps {
613
+ title?: string;
614
+ firstName?: string;
615
+ lastName?: string;
616
+ phone?: string;
617
+ email?: string;
618
+ gender?: string;
619
+ maritalStatus?: string;
620
+ citizenStatus?: string;
621
+ propertyInTrust?: string;
622
+ companyOwnership?: string;
623
+ }
624
+
625
+ /**
626
+ * Display card for applicant personal / contact information.
627
+ * Used inside the Summary tab applicant sub-tabs.
628
+ */
629
+ export function AboutCard({
630
+ title,
631
+ firstName,
632
+ lastName,
633
+ phone,
634
+ email,
635
+ gender,
636
+ maritalStatus,
637
+ citizenStatus,
638
+ propertyInTrust,
639
+ companyOwnership,
640
+ }: AboutCardProps) {
641
+ const fullName =
642
+ [title, firstName, lastName].filter(Boolean).join(" ") || "—";
643
+
644
+ return (
645
+ <div className="border border-border overflow-hidden">
646
+ <div className="grid grid-cols-2 gap-x-8 gap-y-4 px-5 py-4">
647
+ <FinancialDetailField label="Full Name" value={fullName} />
648
+ <FinancialDetailField label="Gender" value={gender || "—"} />
649
+ <FinancialDetailField label="Phone" value={phone || "—"} />
650
+ <FinancialDetailField label="Email" value={email || "—"} />
651
+ <FinancialDetailField
652
+ label="Marital Status"
653
+ value={maritalStatus || "—"}
654
+ />
655
+ <FinancialDetailField
656
+ label="Citizenship"
657
+ value={citizenStatus || "—"}
658
+ />
659
+ <FinancialDetailField
660
+ label="Property in Trust"
661
+ value={propertyInTrust || "—"}
662
+ />
663
+ <FinancialDetailField
664
+ label="Company Ownership"
665
+ value={companyOwnership || "—"}
666
+ />
667
+ </div>
668
+ </div>
669
+ );
670
+ }
671
+
672
+ // ---------------------------------------------------------------------------
673
+ // IncomeCard
674
+ // ---------------------------------------------------------------------------
675
+
676
+ export interface IncomeCardItem {
677
+ incomeType: string;
678
+ jobTitle?: string;
679
+ companyName?: string;
680
+ /** Pre-formatted amount + frequency e.g. "$9,500 / Monthly" */
681
+ amountLabel: string;
682
+ }
683
+
684
+ export interface IncomeCardProps {
685
+ items: IncomeCardItem[];
686
+ /** Pre-formatted total monthly income e.g. "$12,300" */
687
+ totalMonthly?: string;
688
+ }
689
+
690
+ /**
691
+ * Display card for applicant income items.
692
+ * Each income source renders as a bordered row with type, company, and amount.
693
+ */
694
+ export function IncomeCard({ items, totalMonthly }: IncomeCardProps) {
695
+ if (items.length === 0) {
696
+ return (
697
+ <div className="border border-border px-5 py-4">
698
+ <p className="text-sm text-muted-foreground">No income recorded.</p>
699
+ </div>
700
+ );
701
+ }
702
+
703
+ return (
704
+ <div className="border border-border overflow-hidden flex flex-col">
705
+ {items.map((item, i) => (
706
+ <div
707
+ key={i}
708
+ className={cn(
709
+ "grid grid-cols-3 gap-x-6 px-5 py-[15px]",
710
+ i < items.length - 1 && "border-b border-border",
711
+ )}
712
+ >
713
+ <FinancialDetailField label="Type" value={item.incomeType || "—"} />
714
+ <FinancialDetailField
715
+ label="Employer"
716
+ value={item.companyName || item.jobTitle || "—"}
717
+ />
718
+ <FinancialDetailField label="Amount" value={item.amountLabel} />
719
+ </div>
720
+ ))}
721
+ {totalMonthly && (
722
+ <FinancialSubtotalFrame>
723
+ <FinancialSubtotalBlock
724
+ monthlyAverage={totalMonthly}
725
+ label="Total Monthly Income"
726
+ />
727
+ </FinancialSubtotalFrame>
728
+ )}
729
+ </div>
730
+ );
731
+ }
732
+
733
+ // ---------------------------------------------------------------------------
734
+ // ExpensesCard
735
+ // ---------------------------------------------------------------------------
736
+
737
+ const EXPENSE_ICON_MAP: Record<string, LucideIcon> = {
738
+ groceries: ShoppingCart,
739
+ "dining out": UtensilsCrossed,
740
+ dining: UtensilsCrossed,
741
+ restaurants: UtensilsCrossed,
742
+ transport: Car,
743
+ transportation: Car,
744
+ vehicle: Car,
745
+ utilities: Zap,
746
+ electricity: Zap,
747
+ insurance: Shield,
748
+ "council rates": Landmark,
749
+ council: Landmark,
750
+ rates: Landmark,
751
+ medical: HeartPulse,
752
+ health: HeartPulse,
753
+ subscriptions: RefreshCw,
754
+ subscription: RefreshCw,
755
+ "credit card": CreditCard,
756
+ education: GraduationCap,
757
+ childcare: Baby,
758
+ entertainment: Tv,
759
+ gym: Dumbbell,
760
+ fitness: Dumbbell,
761
+ clothing: Shirt,
762
+ rent: Building2,
763
+ };
764
+
765
+ function getExpenseIcon(expenseType: string): LucideIcon {
766
+ return EXPENSE_ICON_MAP[expenseType.toLowerCase()] ?? Receipt;
767
+ }
768
+
769
+ export interface ExpensesCardItem {
770
+ expenseType: string;
771
+ /** Pre-formatted amount + frequency e.g. "$800 / Monthly" */
772
+ amountLabel: string;
773
+ }
774
+
775
+ export interface ExpensesCardProps {
776
+ items: ExpensesCardItem[];
777
+ /** Pre-formatted total monthly expenses e.g. "$2,100" */
778
+ totalMonthly?: string;
779
+ }
780
+
781
+ /**
782
+ * Display card for applicant expense items.
783
+ * Each expense renders as an icon-labelled row with a destructive amount value.
784
+ */
785
+ export function ExpensesCard({ items, totalMonthly }: ExpensesCardProps) {
786
+ if (items.length === 0) {
787
+ return (
788
+ <div className="border border-border px-5 py-4">
789
+ <p className="text-sm text-muted-foreground">No expenses recorded.</p>
790
+ </div>
791
+ );
792
+ }
793
+
794
+ return (
795
+ <div className="border border-border overflow-hidden flex flex-col">
796
+ <div className="flex flex-col">
797
+ {items.map((item, i) => {
798
+ const Icon = getExpenseIcon(item.expenseType);
799
+ return (
800
+ <div
801
+ key={item.expenseType}
802
+ className={cn(
803
+ "flex items-center justify-between gap-4 px-5 py-2.5",
804
+ i < items.length - 1 && "border-b border-border",
805
+ )}
806
+ >
807
+ <div className="flex items-center gap-2.5">
808
+ <Icon className="size-3.5 shrink-0 text-muted-foreground" />
809
+ <span className="text-body-small text-foreground">
810
+ {item.expenseType}
811
+ </span>
812
+ </div>
813
+ <span className="text-label-medium whitespace-nowrap text-destructive">
814
+ {item.amountLabel}
815
+ </span>
816
+ </div>
817
+ );
818
+ })}
819
+ </div>
820
+ {totalMonthly && (
821
+ <FinancialSubtotalFrame>
822
+ <FinancialSubtotalBlock
823
+ monthlyAverage={totalMonthly}
824
+ label="Total Monthly Expenses"
825
+ />
826
+ </FinancialSubtotalFrame>
827
+ )}
828
+ </div>
829
+ );
830
+ }