@wealthx/shadcn 1.1.0 → 1.2.1

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 +12 -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-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  6. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  7. package/dist/chunk-3WMX6KWS.mjs +245 -0
  8. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  9. package/dist/chunk-4CX4SBRO.mjs +153 -0
  10. package/dist/chunk-4MN6UQHG.mjs +443 -0
  11. package/dist/chunk-4Y6R4WEC.mjs +250 -0
  12. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  13. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  14. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  15. package/dist/chunk-7MMXNK3C.mjs +317 -0
  16. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  17. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  18. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  19. package/dist/chunk-A56YQQHG.mjs +402 -0
  20. package/dist/{chunk-GLW2UO6O.mjs → chunk-BL3DXM2X.mjs} +84 -62
  21. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  22. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  23. package/dist/chunk-D4ILTPOG.mjs +293 -0
  24. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  25. package/dist/chunk-DOH3EHX7.mjs +378 -0
  26. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  27. package/dist/{chunk-YBXCIF5Q.mjs → chunk-ERGGHC2V.mjs} +36 -49
  28. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  29. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  30. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  31. package/dist/chunk-FZIXGLMV.mjs +173 -0
  32. package/dist/chunk-GGM2UYGG.mjs +273 -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-JNQORUPP.mjs} +49 -42
  38. package/dist/{chunk-5JGQAAQV.mjs → chunk-K3JYD4IU.mjs} +86 -63
  39. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  40. package/dist/{chunk-UEL4RD5P.mjs → chunk-LHYCMLVA.mjs} +82 -68
  41. package/dist/{chunk-NLLKTU4B.mjs → chunk-LLVQKSU3.mjs} +21 -17
  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-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  45. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  46. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  47. package/dist/chunk-OPNQAVVH.mjs +162 -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-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  53. package/dist/chunk-PR6V5XKM.mjs +209 -0
  54. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  55. package/dist/chunk-RGU7HOEC.mjs +140 -0
  56. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  57. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  58. package/dist/chunk-RYCLWMZ7.mjs +162 -0
  59. package/dist/chunk-SIZMLSRU.mjs +162 -0
  60. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  61. package/dist/chunk-SWGT756Z.mjs +210 -0
  62. package/dist/chunk-SYOD63OZ.mjs +225 -0
  63. package/dist/chunk-TS2ZX2VS.mjs +270 -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-VPBN3WOO.mjs +164 -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-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 +640 -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 +628 -0
  106. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  107. package/dist/components/ui/cashflow-bar-chart.js +124 -70
  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 +166 -67
  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 +164 -66
  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 +683 -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 +77 -39
  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 +11620 -3832
  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 +261 -0
  236. package/src/components/ui/backoffice-contact-history-chart.tsx +326 -0
  237. package/src/components/ui/badge.tsx +17 -15
  238. package/src/components/ui/borrowing-capacity-line-chart.tsx +359 -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 +304 -0
  243. package/src/components/ui/cashflow-bar-chart.tsx +106 -78
  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 +85 -66
  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 +81 -61
  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 +189 -0
  276. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +186 -0
  277. package/src/components/ui/property-mobile-estimate-line-chart.tsx +395 -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 +192 -0
  290. package/src/components/ui/transactions-income-expense-bar-chart.tsx +47 -39
  291. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +192 -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,268 @@
1
+ import * as React from "react";
2
+ import { RefreshCw, Search, X } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+ import { Input } from "@/components/ui/input";
5
+ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
6
+ import { KanbanColumn } from "@/components/ui/kanban-column";
7
+ import type {
8
+ KanbanColumnProps,
9
+ KanbanColumnStage,
10
+ } from "@/components/ui/kanban-column";
11
+ import type { OpportunityCardProps } from "@/components/ui/opportunity-card";
12
+
13
+ /**
14
+ * PipelineBoard — WealthX DS (L5 Board)
15
+ *
16
+ * Full Pipeline / Loan CRM kanban board.
17
+ * Renders a horizontal list of KanbanColumns with a toolbar (search + column filters).
18
+ *
19
+ * **Does NOT include:**
20
+ * - Drag-and-drop (react-dnd) — handled by the app's DndProvider + KanbanColumn wiring.
21
+ * - Data fetching — the app supplies `columns` with pre-loaded opportunities.
22
+ * - Modals (edit column, delete, change priority, put on hold) — opened by the app.
23
+ *
24
+ * ### Layer: L5 Board
25
+ * ```
26
+ * PipelineBoard (L5)
27
+ * └── KanbanColumn (L4) ×N
28
+ * └── OpportunityCard (L3) ×N
29
+ * └── TaskCheckItem (L2) ×N
30
+ * ```
31
+ */
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Types
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface PipelineBoardColumn {
38
+ /** Unique column key (used as React key — may differ from stage.id). */
39
+ key: string;
40
+ stage: KanbanColumnStage;
41
+ opportunities: OpportunityCardProps[];
42
+ /**
43
+ * When set, each card in this column shows a "Send Loan Application Request"
44
+ * button. Intended for the Leads stage only — cards there have no tasks yet.
45
+ */
46
+ onSendLoanApplication?: (opportunityId: string) => void;
47
+ /** DnD index (-1 for High Priority fixed column). */
48
+ columnIndex?: number;
49
+ /** Whether this column is being dragged (controlled by app DnD). */
50
+ isDragging?: boolean;
51
+ /** Whether a card is hovering over this column (controlled by app DnD). */
52
+ isDropTarget?: boolean;
53
+ /** Whether this is a fixed/system column (no Delete in menu). */
54
+ isDefault?: boolean;
55
+ isLoading?: boolean;
56
+ isLoadingMore?: boolean;
57
+ hasMore?: boolean;
58
+ loaderRef?: React.Ref<HTMLDivElement>;
59
+ }
60
+
61
+ export interface PipelineBoardProps {
62
+ // ── Data ─────────────────────────────────────────────────────
63
+ columns: PipelineBoardColumn[];
64
+
65
+ // ── Toolbar ──────────────────────────────────────────────────
66
+ /** Current keyword search value (controlled). */
67
+ searchValue?: string;
68
+ onSearchChange?: (value: string) => void;
69
+ /**
70
+ * Column filter chips. Each item is a label shown as a chip.
71
+ * Pass `"View All"` as a special value to show/select all columns.
72
+ */
73
+ filterOptions?: string[];
74
+ activeFilters?: string[];
75
+ onFilterChange?: (filter: string) => void;
76
+
77
+ // ── Column callbacks ─────────────────────────────────────────
78
+ onEditColumn?: (stageId: string) => void;
79
+ onDeleteColumn?: (stageId: string) => void;
80
+
81
+ // ── Toolbar ──────────────────────────────────────────────────
82
+ onRefresh?: () => void;
83
+
84
+ // ── Card callbacks ───────────────────────────────────────────
85
+ onTaskToggle?: (opportunityId: string, taskId: string) => void;
86
+ onMarkAsDone?: (opportunityId: string) => void;
87
+ onMoveToNextStage?: (opportunityId: string) => void;
88
+ onViewDetails?: (opportunityId: string) => void;
89
+ onChangePriority?: (opportunityId: string) => void;
90
+ onLaunchAssistant?: (opportunityId: string) => void;
91
+ onPutOnHold?: (opportunityId: string) => void;
92
+ onDeleteOpportunity?: (opportunityId: string) => void;
93
+ submittingOpportunityId?: string | null;
94
+
95
+ className?: string;
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Toolbar
100
+ // ---------------------------------------------------------------------------
101
+
102
+ interface ToolbarProps {
103
+ searchValue: string;
104
+ onSearchChange: (v: string) => void;
105
+ filterOptions: string[];
106
+ activeFilters: string[];
107
+ onFilterChange: (f: string) => void;
108
+ onRefresh?: () => void;
109
+ }
110
+
111
+ function Toolbar({
112
+ searchValue,
113
+ onSearchChange,
114
+ filterOptions,
115
+ activeFilters,
116
+ onFilterChange,
117
+ onRefresh,
118
+ }: ToolbarProps) {
119
+ return (
120
+ <div className="flex flex-wrap items-center gap-3 border-b border-border bg-background px-4 py-3">
121
+ {/* Search */}
122
+ <div className="relative w-56 shrink-0">
123
+ <Search className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
124
+ <Input
125
+ placeholder="Search opportunities…"
126
+ value={searchValue}
127
+ onChange={(e) => onSearchChange(e.target.value)}
128
+ className="h-8 pl-8 text-sm"
129
+ />
130
+ {searchValue && (
131
+ <button
132
+ onClick={() => onSearchChange("")}
133
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
134
+ aria-label="Clear search"
135
+ >
136
+ <X className="size-3.5" />
137
+ </button>
138
+ )}
139
+ </div>
140
+
141
+ {/* Column filter toggle group */}
142
+ {filterOptions.length > 0 && (
143
+ <ToggleGroup
144
+ type="multiple"
145
+ variant="outline"
146
+ size="sm"
147
+ spacing={1.5}
148
+ value={activeFilters}
149
+ onValueChange={(newValues) => {
150
+ const toggled =
151
+ newValues.find((v) => !activeFilters.includes(v)) ??
152
+ activeFilters.find((v) => !newValues.includes(v));
153
+ if (toggled) onFilterChange(toggled);
154
+ }}
155
+ >
156
+ {filterOptions.map((option) => (
157
+ <ToggleGroupItem key={option} value={option}>
158
+ {option}
159
+ </ToggleGroupItem>
160
+ ))}
161
+ </ToggleGroup>
162
+ )}
163
+
164
+ {/* Refresh */}
165
+ {onRefresh && (
166
+ <button
167
+ onClick={onRefresh}
168
+ className="ml-auto text-muted-foreground hover:text-foreground"
169
+ aria-label="Refresh board"
170
+ >
171
+ <RefreshCw className="size-4" />
172
+ </button>
173
+ )}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // PipelineBoard
180
+ // ---------------------------------------------------------------------------
181
+
182
+ export function PipelineBoard({
183
+ columns,
184
+ searchValue = "",
185
+ onSearchChange,
186
+ filterOptions = [],
187
+ activeFilters = [],
188
+ onFilterChange,
189
+ onRefresh,
190
+ onEditColumn,
191
+ onDeleteColumn,
192
+ onTaskToggle,
193
+ onMarkAsDone,
194
+ onMoveToNextStage,
195
+ onViewDetails,
196
+ onChangePriority,
197
+ onLaunchAssistant,
198
+ onPutOnHold,
199
+ onDeleteOpportunity,
200
+ submittingOpportunityId,
201
+ className,
202
+ }: PipelineBoardProps) {
203
+ const hasToolbar =
204
+ onSearchChange || (filterOptions.length > 0 && onFilterChange);
205
+
206
+ return (
207
+ <div
208
+ className={cn("flex h-full flex-col bg-muted/20", className)}
209
+ data-slot="pipeline-board"
210
+ >
211
+ {/* ── Toolbar ── */}
212
+ {hasToolbar && (
213
+ <Toolbar
214
+ searchValue={searchValue}
215
+ onSearchChange={onSearchChange ?? (() => {})}
216
+ filterOptions={filterOptions}
217
+ activeFilters={activeFilters}
218
+ onFilterChange={onFilterChange ?? (() => {})}
219
+ onRefresh={onRefresh}
220
+ />
221
+ )}
222
+
223
+ {/* ── Board scroll area ── */}
224
+ <div className="flex flex-1 gap-3 overflow-x-auto p-4">
225
+ {columns.map((col) => (
226
+ <KanbanColumn
227
+ key={col.key}
228
+ stage={col.stage}
229
+ opportunities={col.opportunities}
230
+ isDragging={col.isDragging}
231
+ isDropTarget={col.isDropTarget}
232
+ isDefault={col.isDefault}
233
+ isLoading={col.isLoading}
234
+ isLoadingMore={col.isLoadingMore}
235
+ hasMore={col.hasMore}
236
+ loaderRef={col.loaderRef}
237
+ onEditColumn={
238
+ onEditColumn ? () => onEditColumn(col.stage.id) : undefined
239
+ }
240
+ onDeleteColumn={
241
+ onDeleteColumn && !col.isDefault
242
+ ? () => onDeleteColumn(col.stage.id)
243
+ : undefined
244
+ }
245
+ onTaskToggle={onTaskToggle}
246
+ onMarkAsDone={onMarkAsDone}
247
+ onMoveToNextStage={onMoveToNextStage}
248
+ onSendLoanApplication={col.onSendLoanApplication}
249
+ onViewDetails={onViewDetails}
250
+ onChangePriority={onChangePriority}
251
+ onLaunchAssistant={onLaunchAssistant}
252
+ onPutOnHold={onPutOnHold}
253
+ onDeleteOpportunity={onDeleteOpportunity}
254
+ submittingOpportunityId={submittingOpportunityId}
255
+ />
256
+ ))}
257
+
258
+ {columns.length === 0 && (
259
+ <div className="flex flex-1 items-center justify-center">
260
+ <p className="text-sm text-muted-foreground">
261
+ No columns to display.
262
+ </p>
263
+ </div>
264
+ )}
265
+ </div>
266
+ </div>
267
+ );
268
+ }
@@ -0,0 +1,173 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+ import { useThemeVars } from "@/lib/theme-provider";
4
+
5
+ /**
6
+ * PipelineChart — WealthX DS (L4 Section)
7
+ *
8
+ * Horizontal stacked bar chart showing loan pipeline value by stage.
9
+ * Each segment is proportional to the stage's total loan value.
10
+ * Hover a segment to see the stage name and formatted value.
11
+ *
12
+ * Colors: each stage supplies its own `color` (e.g. `accentColor` from
13
+ * `KanbanColumnStage`). Falls back to the design system's primary token.
14
+ *
15
+ * Data source: `listColumns()` → `Stage[]` in `loan-crm.ts`
16
+ */
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface PipelineChartStage {
23
+ id: string;
24
+ name: string;
25
+ /** Total loan value for this stage in dollars. */
26
+ value: number;
27
+ /** CSS color string — use the stage's `accentColor`. Falls back to primary. */
28
+ color?: string;
29
+ }
30
+
31
+ export interface PipelineChartProps {
32
+ stages: PipelineChartStage[];
33
+ /** Height of the stacked bar in pixels. Default 32. */
34
+ barHeight?: number;
35
+ className?: string;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Helpers
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function formatValue(v: number): string {
43
+ return new Intl.NumberFormat("en-AU", {
44
+ style: "currency",
45
+ currency: "AUD",
46
+ notation: "compact",
47
+ maximumFractionDigits: 1,
48
+ }).format(v);
49
+ }
50
+
51
+ // Fallback palette (used when stage.color is not set) — uses stage vars so
52
+ // colors stay tenant-adaptive even when no explicit color is passed.
53
+ const FALLBACK_COLORS = [
54
+ "var(--color-stage-1)",
55
+ "var(--color-stage-2)",
56
+ "var(--color-stage-3)",
57
+ "var(--color-stage-4)",
58
+ "var(--color-stage-5)",
59
+ ];
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // PipelineChart
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export function PipelineChart({
66
+ stages,
67
+ barHeight = 32,
68
+ className,
69
+ }: PipelineChartProps) {
70
+ const themeVars = useThemeVars();
71
+ const [activeId, setActiveId] = React.useState<string | null>(null);
72
+ const [tooltipX, setTooltipX] = React.useState(0);
73
+ const barRef = React.useRef<HTMLDivElement>(null);
74
+
75
+ const nonEmpty = stages.filter((s) => s.value > 0);
76
+ const total = nonEmpty.reduce((sum, s) => sum + s.value, 0);
77
+
78
+ if (total === 0 || stages.length === 0) {
79
+ return (
80
+ <div
81
+ className={cn(
82
+ "flex items-center justify-center text-xs text-muted-foreground",
83
+ className,
84
+ )}
85
+ style={{ height: barHeight }}
86
+ data-slot="pipeline-chart"
87
+ >
88
+ No pipeline data.
89
+ </div>
90
+ );
91
+ }
92
+
93
+ const activeStage = activeId ? stages.find((s) => s.id === activeId) : null;
94
+
95
+ return (
96
+ <div
97
+ className={cn("flex flex-col gap-3", className)}
98
+ data-slot="pipeline-chart"
99
+ style={themeVars as React.CSSProperties}
100
+ >
101
+ {/* Stacked bar */}
102
+ <div
103
+ ref={barRef}
104
+ className="relative flex w-full overflow-hidden"
105
+ style={{ height: barHeight }}
106
+ >
107
+ {nonEmpty.map((stage, i) => {
108
+ const pct = (stage.value / total) * 100;
109
+ const color =
110
+ stage.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length];
111
+ const isActive = activeId === stage.id;
112
+
113
+ return (
114
+ <div
115
+ key={stage.id}
116
+ role="img"
117
+ className="relative h-full cursor-pointer transition-opacity"
118
+ style={{
119
+ width: `${pct}%`,
120
+ backgroundColor: color,
121
+ opacity: activeId && !isActive ? 0.5 : 1,
122
+ minWidth: 2,
123
+ }}
124
+ onMouseEnter={(e) => {
125
+ setActiveId(stage.id);
126
+ const bar = barRef.current;
127
+ if (!bar) return;
128
+ const rect = e.currentTarget.getBoundingClientRect();
129
+ const barRect = bar.getBoundingClientRect();
130
+ setTooltipX(rect.left + rect.width / 2 - barRect.left);
131
+ }}
132
+ onMouseLeave={() => setActiveId(null)}
133
+ aria-label={`${stage.name}: ${formatValue(stage.value)}`}
134
+ />
135
+ );
136
+ })}
137
+
138
+ {/* Tooltip */}
139
+ {activeStage && (
140
+ <div
141
+ className="pointer-events-none absolute -top-8 z-10 whitespace-nowrap border border-border bg-popover px-2 py-1 text-xs text-popover-foreground shadow-sm"
142
+ style={{ left: tooltipX, transform: "translateX(-50%)" }}
143
+ >
144
+ <span className="font-medium">{activeStage.name}</span>
145
+ {" — "}
146
+ {formatValue(activeStage.value)}
147
+ </div>
148
+ )}
149
+ </div>
150
+
151
+ {/* Legend */}
152
+ <div className="flex flex-wrap gap-x-4 gap-y-1.5">
153
+ {nonEmpty.map((stage, i) => {
154
+ const color =
155
+ stage.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length];
156
+ return (
157
+ <div
158
+ key={stage.id}
159
+ className="flex items-center gap-1.5 text-xs text-muted-foreground"
160
+ >
161
+ <span
162
+ className="inline-block size-2 shrink-0 rounded-full"
163
+ style={{ backgroundColor: color }}
164
+ aria-hidden="true"
165
+ />
166
+ {stage.name}
167
+ </div>
168
+ );
169
+ })}
170
+ </div>
171
+ </div>
172
+ );
173
+ }