@wealthx/shadcn 1.1.0 → 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 -154
  2. package/CHANGELOG.md +6 -0
  3. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  4. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  5. package/dist/chunk-2SF672SZ.mjs +161 -0
  6. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  7. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  8. package/dist/chunk-3WMX6KWS.mjs +245 -0
  9. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  10. package/dist/chunk-4CX4SBRO.mjs +153 -0
  11. package/dist/chunk-4MN6UQHG.mjs +443 -0
  12. package/dist/{chunk-GLW2UO6O.mjs → chunk-5QQVZTVZ.mjs} +82 -61
  13. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  14. package/dist/chunk-6FCGKSZX.mjs +268 -0
  15. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  16. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  17. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  18. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  19. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  20. package/dist/chunk-A56YQQHG.mjs +402 -0
  21. package/dist/chunk-AH52LG6N.mjs +315 -0
  22. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  23. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  24. package/dist/chunk-D4ILTPOG.mjs +293 -0
  25. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  26. package/dist/chunk-DOH3EHX7.mjs +378 -0
  27. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  28. package/dist/{chunk-YBXCIF5Q.mjs → chunk-ERGGHC2V.mjs} +36 -49
  29. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  30. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  31. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  32. package/dist/chunk-FZIXGLMV.mjs +173 -0
  33. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  34. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  35. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  36. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  37. package/dist/{chunk-RN67642N.mjs → chunk-KMCGSZTX.mjs} +47 -41
  38. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  39. package/dist/chunk-LE6YFY6D.mjs +209 -0
  40. package/dist/{chunk-NLLKTU4B.mjs → chunk-LLVQKSU3.mjs} +21 -17
  41. package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
  42. package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
  43. package/dist/chunk-NLCKVHWB.mjs +161 -0
  44. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  45. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  46. package/dist/chunk-NXA3CZ7A.mjs +248 -0
  47. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  48. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  49. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  50. package/dist/chunk-PCPLO5HT.mjs +671 -0
  51. package/dist/chunk-PG6K5XEC.mjs +475 -0
  52. package/dist/{chunk-5JGQAAQV.mjs → chunk-PJHPSRYD.mjs} +84 -62
  53. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  54. package/dist/chunk-PR6V5XKM.mjs +209 -0
  55. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  56. package/dist/chunk-QVKWW6KE.mjs +272 -0
  57. package/dist/chunk-RGU7HOEC.mjs +140 -0
  58. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  59. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  60. package/dist/chunk-RRBS6D63.mjs +163 -0
  61. package/dist/{chunk-UEL4RD5P.mjs → chunk-SMQ3DG25.mjs} +80 -67
  62. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  63. package/dist/chunk-SYOD63OZ.mjs +225 -0
  64. package/dist/chunk-UFYSFDER.mjs +42 -0
  65. package/dist/chunk-VACKZOMY.mjs +190 -0
  66. package/dist/chunk-VLQZANBF.mjs +42 -0
  67. package/dist/chunk-WA6O6EUR.mjs +1885 -0
  68. package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
  69. package/dist/chunk-WG6JGJXB.mjs +165 -0
  70. package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
  71. package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
  72. package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
  73. package/dist/chunk-Y6DWJSKZ.mjs +79 -0
  74. package/dist/chunk-YKPROFLB.mjs +161 -0
  75. package/dist/{chunk-CJ46PDXE.mjs → chunk-ZRO5JO3H.mjs} +106 -66
  76. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  77. package/dist/components/ui/accordion.js +40 -4
  78. package/dist/components/ui/accordion.mjs +2 -2
  79. package/dist/components/ui/add-column-modal.js +789 -0
  80. package/dist/components/ui/add-column-modal.mjs +17 -0
  81. package/dist/components/ui/add-lead-modal.js +647 -0
  82. package/dist/components/ui/add-lead-modal.mjs +16 -0
  83. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  84. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  85. package/dist/components/ui/alert-dialog.js +37 -5
  86. package/dist/components/ui/alert-dialog.mjs +4 -4
  87. package/dist/components/ui/alert.js +37 -11
  88. package/dist/components/ui/alert.mjs +2 -2
  89. package/dist/components/ui/avatar.js +36 -8
  90. package/dist/components/ui/avatar.mjs +2 -2
  91. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  92. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  93. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  95. package/dist/components/ui/badge.js +37 -2
  96. package/dist/components/ui/badge.mjs +2 -2
  97. package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
  98. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  99. package/dist/components/ui/button.js +35 -3
  100. package/dist/components/ui/button.mjs +2 -2
  101. package/dist/components/ui/calendar.js +43 -19
  102. package/dist/components/ui/calendar.mjs +3 -3
  103. package/dist/components/ui/card.js +40 -4
  104. package/dist/components/ui/card.mjs +2 -2
  105. package/dist/components/ui/cash-balance-line-chart.js +627 -0
  106. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  107. package/dist/components/ui/cashflow-bar-chart.js +123 -69
  108. package/dist/components/ui/cashflow-bar-chart.mjs +8 -8
  109. package/dist/components/ui/checkbox.js +36 -5
  110. package/dist/components/ui/checkbox.mjs +2 -3
  111. package/dist/components/ui/chip.js +37 -2
  112. package/dist/components/ui/chip.mjs +3 -3
  113. package/dist/components/ui/combobox.js +68 -49
  114. package/dist/components/ui/combobox.mjs +2 -2
  115. package/dist/components/ui/data-table.js +160 -88
  116. package/dist/components/ui/data-table.mjs +10 -11
  117. package/dist/components/ui/date-picker.js +44 -20
  118. package/dist/components/ui/date-picker.mjs +6 -7
  119. package/dist/components/ui/dialog.js +44 -12
  120. package/dist/components/ui/dialog.mjs +4 -4
  121. package/dist/components/ui/drawer.js +46 -10
  122. package/dist/components/ui/drawer.mjs +3 -3
  123. package/dist/components/ui/dropdown-menu.js +40 -16
  124. package/dist/components/ui/dropdown-menu.mjs +3 -3
  125. package/dist/components/ui/empty.js +41 -5
  126. package/dist/components/ui/empty.mjs +2 -2
  127. package/dist/components/ui/expense-bar-chart.js +165 -66
  128. package/dist/components/ui/expense-bar-chart.mjs +8 -8
  129. package/dist/components/ui/field.js +53 -21
  130. package/dist/components/ui/field.mjs +4 -4
  131. package/dist/components/ui/financial-cards.js +1002 -0
  132. package/dist/components/ui/financial-cards.mjs +24 -0
  133. package/dist/components/ui/financial-drawers.js +637 -0
  134. package/dist/components/ui/financial-drawers.mjs +17 -0
  135. package/dist/components/ui/financial-primitives.js +218 -0
  136. package/dist/components/ui/financial-primitives.mjs +22 -0
  137. package/dist/components/ui/financial-sections.js +1422 -0
  138. package/dist/components/ui/financial-sections.mjs +30 -0
  139. package/dist/components/ui/form-primitives.js +682 -0
  140. package/dist/components/ui/form-primitives.mjs +19 -0
  141. package/dist/components/ui/income-bar-chart.js +163 -65
  142. package/dist/components/ui/income-bar-chart.mjs +8 -8
  143. package/dist/components/ui/input-group.js +43 -7
  144. package/dist/components/ui/input-group.mjs +5 -5
  145. package/dist/components/ui/input-otp.js +39 -3
  146. package/dist/components/ui/input-otp.mjs +2 -2
  147. package/dist/components/ui/input.js +34 -2
  148. package/dist/components/ui/input.mjs +2 -2
  149. package/dist/components/ui/kanban-column.js +1143 -0
  150. package/dist/components/ui/kanban-column.mjs +20 -0
  151. package/dist/components/ui/label.js +35 -7
  152. package/dist/components/ui/label.mjs +2 -2
  153. package/dist/components/ui/opportunity-card.js +960 -0
  154. package/dist/components/ui/opportunity-card.mjs +20 -0
  155. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  156. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  157. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  158. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  159. package/dist/components/ui/pagination.js +35 -3
  160. package/dist/components/ui/pagination.mjs +3 -3
  161. package/dist/components/ui/pipeline-alerts.js +103 -0
  162. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  163. package/dist/components/ui/pipeline-board.js +1408 -0
  164. package/dist/components/ui/pipeline-board.mjs +24 -0
  165. package/dist/components/ui/pipeline-chart.js +216 -0
  166. package/dist/components/ui/pipeline-chart.mjs +10 -0
  167. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  168. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  169. package/dist/components/ui/pipeline-primitives.js +300 -0
  170. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  171. package/dist/components/ui/popover.js +45 -4
  172. package/dist/components/ui/popover.mjs +3 -3
  173. package/dist/components/ui/progress.js +33 -1
  174. package/dist/components/ui/progress.mjs +2 -2
  175. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  176. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  177. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  179. package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  181. package/dist/components/ui/radio-group.js +33 -1
  182. package/dist/components/ui/radio-group.mjs +2 -2
  183. package/dist/components/ui/select.js +66 -26
  184. package/dist/components/ui/select.mjs +3 -3
  185. package/dist/components/ui/separator.js +33 -1
  186. package/dist/components/ui/separator.mjs +2 -2
  187. package/dist/components/ui/sheet.js +37 -9
  188. package/dist/components/ui/sheet.mjs +3 -3
  189. package/dist/components/ui/skeleton.js +33 -1
  190. package/dist/components/ui/skeleton.mjs +2 -2
  191. package/dist/components/ui/slider.js +86 -102
  192. package/dist/components/ui/slider.mjs +2 -2
  193. package/dist/components/ui/spinner.js +33 -1
  194. package/dist/components/ui/spinner.mjs +2 -2
  195. package/dist/components/ui/stage-timeline.js +579 -0
  196. package/dist/components/ui/stage-timeline.mjs +15 -0
  197. package/dist/components/ui/switch.js +37 -4
  198. package/dist/components/ui/switch.mjs +2 -3
  199. package/dist/components/ui/table.js +37 -5
  200. package/dist/components/ui/table.mjs +2 -2
  201. package/dist/components/ui/tabs.js +36 -12
  202. package/dist/components/ui/tabs.mjs +2 -2
  203. package/dist/components/ui/textarea.js +34 -2
  204. package/dist/components/ui/textarea.mjs +2 -2
  205. package/dist/components/ui/toggle-group.js +35 -4
  206. package/dist/components/ui/toggle-group.mjs +3 -4
  207. package/dist/components/ui/toggle.js +35 -4
  208. package/dist/components/ui/toggle.mjs +2 -3
  209. package/dist/components/ui/tooltip.js +51 -22
  210. package/dist/components/ui/tooltip.mjs +3 -3
  211. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  213. package/dist/components/ui/transactions-income-expense-bar-chart.js +76 -38
  214. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +8 -8
  215. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  217. package/dist/index.js +11616 -3831
  218. package/dist/index.mjs +333 -161
  219. package/dist/lib/theme-provider.js +10 -1
  220. package/dist/lib/theme-provider.mjs +1 -1
  221. package/dist/lib/typography.js +8 -0
  222. package/dist/lib/typography.mjs +3 -1
  223. package/dist/lib/utils.js +33 -1
  224. package/dist/lib/utils.mjs +1 -1
  225. package/dist/styles.css +1 -1
  226. package/package.json +140 -5
  227. package/src/components/index.tsx +296 -42
  228. package/src/components/ui/accordion.tsx +6 -3
  229. package/src/components/ui/add-column-modal.tsx +339 -0
  230. package/src/components/ui/add-lead-modal.tsx +290 -0
  231. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  232. package/src/components/ui/alert-dialog.tsx +80 -54
  233. package/src/components/ui/alert.tsx +28 -28
  234. package/src/components/ui/avatar.tsx +30 -29
  235. package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
  236. package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
  237. package/src/components/ui/badge.tsx +17 -15
  238. package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
  239. package/src/components/ui/button.tsx +30 -27
  240. package/src/components/ui/calendar.tsx +53 -67
  241. package/src/components/ui/card.tsx +27 -24
  242. package/src/components/ui/cash-balance-line-chart.tsx +302 -0
  243. package/src/components/ui/cashflow-bar-chart.tsx +104 -77
  244. package/src/components/ui/chart-shared.tsx +176 -15
  245. package/src/components/ui/checkbox.tsx +30 -26
  246. package/src/components/ui/combobox.tsx +78 -72
  247. package/src/components/ui/data-table.tsx +160 -99
  248. package/src/components/ui/date-picker.tsx +0 -2
  249. package/src/components/ui/dialog.tsx +70 -60
  250. package/src/components/ui/drawer.tsx +57 -48
  251. package/src/components/ui/dropdown-menu.tsx +90 -82
  252. package/src/components/ui/empty.tsx +31 -27
  253. package/src/components/ui/expense-bar-chart.tsx +83 -65
  254. package/src/components/ui/field.tsx +70 -62
  255. package/src/components/ui/financial-cards.tsx +830 -0
  256. package/src/components/ui/financial-drawers.tsx +339 -0
  257. package/src/components/ui/financial-primitives.tsx +331 -0
  258. package/src/components/ui/financial-sections.tsx +672 -0
  259. package/src/components/ui/form-primitives.tsx +536 -0
  260. package/src/components/ui/income-bar-chart.tsx +79 -60
  261. package/src/components/ui/input-group.tsx +41 -34
  262. package/src/components/ui/input-otp.tsx +29 -24
  263. package/src/components/ui/input.tsx +8 -8
  264. package/src/components/ui/kanban-column.tsx +333 -0
  265. package/src/components/ui/label.tsx +9 -12
  266. package/src/components/ui/opportunity-card.tsx +616 -0
  267. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  268. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  269. package/src/components/ui/pipeline-alerts.tsx +74 -0
  270. package/src/components/ui/pipeline-board.tsx +268 -0
  271. package/src/components/ui/pipeline-chart.tsx +173 -0
  272. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  273. package/src/components/ui/pipeline-primitives.tsx +108 -0
  274. package/src/components/ui/popover.tsx +41 -36
  275. package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
  276. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
  277. package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
  278. package/src/components/ui/select.tsx +65 -52
  279. package/src/components/ui/sheet.tsx +55 -52
  280. package/src/components/ui/slider.tsx +54 -77
  281. package/src/components/ui/stage-timeline.tsx +205 -0
  282. package/src/components/ui/switch.tsx +42 -29
  283. package/src/components/ui/table.tsx +28 -28
  284. package/src/components/ui/tabs.tsx +22 -28
  285. package/src/components/ui/textarea.tsx +8 -8
  286. package/src/components/ui/toggle-group.tsx +0 -2
  287. package/src/components/ui/toggle.tsx +13 -15
  288. package/src/components/ui/tooltip.tsx +30 -28
  289. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
  290. package/src/components/ui/transactions-income-expense-bar-chart.tsx +45 -38
  291. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
  292. package/src/lib/theme-provider.tsx +10 -0
  293. package/src/lib/typography.ts +9 -0
  294. package/src/lib/utils.ts +41 -3
  295. package/src/styles/globals.css +371 -124
  296. package/src/styles/styles-css.ts +1 -1
  297. package/tsup.config.ts +27 -0
  298. package/dist/chunk-3EQP72AW.mjs +0 -58
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,616 @@
1
+ import {
2
+ Phone,
3
+ Mail,
4
+ User,
5
+ Users,
6
+ Calendar,
7
+ MoreVertical,
8
+ Clock,
9
+ ArrowRight,
10
+ Bot,
11
+ } from "lucide-react";
12
+ import { cn } from "@/lib/utils";
13
+ import { Badge } from "@/components/ui/badge";
14
+ import { Button, buttonVariants } from "@/components/ui/button";
15
+ import { Separator } from "@/components/ui/separator";
16
+ import {
17
+ DropdownMenu,
18
+ DropdownMenuContent,
19
+ DropdownMenuItem,
20
+ DropdownMenuSeparator,
21
+ DropdownMenuTrigger,
22
+ } from "@/components/ui/dropdown-menu";
23
+ import {
24
+ Accordion,
25
+ AccordionContent,
26
+ AccordionItem,
27
+ AccordionTrigger,
28
+ } from "@/components/ui/accordion";
29
+ import { TaskCheckItem } from "@/components/ui/pipeline-primitives";
30
+
31
+ /**
32
+ * OpportunityCard — WealthX DS (L3 Card)
33
+ *
34
+ * Kanban card for a single loan opportunity in the Pipeline board.
35
+ * Renders customer info, loan metadata, task progress, and quick actions.
36
+ *
37
+ * This component is display-only — drag-and-drop is handled by the
38
+ * KanbanColumn wrapper in the app.
39
+ *
40
+ * Data source: `listLoans()` → `Opportunity` in `loan-crm.ts`
41
+ */
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Types
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export type Priority = "HIGH" | "MEDIUM" | "LOW" | "NONE";
48
+
49
+ export interface OpportunityTask {
50
+ id: string;
51
+ title: string;
52
+ completed: boolean;
53
+ aiAgentId?: string | null;
54
+ aiAgentName?: string | null;
55
+ }
56
+
57
+ export interface OpportunityCardProps {
58
+ // ── Core data ────────────────────────────────────────────────
59
+ id: string;
60
+ customerName: string;
61
+ customerPhone?: string;
62
+ customerEmail?: string;
63
+ /** Number of additional co-applicants beyond the primary contact. */
64
+ additionalContacts?: number;
65
+ loanType?: string;
66
+ loanPurposeLabel?: string;
67
+ /** Loan amount in dollars. */
68
+ amount: number;
69
+ /** ISO date string of the opportunity creation date. */
70
+ date: string;
71
+
72
+ // ── Status ───────────────────────────────────────────────────
73
+ priority: Priority;
74
+ /** Days the opportunity has been in its current stage. */
75
+ daysSinceColumnChanged?: number;
76
+ /**
77
+ * Stage threshold for MEDIUM (orange) warning color.
78
+ * Comes from `Stage.warningDays`.
79
+ */
80
+ warningDays?: number;
81
+ /**
82
+ * Stage threshold for HIGH (red) priority color.
83
+ * Comes from `Stage.priorityDays`.
84
+ */
85
+ priorityDays?: number;
86
+ /** ISO date string — if set, the card shows an "On Hold" banner. */
87
+ onHoldTo?: string | null;
88
+ /** Whether this opportunity represents a modification of a completed loan. */
89
+ isModifyCompletedLoan?: boolean;
90
+
91
+ // ── Tasks ────────────────────────────────────────────────────
92
+ tasks?: OpportunityTask[];
93
+ /** Title of the next pending task (shown as a hint below the task list). */
94
+ nextTask?: string | null;
95
+
96
+ // ── Lead data (passed through by KanbanColumn to LeadCard) ───
97
+ /**
98
+ * URL for the loan application form.
99
+ * Not rendered by OpportunityCard — used by KanbanColumn to forward
100
+ * to LeadCard when this opportunity is in the Leads stage.
101
+ */
102
+ loanApplicationUrl?: string;
103
+
104
+ // ── Actions ──────────────────────────────────────────────────
105
+ onViewDetails?: () => void;
106
+ onTaskToggle?: (taskId: string) => void;
107
+ onMarkAsDone?: () => void;
108
+ onMoveToNextStage?: () => void;
109
+ onLaunchAssistant?: () => void;
110
+ onChangePriority?: () => void;
111
+ onDelete?: () => void;
112
+ onPutOnHold?: () => void;
113
+
114
+ /** Shows a loading state on action buttons while an async op is in flight. */
115
+ isSubmitting?: boolean;
116
+ className?: string;
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Internal helpers
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /** Used for the priority dot background — full-saturation token is fine on colored bg. */
124
+ const PRIORITY_COLORS: Record<Priority, string> = {
125
+ HIGH: "var(--color-destructive)",
126
+ MEDIUM: "var(--color-warning)",
127
+ LOW: "var(--color-success)",
128
+ NONE: "var(--color-muted-foreground)",
129
+ };
130
+
131
+ /** Used for text / icons on white/light backgrounds — darkened tokens ensure 4.5:1 contrast. */
132
+ const PRIORITY_TEXT_COLORS: Record<Priority, string> = {
133
+ HIGH: "var(--color-destructive-text)",
134
+ MEDIUM: "var(--color-warning-text)",
135
+ LOW: "var(--color-success-text)",
136
+ NONE: "var(--color-muted-foreground)",
137
+ };
138
+
139
+ function resolvePriority(
140
+ days: number | undefined,
141
+ warningDays: number | undefined,
142
+ priorityDays: number | undefined,
143
+ priority: Priority,
144
+ ): Priority {
145
+ if (days === undefined) return priority;
146
+ if (priorityDays !== undefined && days >= priorityDays) return "HIGH";
147
+ if (warningDays !== undefined && days >= warningDays) return "MEDIUM";
148
+ if (warningDays !== undefined || priorityDays !== undefined) return "LOW";
149
+ return priority;
150
+ }
151
+
152
+ function formatAmount(amount: number): string {
153
+ return new Intl.NumberFormat("en-AU", {
154
+ style: "currency",
155
+ currency: "AUD",
156
+ maximumFractionDigits: 0,
157
+ }).format(amount);
158
+ }
159
+
160
+ function formatDate(iso: string): string {
161
+ try {
162
+ return new Date(iso).toLocaleDateString("en-AU", {
163
+ day: "2-digit",
164
+ month: "short",
165
+ year: "numeric",
166
+ });
167
+ } catch {
168
+ return iso;
169
+ }
170
+ }
171
+
172
+ function formatHoldDate(iso: string): string {
173
+ try {
174
+ return new Date(iso).toLocaleDateString("en-AU", {
175
+ day: "2-digit",
176
+ month: "short",
177
+ });
178
+ } catch {
179
+ return iso;
180
+ }
181
+ }
182
+
183
+ function formatLoanType(type: string): string {
184
+ return type
185
+ .split("-")
186
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
187
+ .join(" ");
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // OpportunityCard
192
+ // ---------------------------------------------------------------------------
193
+
194
+ export function OpportunityCard({
195
+ customerName,
196
+ customerPhone,
197
+ customerEmail,
198
+ additionalContacts,
199
+ loanType,
200
+ loanPurposeLabel,
201
+ amount,
202
+ date,
203
+ priority,
204
+ daysSinceColumnChanged,
205
+ warningDays,
206
+ priorityDays,
207
+ onHoldTo,
208
+ isModifyCompletedLoan,
209
+ tasks = [],
210
+ nextTask,
211
+ onViewDetails,
212
+ onTaskToggle,
213
+ onMarkAsDone,
214
+ onMoveToNextStage,
215
+ onLaunchAssistant,
216
+ onChangePriority,
217
+ onDelete,
218
+ onPutOnHold,
219
+ isSubmitting = false,
220
+ className,
221
+ }: OpportunityCardProps) {
222
+ const resolvedPriority = resolvePriority(
223
+ daysSinceColumnChanged,
224
+ warningDays,
225
+ priorityDays,
226
+ priority,
227
+ );
228
+ const priorityColor = PRIORITY_COLORS[resolvedPriority];
229
+ const priorityTextColor = PRIORITY_TEXT_COLORS[resolvedPriority];
230
+
231
+ const completedCount = tasks.filter((t) => t.completed).length;
232
+ const hasTasks = tasks.length > 0;
233
+ const hasActions = onMarkAsDone || onMoveToNextStage;
234
+ const hasMenu = onViewDetails || onChangePriority || onPutOnHold || onDelete;
235
+
236
+ return (
237
+ <div
238
+ className={cn(
239
+ "flex flex-col gap-2 border border-border bg-background p-4 text-foreground shadow-sm",
240
+ isSubmitting && "opacity-60",
241
+ className,
242
+ )}
243
+ data-slot="opportunity-card"
244
+ >
245
+ {/* ── On-hold banner ── */}
246
+ {onHoldTo && (
247
+ <div
248
+ className="flex items-center gap-1.5 rounded border border-warning/30 bg-warning/10 px-2 py-1 text-xs font-medium"
249
+ style={{ color: "var(--color-warning-text)" }}
250
+ >
251
+ <Clock className="size-3 shrink-0" />
252
+ On hold until {formatHoldDate(onHoldTo)}
253
+ </div>
254
+ )}
255
+
256
+ {/* ── Modify-completed-loan tag ── */}
257
+ {isModifyCompletedLoan && (
258
+ <div
259
+ className="flex items-center gap-1.5 rounded border border-info/30 bg-info/10 px-2 py-1 text-xs font-medium"
260
+ style={{ color: "var(--color-info-text)" }}
261
+ >
262
+ Modify completed loan
263
+ </div>
264
+ )}
265
+
266
+ {/* ── Header: loan purpose + amount + menu ── */}
267
+ <div className="flex items-start justify-between gap-2">
268
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
269
+ {(loanPurposeLabel || loanType) && (
270
+ <Badge variant="outline" className="self-start">
271
+ {loanPurposeLabel ?? formatLoanType(loanType!)}
272
+ </Badge>
273
+ )}
274
+ <span className="text-base font-bold tabular-nums text-foreground">
275
+ {formatAmount(amount)}
276
+ </span>
277
+ </div>
278
+
279
+ <div className="flex items-center gap-1 -mr-1 -mt-1">
280
+ {onLaunchAssistant && (
281
+ <button
282
+ type="button"
283
+ className={cn(
284
+ buttonVariants({ variant: "ghost", size: "icon" }),
285
+ "size-7 shrink-0",
286
+ )}
287
+ onClick={onLaunchAssistant}
288
+ aria-label="Launch AI Assistant"
289
+ title="Launch AI Assistant"
290
+ >
291
+ <Bot className="size-4" />
292
+ </button>
293
+ )}
294
+ {hasMenu && (
295
+ <DropdownMenu>
296
+ <DropdownMenuTrigger
297
+ className={cn(
298
+ buttonVariants({ variant: "ghost", size: "icon" }),
299
+ "size-7 shrink-0",
300
+ )}
301
+ aria-label="Opportunity actions"
302
+ >
303
+ <MoreVertical className="size-4" />
304
+ </DropdownMenuTrigger>
305
+ <DropdownMenuContent align="end">
306
+ {onViewDetails && (
307
+ <DropdownMenuItem onClick={onViewDetails}>
308
+ View details
309
+ </DropdownMenuItem>
310
+ )}
311
+ {onChangePriority && (
312
+ <DropdownMenuItem onClick={onChangePriority}>
313
+ Change priority
314
+ </DropdownMenuItem>
315
+ )}
316
+ {onPutOnHold && (
317
+ <DropdownMenuItem onClick={onPutOnHold}>
318
+ Put on hold
319
+ </DropdownMenuItem>
320
+ )}
321
+ {onDelete && (
322
+ <>
323
+ <DropdownMenuSeparator />
324
+ <DropdownMenuItem onClick={onDelete} variant="destructive">
325
+ Delete
326
+ </DropdownMenuItem>
327
+ </>
328
+ )}
329
+ </DropdownMenuContent>
330
+ </DropdownMenu>
331
+ )}
332
+ </div>
333
+ </div>
334
+
335
+ <Separator />
336
+
337
+ {/* ── Customer info ── */}
338
+ <div className="flex flex-col gap-1">
339
+ <div className="flex items-center justify-between gap-2">
340
+ <span className="truncate text-sm font-semibold text-foreground">
341
+ {customerName}
342
+ </span>
343
+ {additionalContacts && additionalContacts > 0 ? (
344
+ <Badge variant="secondary" className="shrink-0 gap-1">
345
+ <Users className="size-3" aria-hidden="true" />
346
+ Joint
347
+ </Badge>
348
+ ) : (
349
+ <Badge variant="secondary" className="shrink-0 gap-1">
350
+ <User className="size-3" aria-hidden="true" />
351
+ Individual
352
+ </Badge>
353
+ )}
354
+ </div>
355
+ {customerPhone && (
356
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
357
+ <Phone className="size-3 shrink-0" aria-hidden="true" />
358
+ {customerPhone}
359
+ </span>
360
+ )}
361
+ {customerEmail && (
362
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
363
+ <Mail className="size-3 shrink-0" aria-hidden="true" />
364
+ <span className="truncate">{customerEmail}</span>
365
+ </span>
366
+ )}
367
+ </div>
368
+
369
+ <Separator />
370
+
371
+ {/* ── Metadata: date (left) | days-since chip + priority dot (right, adjacent) ── */}
372
+ <div className="flex items-center justify-between">
373
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
374
+ <Calendar className="size-3 shrink-0" aria-hidden="true" />
375
+ {formatDate(date)}
376
+ </span>
377
+
378
+ <span className="flex items-center gap-1.5">
379
+ {daysSinceColumnChanged !== undefined && (
380
+ <>
381
+ <Clock
382
+ className="size-3 shrink-0"
383
+ style={{ color: priorityTextColor }}
384
+ aria-hidden="true"
385
+ />
386
+ <span
387
+ className="text-xs font-medium tabular-nums"
388
+ style={{ color: priorityTextColor }}
389
+ >
390
+ {daysSinceColumnChanged}d
391
+ </span>
392
+ </>
393
+ )}
394
+ {/* Dot: full-saturation color on its own background — no contrast issue */}
395
+ <span
396
+ role="img"
397
+ className="inline-block size-2 shrink-0 rounded-full"
398
+ style={{ backgroundColor: priorityColor }}
399
+ aria-label={`Priority: ${resolvedPriority}`}
400
+ />
401
+ </span>
402
+ </div>
403
+
404
+ {/* ── Tasks: segmented progress bar + animated accordion ── */}
405
+ {hasTasks && (
406
+ <>
407
+ {/* Segmented bar — one segment per task, filled = completed */}
408
+ <div
409
+ className="flex gap-0.5"
410
+ role="progressbar"
411
+ aria-valuenow={completedCount}
412
+ aria-valuemin={0}
413
+ aria-valuemax={tasks.length}
414
+ aria-label={`${completedCount} of ${tasks.length} tasks complete`}
415
+ >
416
+ {tasks.map((t, i) => (
417
+ <div
418
+ key={t.id}
419
+ className={cn(
420
+ "h-[7px] flex-1",
421
+ i < completedCount ? "bg-primary" : "bg-muted",
422
+ )}
423
+ />
424
+ ))}
425
+ </div>
426
+
427
+ {/* Accordion — animated expand/collapse using shadcn Accordion */}
428
+ <Accordion type="single" collapsible className="-mx-4">
429
+ <AccordionItem value="tasks" className="border-0">
430
+ <AccordionTrigger className="px-4 py-1.5 text-xs font-normal text-muted-foreground hover:no-underline hover:text-foreground [&>svg]:size-3.5">
431
+ Tasks ({completedCount}/{tasks.length})
432
+ </AccordionTrigger>
433
+ <AccordionContent className="px-4">
434
+ <div className="flex flex-col">
435
+ {tasks.map((task) => (
436
+ <TaskCheckItem
437
+ key={task.id}
438
+ title={task.title}
439
+ completed={task.completed}
440
+ aiAgentName={task.aiAgentName}
441
+ onToggle={
442
+ onTaskToggle ? () => onTaskToggle(task.id) : undefined
443
+ }
444
+ disabled={isSubmitting}
445
+ />
446
+ ))}
447
+ </div>
448
+ </AccordionContent>
449
+ </AccordionItem>
450
+ </Accordion>
451
+
452
+ {/* Next task hint — only visible when accordion is collapsed */}
453
+ {nextTask && (
454
+ <div className="flex items-start gap-1.5 border border-primary/30 bg-primary/5 px-2 py-1.5 text-xs">
455
+ <ArrowRight
456
+ className="mt-0.5 size-3 shrink-0 text-primary"
457
+ aria-hidden="true"
458
+ />
459
+ <span className="font-medium">Next:</span>
460
+ <span className="text-muted-foreground">{nextTask}</span>
461
+ </div>
462
+ )}
463
+ </>
464
+ )}
465
+
466
+ {/* ── Action buttons ── */}
467
+ {hasActions && (
468
+ <>
469
+ <Separator />
470
+ {(onMoveToNextStage || onMarkAsDone) && (
471
+ <div className="flex gap-2">
472
+ {onMoveToNextStage && (
473
+ <Button
474
+ variant="outline"
475
+ size="sm"
476
+ className="flex-1 text-caption"
477
+ onClick={onMoveToNextStage}
478
+ disabled={isSubmitting}
479
+ >
480
+ Move to next stage
481
+ </Button>
482
+ )}
483
+ {onMarkAsDone && (
484
+ <Button
485
+ variant="default"
486
+ size="sm"
487
+ className="flex-1 text-caption"
488
+ onClick={onMarkAsDone}
489
+ disabled={isSubmitting}
490
+ >
491
+ Mark as done
492
+ </Button>
493
+ )}
494
+ </div>
495
+ )}
496
+ </>
497
+ )}
498
+ </div>
499
+ );
500
+ }
501
+
502
+ // ---------------------------------------------------------------------------
503
+ // LeadCard — simplified card for the Leads stage
504
+ //
505
+ // Leads are new contacts who haven't started their loan application yet.
506
+ // No loan amount, no purpose tag, no joint badge, no tasks, no date.
507
+ // Primary action: send them the loan application link.
508
+ // ---------------------------------------------------------------------------
509
+
510
+ export interface LeadCardProps {
511
+ id: string;
512
+ customerName: string;
513
+ customerPhone?: string;
514
+ customerEmail?: string;
515
+ onSendLoanApplication?: () => void;
516
+ loanApplicationUrl?: string;
517
+ onDelete?: () => void;
518
+ isSubmitting?: boolean;
519
+ className?: string;
520
+ }
521
+
522
+ export function LeadCard({
523
+ customerName,
524
+ customerPhone,
525
+ customerEmail,
526
+ onSendLoanApplication,
527
+ loanApplicationUrl,
528
+ onDelete,
529
+ isSubmitting = false,
530
+ className,
531
+ }: LeadCardProps) {
532
+ return (
533
+ <div
534
+ className={cn(
535
+ "flex flex-col gap-3 border border-border bg-background p-4 text-foreground shadow-sm",
536
+ isSubmitting && "opacity-60",
537
+ className,
538
+ )}
539
+ data-slot="lead-card"
540
+ >
541
+ {/* ── Customer info + delete menu ── */}
542
+ <div className="flex items-start justify-between gap-2">
543
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
544
+ <span className="text-sm font-semibold text-foreground">
545
+ {customerName}
546
+ </span>
547
+ {customerPhone && (
548
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
549
+ <Phone className="size-3 shrink-0" aria-hidden="true" />
550
+ {customerPhone}
551
+ </span>
552
+ )}
553
+ {customerEmail && (
554
+ <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
555
+ <Mail className="size-3 shrink-0" aria-hidden="true" />
556
+ <span className="truncate">{customerEmail}</span>
557
+ </span>
558
+ )}
559
+ </div>
560
+
561
+ {onDelete && (
562
+ <DropdownMenu>
563
+ <DropdownMenuTrigger
564
+ className={cn(
565
+ buttonVariants({ variant: "ghost", size: "icon" }),
566
+ "-mr-1 -mt-1 size-7 shrink-0",
567
+ )}
568
+ aria-label="Lead actions"
569
+ >
570
+ <MoreVertical className="size-4" />
571
+ </DropdownMenuTrigger>
572
+ <DropdownMenuContent align="end">
573
+ <DropdownMenuItem
574
+ onClick={onDelete}
575
+ className="text-destructive focus:text-destructive"
576
+ >
577
+ Delete
578
+ </DropdownMenuItem>
579
+ </DropdownMenuContent>
580
+ </DropdownMenu>
581
+ )}
582
+ </div>
583
+
584
+ <Separator />
585
+
586
+ {/* ── Send loan application action ── */}
587
+ {onSendLoanApplication && (
588
+ <div className="flex flex-col gap-2">
589
+ <Button
590
+ variant="outline"
591
+ size="sm"
592
+ className="w-full text-caption"
593
+ onClick={onSendLoanApplication}
594
+ disabled={isSubmitting}
595
+ >
596
+ Send Loan Application Request
597
+ </Button>
598
+ {loanApplicationUrl && (
599
+ <p className="text-xs text-muted-foreground">
600
+ Or the link below to fill out the loan application directly.
601
+ <br />
602
+ <a
603
+ href={`https://${loanApplicationUrl.replace(/^https?:\/\//, "")}`}
604
+ target="_blank"
605
+ rel="noreferrer"
606
+ className="text-primary underline-offset-2 hover:underline"
607
+ >
608
+ {loanApplicationUrl}
609
+ </a>
610
+ </p>
611
+ )}
612
+ </div>
613
+ )}
614
+ </div>
615
+ );
616
+ }