@wealthx/shadcn 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +227 -191
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-2UM72RJ7.mjs → chunk-2D3HQPFN.mjs} +12 -10
- package/dist/chunk-2EM2FRU6.mjs +613 -0
- package/dist/{chunk-FH6QVUVZ.mjs → chunk-2GIYVERS.mjs} +2 -2
- package/dist/chunk-2P7HP7LR.mjs +68 -0
- package/dist/{chunk-HISNT2MG.mjs → chunk-37AE3OM5.mjs} +5 -5
- package/dist/{chunk-HBZLGDIN.mjs → chunk-3ERBUVHC.mjs} +169 -110
- package/dist/{chunk-C7CQJNMR.mjs → chunk-3VDET466.mjs} +2 -2
- package/dist/{chunk-462HMNO4.mjs → chunk-4MM7LHM5.mjs} +2 -2
- package/dist/{chunk-QMY3AZJH.mjs → chunk-4Z66LMIQ.mjs} +2 -2
- package/dist/{chunk-U5X52X37.mjs → chunk-57ZXILTS.mjs} +6 -6
- package/dist/{chunk-2A5RRQGG.mjs → chunk-5NF6T2RS.mjs} +11 -20
- package/dist/{chunk-3OYFOX3X.mjs → chunk-5VOTTIXF.mjs} +2 -2
- package/dist/{chunk-LBMRIB3G.mjs → chunk-6AJUS7VX.mjs} +1 -1
- package/dist/{chunk-OODBHKG7.mjs → chunk-6HIOM2HL.mjs} +7 -4
- package/dist/{chunk-BDYZCBRT.mjs → chunk-6QAFGZC2.mjs} +2 -2
- package/dist/{chunk-U4NDAF2P.mjs → chunk-6TX73WG7.mjs} +1 -1
- package/dist/{chunk-GD4BJDJR.mjs → chunk-7BTFGCFC.mjs} +4 -4
- package/dist/{chunk-FAKPBKLT.mjs → chunk-7GWRPXHD.mjs} +4 -4
- package/dist/{chunk-NMOI6CQD.mjs → chunk-7YI3HEBH.mjs} +5 -5
- package/dist/{chunk-T4BJLT57.mjs → chunk-AE7MASLF.mjs} +5 -5
- package/dist/{chunk-VLQZANBF.mjs → chunk-AFML43VJ.mjs} +6 -1
- package/dist/chunk-BBXSNDS3.mjs +260 -0
- package/dist/chunk-BOW7U26Y.mjs +203 -0
- package/dist/{chunk-34NWQURD.mjs → chunk-BS75ICOO.mjs} +2 -2
- package/dist/chunk-D2NSIIXG.mjs +394 -0
- package/dist/{chunk-3GF7OVTP.mjs → chunk-DGNHGNYH.mjs} +2 -2
- package/dist/{chunk-VLARHE5V.mjs → chunk-DMXYRCHM.mjs} +6 -6
- package/dist/{chunk-OGOYQ7BG.mjs → chunk-DQB4EPIS.mjs} +1 -1
- package/dist/{chunk-MIZQHHUO.mjs → chunk-FL6DZFJK.mjs} +106 -38
- package/dist/{chunk-I3RZS7V2.mjs → chunk-FLL633WS.mjs} +19 -33
- package/dist/{chunk-PBL4OQV2.mjs → chunk-FTPBQVQ6.mjs} +4 -4
- package/dist/chunk-FYPSTTEJ.mjs +169 -0
- package/dist/{chunk-6O6KD7CE.mjs → chunk-G27TSQLQ.mjs} +6 -6
- package/dist/{chunk-66MI7Q4B.mjs → chunk-GT3RU6GA.mjs} +2 -2
- package/dist/{chunk-D6ID6M4V.mjs → chunk-GTAVSBDO.mjs} +2 -2
- package/dist/{chunk-24FUO7TD.mjs → chunk-H6NQTIF4.mjs} +2 -2
- package/dist/{chunk-7DHU4VGG.mjs → chunk-HK4HUQTV.mjs} +2 -2
- package/dist/chunk-I4KVSZCH.mjs +101 -0
- package/dist/{chunk-RGVKLTLH.mjs → chunk-IKXYTCSB.mjs} +2 -2
- package/dist/{chunk-Y6DWJSKZ.mjs → chunk-ISUA7DSB.mjs} +1 -1
- package/dist/{chunk-J5UICVJS.mjs → chunk-JPGL36WQ.mjs} +2 -2
- package/dist/{chunk-7XJHLGUV.mjs → chunk-JTK6VJXY.mjs} +2 -2
- package/dist/{chunk-7YAU5CY6.mjs → chunk-JVMXMFBB.mjs} +2 -2
- package/dist/{chunk-IAE3F7DR.mjs → chunk-JZY6TNIS.mjs} +21 -21
- package/dist/{chunk-K5A5L6T2.mjs → chunk-K4KOD3KR.mjs} +12 -12
- package/dist/{chunk-MBON7YRJ.mjs → chunk-K5QV4TT6.mjs} +3 -3
- package/dist/{chunk-IHMFS7NZ.mjs → chunk-K5VHK7CM.mjs} +21 -21
- package/dist/{chunk-RJI6GKVF.mjs → chunk-KCWNDYPZ.mjs} +5 -5
- package/dist/{chunk-UFYSFDER.mjs → chunk-KFH36NKF.mjs} +1 -1
- package/dist/{chunk-EBXQWIYG.mjs → chunk-KLTACJ2G.mjs} +5 -5
- package/dist/{chunk-3TTACBDP.mjs → chunk-KWD6GANL.mjs} +4 -4
- package/dist/{chunk-IOJRDS6V.mjs → chunk-L4NSRQ3T.mjs} +218 -147
- package/dist/{chunk-GYMYRIZP.mjs → chunk-LBTHZSBT.mjs} +2 -2
- package/dist/{chunk-AMQZRHEZ.mjs → chunk-LQULK2E3.mjs} +5 -5
- package/dist/{chunk-YJG55G2H.mjs → chunk-LR6LHDP3.mjs} +5 -5
- package/dist/{chunk-7PV3IWCN.mjs → chunk-M4VYX2PV.mjs} +19 -1
- package/dist/{chunk-P76HMUI6.mjs → chunk-MDUKXXIL.mjs} +2 -2
- package/dist/{chunk-LV35NGVG.mjs → chunk-N6Q5IPKT.mjs} +9 -9
- package/dist/{chunk-DOEO3CDL.mjs → chunk-NB3ZL36B.mjs} +1 -1
- package/dist/{chunk-XREGSKX3.mjs → chunk-NOOEKOWY.mjs} +5 -5
- package/dist/{chunk-NL3ZO62D.mjs → chunk-NT4FX27K.mjs} +1 -1
- package/dist/{chunk-QZ4RE6NA.mjs → chunk-NTYQWVLI.mjs} +6 -6
- package/dist/{chunk-ERGGHC2V.mjs → chunk-OEOOYMC2.mjs} +2 -2
- package/dist/{chunk-4GAWMKMI.mjs → chunk-OIKBW2QD.mjs} +291 -54
- package/dist/{chunk-DUJTAXMH.mjs → chunk-OKTJFDPN.mjs} +6 -6
- package/dist/chunk-OLKMCXAR.mjs +1219 -0
- package/dist/{chunk-EI5F6FMT.mjs → chunk-OWFQSXVD.mjs} +3 -3
- package/dist/{chunk-6DZEXFNB.mjs → chunk-P2N2PEFY.mjs} +3 -3
- package/dist/{chunk-NSLMILBT.mjs → chunk-P7CEBZM6.mjs} +2 -2
- package/dist/{chunk-7S5AESZO.mjs → chunk-PNRUH7JY.mjs} +6 -6
- package/dist/{chunk-ZU4NV6RG.mjs → chunk-PNSYFE3K.mjs} +2 -2
- package/dist/{chunk-JKGDCQTZ.mjs → chunk-QTRSCVQ3.mjs} +5 -5
- package/dist/{chunk-ABFDMHOR.mjs → chunk-QX7IFQSF.mjs} +5 -5
- package/dist/{chunk-CFMQP5QS.mjs → chunk-QXKGOMUX.mjs} +6 -6
- package/dist/{chunk-NQPOYKAQ.mjs → chunk-R2ON6CAN.mjs} +2 -2
- package/dist/{chunk-DBHJ5KC3.mjs → chunk-R4HCRDU5.mjs} +1 -1
- package/dist/{chunk-EWRB4PAD.mjs → chunk-RCAOCHWA.mjs} +14 -14
- package/dist/{chunk-EFRENWEJ.mjs → chunk-RSUIPKGX.mjs} +2 -2
- package/dist/{chunk-DGHAXJBN.mjs → chunk-S2FKV4M5.mjs} +5 -5
- package/dist/{chunk-RGU7HOEC.mjs → chunk-SET2ANTY.mjs} +5 -7
- package/dist/chunk-SFH2NJEJ.mjs +47 -0
- package/dist/{chunk-6AW4KJHE.mjs → chunk-SIVYAI3M.mjs} +12 -12
- package/dist/{chunk-5FQIKDKP.mjs → chunk-THVO2N47.mjs} +8 -8
- package/dist/{chunk-JMHR3YGZ.mjs → chunk-TLAWKTSA.mjs} +3 -3
- package/dist/{chunk-HVY6KCCF.mjs → chunk-TOWTPLRC.mjs} +68 -72
- package/dist/{chunk-6JQFUE5I.mjs → chunk-UALR6JGV.mjs} +2 -2
- package/dist/{chunk-N6TNTQL6.mjs → chunk-UJZ4UHWI.mjs} +9 -11
- package/dist/{chunk-MARPPFOJ.mjs → chunk-UNACI2YK.mjs} +2 -2
- package/dist/{chunk-3NCUZIFP.mjs → chunk-V6XGXYCJ.mjs} +7 -7
- package/dist/chunk-VB5M6OZQ.mjs +57 -0
- package/dist/{chunk-5IS7G74I.mjs → chunk-VY5NEUP7.mjs} +6 -6
- package/dist/{chunk-JHJHG4GO.mjs → chunk-WE4YKBDE.mjs} +2 -2
- package/dist/{chunk-BKNFWEH2.mjs → chunk-WL6WVV47.mjs} +3 -3
- package/dist/{chunk-FWCSY2DS.mjs → chunk-WNQUEZJF.mjs} +22 -1
- package/dist/{chunk-2Y7YJKPE.mjs → chunk-WZ6UJCBL.mjs} +1 -1
- package/dist/{chunk-UMTOX62O.mjs → chunk-XYPW2XA5.mjs} +13 -10
- package/dist/chunk-Y2MTAVAK.mjs +34 -0
- package/dist/{chunk-6CR5N2JW.mjs → chunk-YCWLFG27.mjs} +6 -6
- package/dist/{chunk-PU4YZQXV.mjs → chunk-YE67AALL.mjs} +12 -12
- package/dist/{chunk-M3FV7LOK.mjs → chunk-YEWNFK5S.mjs} +6 -1
- package/dist/{chunk-R3VSPKNP.mjs → chunk-YIZHS72Z.mjs} +11 -12
- package/dist/{chunk-7PYJD5JI.mjs → chunk-ZEDMKQK2.mjs} +2 -2
- package/dist/{chunk-N2PT566P.mjs → chunk-ZFCDYW6N.mjs} +4 -4
- package/dist/chunk-ZGQIVGIN.mjs +57 -0
- package/dist/{chunk-Q2BGOAMG.mjs → chunk-ZKWXDQDG.mjs} +4 -4
- package/dist/{chunk-GHC7LLUX.mjs → chunk-ZOWL2L5J.mjs} +5 -5
- package/dist/components/ui/accordion.mjs +3 -3
- package/dist/components/ui/add-column-modal.js +2 -2
- package/dist/components/ui/add-column-modal.mjs +10 -10
- package/dist/components/ui/add-lead-modal.js +424 -82
- package/dist/components/ui/add-lead-modal.mjs +12 -9
- package/dist/components/ui/advisor-card.js +2 -2
- package/dist/components/ui/advisor-card.mjs +8 -8
- package/dist/components/ui/ai-assistant-drawer.js +2 -2
- package/dist/components/ui/ai-assistant-drawer.mjs +9 -9
- package/dist/components/ui/ai-builder.js +958 -0
- package/dist/components/ui/ai-builder.mjs +25 -0
- package/dist/components/ui/ai-conversations.js +2045 -0
- package/dist/components/ui/ai-conversations.mjs +41 -0
- package/dist/components/ui/alert-dialog.js +2 -2
- package/dist/components/ui/alert-dialog.mjs +5 -5
- package/dist/components/ui/alert.mjs +3 -3
- package/dist/components/ui/appointment-action-dialogs.js +19 -3
- package/dist/components/ui/appointment-action-dialogs.mjs +15 -14
- package/dist/components/ui/appointment-availability-settings.js +181 -111
- package/dist/components/ui/appointment-availability-settings.mjs +20 -18
- package/dist/components/ui/appointment-book-dialog.js +113 -24
- package/dist/components/ui/appointment-book-dialog.mjs +21 -20
- package/dist/components/ui/appointment-calendar-view.js +19 -3
- package/dist/components/ui/appointment-calendar-view.mjs +10 -9
- package/dist/components/ui/appointment-detail-sheet.js +19 -3
- package/dist/components/ui/appointment-detail-sheet.mjs +18 -17
- package/dist/components/ui/appointment-gmail-connect.js +49 -89
- package/dist/components/ui/appointment-gmail-connect.mjs +8 -9
- package/dist/components/ui/appointment-mini-card.js +2 -2
- package/dist/components/ui/appointment-mini-card.mjs +6 -6
- package/dist/components/ui/appointment-time-slot-picker.mjs +6 -6
- package/dist/components/ui/appointment-upcoming-card.js +19 -3
- package/dist/components/ui/appointment-upcoming-card.mjs +15 -14
- package/dist/components/ui/auth-logo.js +95 -0
- package/dist/components/ui/auth-logo.mjs +8 -0
- package/dist/components/ui/auth-page-layout.js +108 -0
- package/dist/components/ui/auth-page-layout.mjs +8 -0
- package/dist/components/ui/avatar.mjs +3 -3
- package/dist/components/ui/backoffice-alert-history-chart.js +2 -2
- package/dist/components/ui/backoffice-alert-history-chart.mjs +9 -9
- package/dist/components/ui/backoffice-alerts-chart.js +2 -2
- package/dist/components/ui/backoffice-alerts-chart.mjs +11 -11
- package/dist/components/ui/backoffice-connections-chart.js +2 -2
- package/dist/components/ui/backoffice-connections-chart.mjs +11 -11
- package/dist/components/ui/backoffice-contact-history-chart.js +2 -2
- package/dist/components/ui/backoffice-contact-history-chart.mjs +9 -9
- package/dist/components/ui/badge.mjs +4 -4
- package/dist/components/ui/borrowing-capacity-line-chart.js +145 -132
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +9 -9
- package/dist/components/ui/button.js +2 -2
- package/dist/components/ui/button.mjs +4 -4
- package/dist/components/ui/calendar.js +17 -3
- package/dist/components/ui/calendar.mjs +6 -5
- package/dist/components/ui/card.mjs +3 -3
- package/dist/components/ui/cash-balance-line-chart.js +158 -158
- package/dist/components/ui/cash-balance-line-chart.mjs +9 -9
- package/dist/components/ui/cashflow-bar-chart.js +2 -2
- package/dist/components/ui/cashflow-bar-chart.mjs +9 -9
- package/dist/components/ui/chat-widget-primitives.js +573 -0
- package/dist/components/ui/chat-widget-primitives.mjs +21 -0
- package/dist/components/ui/chat-widget.js +1268 -0
- package/dist/components/ui/chat-widget.mjs +29 -0
- package/dist/components/ui/checkbox.mjs +3 -3
- package/dist/components/ui/chip.js +2 -2
- package/dist/components/ui/chip.mjs +6 -6
- package/dist/components/ui/color-picker.js +2 -2
- package/dist/components/ui/color-picker.mjs +7 -7
- package/dist/components/ui/combobox.mjs +3 -3
- package/dist/components/ui/data-table.js +2 -2
- package/dist/components/ui/data-table.mjs +12 -12
- package/dist/components/ui/date-picker.js +22 -6
- package/dist/components/ui/date-picker.mjs +9 -8
- package/dist/components/ui/dialog.js +2 -2
- package/dist/components/ui/dialog.mjs +5 -5
- package/dist/components/ui/document-checklist-template.js +630 -0
- package/dist/components/ui/document-checklist-template.mjs +15 -0
- package/dist/components/ui/drawer.js +2 -2
- package/dist/components/ui/drawer.mjs +3 -3
- package/dist/components/ui/dropdown-menu.mjs +3 -3
- package/dist/components/ui/empty.mjs +3 -3
- package/dist/components/ui/expense-bar-chart.js +2 -2
- package/dist/components/ui/expense-bar-chart.mjs +9 -9
- package/dist/components/ui/field.mjs +5 -5
- package/dist/components/ui/financial-cards.js +431 -291
- package/dist/components/ui/financial-cards.mjs +10 -9
- package/dist/components/ui/financial-drawers.js +4 -4
- package/dist/components/ui/financial-drawers.mjs +8 -8
- package/dist/components/ui/financial-primitives.mjs +3 -3
- package/dist/components/ui/financial-sections.js +8 -9
- package/dist/components/ui/financial-sections.mjs +12 -12
- package/dist/components/ui/form-primitives.mjs +8 -8
- package/dist/components/ui/income-bar-chart.js +2 -2
- package/dist/components/ui/income-bar-chart.mjs +9 -9
- package/dist/components/ui/input-group.js +2 -2
- package/dist/components/ui/input-group.mjs +7 -7
- package/dist/components/ui/input-otp.mjs +3 -3
- package/dist/components/ui/input.mjs +3 -3
- package/dist/components/ui/kanban-column.js +19 -23
- package/dist/components/ui/kanban-column.mjs +14 -14
- package/dist/components/ui/label.mjs +3 -3
- package/dist/components/ui/onboarding-layout.js +476 -0
- package/dist/components/ui/onboarding-layout.mjs +11 -0
- package/dist/components/ui/opportunity-card.js +2 -2
- package/dist/components/ui/opportunity-card.mjs +12 -12
- package/dist/components/ui/opportunity-edit-modals.js +22 -6
- package/dist/components/ui/opportunity-edit-modals.mjs +21 -20
- package/dist/components/ui/opportunity-summary-tab.js +991 -674
- package/dist/components/ui/opportunity-summary-tab.mjs +26 -26
- package/dist/components/ui/page-header.mjs +3 -3
- package/dist/components/ui/page-top-bar.mjs +3 -3
- package/dist/components/ui/pagination.js +2 -2
- package/dist/components/ui/pagination.mjs +6 -6
- package/dist/components/ui/password-strength-tooltip.js +197 -0
- package/dist/components/ui/password-strength-tooltip.mjs +11 -0
- package/dist/components/ui/pipeline-alerts.mjs +3 -3
- package/dist/components/ui/pipeline-board.js +19 -23
- package/dist/components/ui/pipeline-board.mjs +18 -18
- package/dist/components/ui/pipeline-chart.js +12 -6
- package/dist/components/ui/pipeline-chart.mjs +4 -3
- package/dist/components/ui/pipeline-dialogs.js +28 -12
- package/dist/components/ui/pipeline-dialogs.mjs +14 -13
- package/dist/components/ui/pipeline-primitives.mjs +6 -6
- package/dist/components/ui/popover.mjs +3 -3
- package/dist/components/ui/progress.mjs +3 -3
- package/dist/components/ui/property-cashflow-doughnut-chart.js +2 -2
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +9 -9
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +2 -2
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +9 -9
- package/dist/components/ui/property-mobile-estimate-line-chart.js +2 -2
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +9 -9
- package/dist/components/ui/radio-group.mjs +3 -3
- package/dist/components/ui/select.mjs +3 -3
- package/dist/components/ui/separator.mjs +3 -3
- package/dist/components/ui/sheet.mjs +3 -3
- package/dist/components/ui/sidebar-nav.js +6 -5
- package/dist/components/ui/sidebar-nav.mjs +7 -7
- package/dist/components/ui/skeleton.mjs +3 -3
- package/dist/components/ui/slider.mjs +3 -3
- package/dist/components/ui/sonner.mjs +2 -2
- package/dist/components/ui/spinner.mjs +3 -3
- package/dist/components/ui/stage-timeline.mjs +10 -10
- package/dist/components/ui/stepper.mjs +3 -3
- package/dist/components/ui/switch.mjs +3 -3
- package/dist/components/ui/table.mjs +3 -3
- package/dist/components/ui/tabs.mjs +3 -3
- package/dist/components/ui/textarea.mjs +3 -3
- package/dist/components/ui/toggle-group.mjs +4 -4
- package/dist/components/ui/toggle.mjs +3 -3
- package/dist/components/ui/tooltip.mjs +3 -3
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +2 -2
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +9 -9
- package/dist/components/ui/transactions-income-expense-bar-chart.js +2 -2
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +9 -9
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +2 -2
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +9 -9
- package/dist/components/ui/two-fa-setup-form.js +612 -0
- package/dist/components/ui/two-fa-setup-form.mjs +16 -0
- package/dist/components/ui/upload-card.js +187 -0
- package/dist/components/ui/upload-card.mjs +10 -0
- package/dist/components/ui/video-background.js +118 -0
- package/dist/components/ui/video-background.mjs +8 -0
- package/dist/index.js +12674 -9311
- package/dist/index.mjs +341 -245
- package/dist/lib/colors.mjs +1 -1
- package/dist/lib/theme-provider.mjs +1 -1
- package/dist/lib/typography.mjs +2 -2
- package/dist/lib/utils.js +8 -2
- package/dist/lib/utils.mjs +6 -4
- package/dist/styles.css +1 -1
- package/package.json +61 -1
- package/src/components/index.tsx +126 -1
- package/src/components/ui/add-lead-modal.tsx +101 -142
- package/src/components/ui/ai-builder.tsx +560 -0
- package/src/components/ui/ai-conversations.tsx +1690 -0
- package/src/components/ui/appointment-availability-settings.tsx +152 -101
- package/src/components/ui/appointment-book-dialog.tsx +138 -24
- package/src/components/ui/appointment-calendar-view.tsx +2 -3
- package/src/components/ui/appointment-gmail-connect.tsx +23 -42
- package/src/components/ui/auth-logo.tsx +50 -0
- package/src/components/ui/auth-page-layout.tsx +59 -0
- package/src/components/ui/borrowing-capacity-line-chart.tsx +10 -8
- package/src/components/ui/button.tsx +2 -2
- package/src/components/ui/calendar.tsx +2 -1
- package/src/components/ui/cash-balance-line-chart.tsx +11 -20
- package/src/components/ui/chart-shared.tsx +10 -0
- package/src/components/ui/chat-widget-primitives.tsx +336 -0
- package/src/components/ui/chat-widget.tsx +822 -0
- package/src/components/ui/document-checklist-template.tsx +264 -0
- package/src/components/ui/drawer.tsx +2 -2
- package/src/components/ui/financial-cards.tsx +176 -78
- package/src/components/ui/financial-drawers.tsx +2 -2
- package/src/components/ui/financial-sections.tsx +1 -1
- package/src/components/ui/kanban-column.tsx +2 -5
- package/src/components/ui/onboarding-layout.tsx +109 -0
- package/src/components/ui/opportunity-summary-tab.tsx +469 -142
- package/src/components/ui/password-strength-tooltip.tsx +70 -0
- package/src/components/ui/pipeline-chart.tsx +2 -6
- package/src/components/ui/sidebar-nav.tsx +1 -11
- package/src/components/ui/two-fa-setup-form.tsx +229 -0
- package/src/components/ui/upload-card.tsx +98 -0
- package/src/components/ui/video-background.tsx +55 -0
- package/src/lib/format-date.ts +26 -0
- package/src/lib/utils.ts +11 -0
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +13 -0
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AlertCircle,
|
|
5
|
+
CheckCircle2,
|
|
6
|
+
Download,
|
|
7
|
+
FileText,
|
|
8
|
+
Mail,
|
|
9
|
+
Pencil,
|
|
10
|
+
Phone,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { Badge } from "./badge";
|
|
13
|
+
import {
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue,
|
|
19
|
+
} from "./select";
|
|
4
20
|
import { Button } from "./button";
|
|
21
|
+
import { Checkbox } from "./checkbox";
|
|
5
22
|
import { cn } from "@/lib/utils";
|
|
6
23
|
import { formatCurrency } from "@/lib/format-currency";
|
|
7
24
|
import { PROPERTY_ASSET_TYPES } from "@/lib/opportunity-constants";
|
|
@@ -56,6 +73,20 @@ import type {
|
|
|
56
73
|
// Types
|
|
57
74
|
// ---------------------------------------------------------------------------
|
|
58
75
|
|
|
76
|
+
export interface DocumentItem {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
documentType: string;
|
|
80
|
+
/** Which applicant uploaded this document. */
|
|
81
|
+
uploadedBy: "main" | "co";
|
|
82
|
+
uploadedAt: string;
|
|
83
|
+
status: "verified" | "pending" | "rejected";
|
|
84
|
+
/** PDF URL — opens in browser when title is clicked. */
|
|
85
|
+
url?: string;
|
|
86
|
+
/** Document checklist category this satisfies (e.g. "Income Verification"). */
|
|
87
|
+
checklistItem?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
59
90
|
export interface OpportunitySummaryTabProps {
|
|
60
91
|
/** Whether this is a joint application (shows Co-Applicant tab). */
|
|
61
92
|
isJoint?: boolean;
|
|
@@ -85,6 +116,16 @@ export interface OpportunitySummaryTabProps {
|
|
|
85
116
|
onCoIncomeChange?: (data: IncomeFormData) => void;
|
|
86
117
|
coExpenses?: ExpensesFormData;
|
|
87
118
|
onCoExpensesChange?: (data: ExpensesFormData) => void;
|
|
119
|
+
|
|
120
|
+
// ── Documents ─────────────────────────────────────────────────────────────
|
|
121
|
+
documents?: DocumentItem[];
|
|
122
|
+
/** Called with the selected documents when advisor clicks Download. */
|
|
123
|
+
onDocumentsDownload?: (docs: DocumentItem[]) => void;
|
|
124
|
+
/** Called when advisor changes a document's verification status. */
|
|
125
|
+
onDocumentStatusChange?: (
|
|
126
|
+
docId: string,
|
|
127
|
+
status: DocumentItem["status"],
|
|
128
|
+
) => void;
|
|
88
129
|
}
|
|
89
130
|
|
|
90
131
|
// ---------------------------------------------------------------------------
|
|
@@ -128,25 +169,15 @@ function lvrColorClass(pct: number): string {
|
|
|
128
169
|
return "text-destructive";
|
|
129
170
|
}
|
|
130
171
|
|
|
131
|
-
/** Serviceability label + badge
|
|
172
|
+
/** Serviceability label + badge variant based on net monthly surplus. */
|
|
132
173
|
function serviceabilityInfo(surplus: number): {
|
|
133
174
|
label: string;
|
|
134
|
-
|
|
175
|
+
variant: "success" | "warning" | "destructive";
|
|
135
176
|
} {
|
|
136
177
|
if (surplus > 1500)
|
|
137
|
-
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
141
|
-
if (surplus > 0)
|
|
142
|
-
return {
|
|
143
|
-
label: "Borderline",
|
|
144
|
-
badgeClass: "bg-amber-50 text-amber-700 border border-amber-200",
|
|
145
|
-
};
|
|
146
|
-
return {
|
|
147
|
-
label: "At Risk",
|
|
148
|
-
badgeClass: "bg-red-50 text-destructive border border-red-200",
|
|
149
|
-
};
|
|
178
|
+
return { label: "Likely Serviceable", variant: "success" };
|
|
179
|
+
if (surplus > 0) return { label: "Borderline", variant: "warning" };
|
|
180
|
+
return { label: "At Risk", variant: "destructive" };
|
|
150
181
|
}
|
|
151
182
|
|
|
152
183
|
/**
|
|
@@ -303,9 +334,7 @@ function HeroBand({
|
|
|
303
334
|
{surplusDisplay}
|
|
304
335
|
</span>
|
|
305
336
|
</div>
|
|
306
|
-
<
|
|
307
|
-
{svc.label}
|
|
308
|
-
</div>
|
|
337
|
+
<Badge variant={svc.variant}>{svc.label}</Badge>
|
|
309
338
|
</div>
|
|
310
339
|
</div>
|
|
311
340
|
);
|
|
@@ -313,9 +342,6 @@ function HeroBand({
|
|
|
313
342
|
|
|
314
343
|
// ---------------------------------------------------------------------------
|
|
315
344
|
// ApplicantCardTab (internal)
|
|
316
|
-
// Replaces ApplicantAccordionTab — uses bordered card pattern for consistency.
|
|
317
|
-
// AboutCard / IncomeCard / ExpensesCard each carry their own border so we float
|
|
318
|
-
// the section label + edit button ABOVE each card (no outer bordered wrapper).
|
|
319
345
|
// ---------------------------------------------------------------------------
|
|
320
346
|
|
|
321
347
|
interface ApplicantCardTabProps {
|
|
@@ -351,69 +377,226 @@ function ApplicantCardTab({
|
|
|
351
377
|
return (
|
|
352
378
|
<div className="flex flex-col gap-4 py-4">
|
|
353
379
|
{/* ── About ── */}
|
|
354
|
-
<div className="
|
|
355
|
-
<div className="flex items-center justify-between">
|
|
380
|
+
<div className="border border-border">
|
|
381
|
+
<div className="flex items-center justify-between px-4 py-2.5">
|
|
356
382
|
<FinancialSectionLabel>About</FinancialSectionLabel>
|
|
357
383
|
<SectionEditButton onClick={onEditAbout} title="Edit About" />
|
|
358
384
|
</div>
|
|
359
|
-
<
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
385
|
+
<div className="border-t border-border">
|
|
386
|
+
<AboutCard
|
|
387
|
+
borderless
|
|
388
|
+
title={about.title}
|
|
389
|
+
firstName={about.firstName}
|
|
390
|
+
lastName={about.lastName}
|
|
391
|
+
phone={about.phone}
|
|
392
|
+
email={about.email}
|
|
393
|
+
dob={about.dob}
|
|
394
|
+
gender={about.gender}
|
|
395
|
+
maritalStatus={about.maritalStatus}
|
|
396
|
+
numDependants={about.numDependants}
|
|
397
|
+
dependants={about.dependants}
|
|
398
|
+
citizenStatus={about.citizenStatus}
|
|
399
|
+
residentialAddress={about.residentialAddress}
|
|
400
|
+
residentialStatus={about.residentialStatus}
|
|
401
|
+
timeAtAddressYears={about.timeAtAddressYears}
|
|
402
|
+
timeAtAddressMonths={about.timeAtAddressMonths}
|
|
403
|
+
driversLicence={about.driversLicence}
|
|
404
|
+
passport={about.passport}
|
|
405
|
+
propertyInTrust={about.propertyInTrust}
|
|
406
|
+
companyOwnership={about.companyOwnership}
|
|
407
|
+
/>
|
|
408
|
+
</div>
|
|
379
409
|
</div>
|
|
380
410
|
|
|
381
411
|
{/* ── Income ── */}
|
|
382
|
-
<div className="
|
|
383
|
-
<div className="flex items-center justify-between">
|
|
412
|
+
<div className="border border-border">
|
|
413
|
+
<div className="flex items-center justify-between px-4 py-2.5">
|
|
384
414
|
<FinancialSectionLabel>Income</FinancialSectionLabel>
|
|
385
415
|
<SectionEditButton onClick={onEditIncome} title="Edit Income" />
|
|
386
416
|
</div>
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
417
|
+
<div className="border-t border-border">
|
|
418
|
+
<IncomeCard
|
|
419
|
+
borderless
|
|
420
|
+
items={income.items.map((i) => ({
|
|
421
|
+
incomeType: i.incomeType,
|
|
422
|
+
jobTitle: i.jobTitle,
|
|
423
|
+
companyName: i.companyName,
|
|
424
|
+
companyAddress: i.companyAddress,
|
|
425
|
+
startDate: i.startDate,
|
|
426
|
+
stillInPosition: i.stillInPosition,
|
|
427
|
+
endDate: i.endDate,
|
|
428
|
+
companyType: i.companyType,
|
|
429
|
+
amountLabel: `${formatCurrency(i.incomeAmount)} / ${i.frequency}`,
|
|
430
|
+
}))}
|
|
431
|
+
totalMonthly={totalMonthlyIncome}
|
|
432
|
+
/>
|
|
433
|
+
</div>
|
|
401
434
|
</div>
|
|
402
435
|
|
|
403
436
|
{/* ── Expenses ── */}
|
|
404
|
-
<div className="
|
|
405
|
-
<div className="flex items-center justify-between">
|
|
437
|
+
<div className="border border-border">
|
|
438
|
+
<div className="flex items-center justify-between px-4 py-2.5">
|
|
406
439
|
<FinancialSectionLabel>Expenses</FinancialSectionLabel>
|
|
407
440
|
<SectionEditButton onClick={onEditExpenses} title="Edit Expenses" />
|
|
408
441
|
</div>
|
|
409
|
-
<
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
442
|
+
<div className="border-t border-border">
|
|
443
|
+
<ExpensesCard
|
|
444
|
+
borderless
|
|
445
|
+
items={expenses.items.map((e) => ({
|
|
446
|
+
expenseType: e.expenseType,
|
|
447
|
+
amountLabel: `${formatCurrency(e.amount)} / ${e.frequency}`,
|
|
448
|
+
}))}
|
|
449
|
+
totalMonthly={totalMonthlyExpenses}
|
|
450
|
+
/>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
// DocRow — single document row (flat list or grouped)
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
|
|
461
|
+
const STATUS_OPTIONS = ["verified", "pending", "rejected"] as const;
|
|
462
|
+
|
|
463
|
+
const STATUS_CONFIG: Record<
|
|
464
|
+
DocumentItem["status"],
|
|
465
|
+
{
|
|
466
|
+
Icon: React.ComponentType<{ className?: string }>;
|
|
467
|
+
label: string;
|
|
468
|
+
iconCls: string;
|
|
469
|
+
triggerCls: string;
|
|
470
|
+
}
|
|
471
|
+
> = {
|
|
472
|
+
verified: {
|
|
473
|
+
Icon: CheckCircle2,
|
|
474
|
+
label: "Verified",
|
|
475
|
+
iconCls: "text-success",
|
|
476
|
+
triggerCls: "border-success text-success",
|
|
477
|
+
},
|
|
478
|
+
pending: {
|
|
479
|
+
Icon: AlertCircle,
|
|
480
|
+
label: "Pending",
|
|
481
|
+
iconCls: "text-warning",
|
|
482
|
+
triggerCls: "border-warning text-warning",
|
|
483
|
+
},
|
|
484
|
+
rejected: {
|
|
485
|
+
Icon: AlertCircle,
|
|
486
|
+
label: "Rejected",
|
|
487
|
+
iconCls: "text-destructive",
|
|
488
|
+
triggerCls: "border-destructive text-destructive",
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
interface DocRowProps {
|
|
493
|
+
doc: DocumentItem;
|
|
494
|
+
status: DocumentItem["status"];
|
|
495
|
+
isSelected: boolean;
|
|
496
|
+
uploaderName: string;
|
|
497
|
+
onRowClick: () => void;
|
|
498
|
+
onStatusChange: (status: DocumentItem["status"]) => void;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function DocRow({
|
|
502
|
+
doc,
|
|
503
|
+
status,
|
|
504
|
+
isSelected,
|
|
505
|
+
uploaderName,
|
|
506
|
+
onRowClick,
|
|
507
|
+
onStatusChange,
|
|
508
|
+
}: DocRowProps) {
|
|
509
|
+
const {
|
|
510
|
+
Icon: StatusIcon,
|
|
511
|
+
label: statusLabel,
|
|
512
|
+
iconCls,
|
|
513
|
+
triggerCls,
|
|
514
|
+
} = STATUS_CONFIG[status];
|
|
515
|
+
|
|
516
|
+
return (
|
|
517
|
+
<div
|
|
518
|
+
onClick={onRowClick}
|
|
519
|
+
className={cn(
|
|
520
|
+
"flex min-h-[44px] cursor-pointer items-center gap-3 px-4 transition-colors",
|
|
521
|
+
isSelected ? "bg-primary/5" : "hover:bg-muted/40",
|
|
522
|
+
)}
|
|
523
|
+
>
|
|
524
|
+
<span className="shrink-0" onClick={(e) => e.stopPropagation()}>
|
|
525
|
+
<Checkbox checked={isSelected} onCheckedChange={() => onRowClick()} />
|
|
526
|
+
</span>
|
|
527
|
+
<FileText className="size-4 shrink-0 text-muted-foreground" />
|
|
528
|
+
|
|
529
|
+
{/* Name + subtitle */}
|
|
530
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
531
|
+
{doc.url ? (
|
|
532
|
+
<a
|
|
533
|
+
href={doc.url}
|
|
534
|
+
target="_blank"
|
|
535
|
+
rel="noreferrer"
|
|
536
|
+
onClick={(e) => e.stopPropagation()}
|
|
537
|
+
className="w-fit text-label-medium text-foreground underline-offset-2 hover:underline"
|
|
538
|
+
>
|
|
539
|
+
{doc.name}
|
|
540
|
+
</a>
|
|
541
|
+
) : (
|
|
542
|
+
<span className="text-label-medium text-foreground">{doc.name}</span>
|
|
543
|
+
)}
|
|
544
|
+
<span className="text-xs text-muted-foreground">
|
|
545
|
+
{doc.documentType}
|
|
546
|
+
{doc.checklistItem ? ` · ${doc.checklistItem}` : ""}
|
|
547
|
+
</span>
|
|
416
548
|
</div>
|
|
549
|
+
|
|
550
|
+
{/* Uploader */}
|
|
551
|
+
<Badge variant="secondary" className="shrink-0">
|
|
552
|
+
{uploaderName}
|
|
553
|
+
</Badge>
|
|
554
|
+
|
|
555
|
+
{/* Status — select */}
|
|
556
|
+
<span className="shrink-0" onClick={(e) => e.stopPropagation()}>
|
|
557
|
+
<Select
|
|
558
|
+
value={status}
|
|
559
|
+
onValueChange={(v) => onStatusChange(v as DocumentItem["status"])}
|
|
560
|
+
>
|
|
561
|
+
<SelectTrigger
|
|
562
|
+
size="sm"
|
|
563
|
+
className={cn("h-7 w-[116px] gap-1.5 text-xs", triggerCls)}
|
|
564
|
+
>
|
|
565
|
+
<StatusIcon className={cn("size-3.5", iconCls)} />
|
|
566
|
+
{statusLabel}
|
|
567
|
+
</SelectTrigger>
|
|
568
|
+
<SelectContent>
|
|
569
|
+
{STATUS_OPTIONS.map((v) => {
|
|
570
|
+
const { Icon, label, iconCls } = STATUS_CONFIG[v];
|
|
571
|
+
return (
|
|
572
|
+
<SelectItem key={v} value={v}>
|
|
573
|
+
<span className="flex items-center gap-1.5">
|
|
574
|
+
<Icon className={cn("size-3.5", iconCls)} />
|
|
575
|
+
{label}
|
|
576
|
+
</span>
|
|
577
|
+
</SelectItem>
|
|
578
|
+
);
|
|
579
|
+
})}
|
|
580
|
+
</SelectContent>
|
|
581
|
+
</Select>
|
|
582
|
+
</span>
|
|
583
|
+
|
|
584
|
+
{/* Upload date */}
|
|
585
|
+
<span className="shrink-0 text-xs text-muted-foreground">
|
|
586
|
+
{doc.uploadedAt}
|
|
587
|
+
</span>
|
|
588
|
+
|
|
589
|
+
{/* Per-row download */}
|
|
590
|
+
{doc.url && (
|
|
591
|
+
<a
|
|
592
|
+
href={doc.url}
|
|
593
|
+
download
|
|
594
|
+
onClick={(e) => e.stopPropagation()}
|
|
595
|
+
className="shrink-0 text-muted-foreground transition-colors hover:text-foreground"
|
|
596
|
+
>
|
|
597
|
+
<Download className="size-4" />
|
|
598
|
+
</a>
|
|
599
|
+
)}
|
|
417
600
|
</div>
|
|
418
601
|
);
|
|
419
602
|
}
|
|
@@ -442,6 +625,9 @@ export function OpportunitySummaryTab({
|
|
|
442
625
|
onCoIncomeChange,
|
|
443
626
|
coExpenses,
|
|
444
627
|
onCoExpensesChange,
|
|
628
|
+
documents = [],
|
|
629
|
+
onDocumentsDownload,
|
|
630
|
+
onDocumentStatusChange,
|
|
445
631
|
}: OpportunitySummaryTabProps) {
|
|
446
632
|
// ── Portal container — scopes edit modal overlays inside the drawer ────────
|
|
447
633
|
const [portalEl, setPortalEl] = useState<HTMLDivElement | null>(null);
|
|
@@ -451,8 +637,69 @@ export function OpportunitySummaryTab({
|
|
|
451
637
|
"joint",
|
|
452
638
|
);
|
|
453
639
|
|
|
454
|
-
// ──
|
|
455
|
-
const [
|
|
640
|
+
// ── Document selection for download ──────────────────────────────────────
|
|
641
|
+
const [selectedDocIds, setSelectedDocIds] = useState<Set<string>>(new Set());
|
|
642
|
+
|
|
643
|
+
function toggleDocSelection(id: string) {
|
|
644
|
+
setSelectedDocIds((prev) => {
|
|
645
|
+
const next = new Set(prev);
|
|
646
|
+
if (next.has(id)) next.delete(id);
|
|
647
|
+
else next.add(id);
|
|
648
|
+
return next;
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ── Document status overrides (local optimistic state) ────────────────────
|
|
653
|
+
const [docStatusOverrides, setDocStatusOverrides] = useState<
|
|
654
|
+
Record<string, DocumentItem["status"]>
|
|
655
|
+
>({});
|
|
656
|
+
|
|
657
|
+
function handleDocStatusChange(
|
|
658
|
+
docId: string,
|
|
659
|
+
status: DocumentItem["status"],
|
|
660
|
+
) {
|
|
661
|
+
setDocStatusOverrides((prev) => ({ ...prev, [docId]: status }));
|
|
662
|
+
onDocumentStatusChange?.(docId, status);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function effectiveStatus(doc: DocumentItem): DocumentItem["status"] {
|
|
666
|
+
return docStatusOverrides[doc.id] ?? doc.status;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ── Checklist progress — derived from docs grouped by checklistItem ────────
|
|
670
|
+
const checklistProgress = React.useMemo(() => {
|
|
671
|
+
const map = new Map<
|
|
672
|
+
string,
|
|
673
|
+
{ count: number; hasVerified: boolean; hasPending: boolean }
|
|
674
|
+
>();
|
|
675
|
+
for (const doc of documents) {
|
|
676
|
+
if (!doc.checklistItem) continue;
|
|
677
|
+
const st = docStatusOverrides[doc.id] ?? doc.status;
|
|
678
|
+
const entry = map.get(doc.checklistItem) ?? {
|
|
679
|
+
count: 0,
|
|
680
|
+
hasVerified: false,
|
|
681
|
+
hasPending: false,
|
|
682
|
+
};
|
|
683
|
+
entry.count += 1;
|
|
684
|
+
if (st === "verified") entry.hasVerified = true;
|
|
685
|
+
if (st === "pending") entry.hasPending = true;
|
|
686
|
+
map.set(doc.checklistItem, entry);
|
|
687
|
+
}
|
|
688
|
+
return Array.from(map.entries()).map(([name, data]) => ({ name, ...data }));
|
|
689
|
+
}, [documents, docStatusOverrides]);
|
|
690
|
+
|
|
691
|
+
// ── Group docs by checklistItem; null when flat list is preferred ────────
|
|
692
|
+
const groupedDocs = React.useMemo(() => {
|
|
693
|
+
if (documents.length <= 5 || !documents.some((d) => d.checklistItem))
|
|
694
|
+
return null;
|
|
695
|
+
const groups: Record<string, DocumentItem[]> = {};
|
|
696
|
+
for (const doc of documents) {
|
|
697
|
+
const key = doc.checklistItem ?? "Other";
|
|
698
|
+
if (!groups[key]) groups[key] = [];
|
|
699
|
+
groups[key].push(doc);
|
|
700
|
+
}
|
|
701
|
+
return groups;
|
|
702
|
+
}, [documents]);
|
|
456
703
|
|
|
457
704
|
// ── Modal open state (internal) ───────────────────────────────────────────
|
|
458
705
|
const [editLoanOpen, setEditLoanOpen] = useState(false);
|
|
@@ -520,76 +767,58 @@ export function OpportunitySummaryTab({
|
|
|
520
767
|
netSurplus={netSurplus}
|
|
521
768
|
/>
|
|
522
769
|
|
|
523
|
-
{/* ── Loan Scenario
|
|
770
|
+
{/* ── Loan Scenario ── */}
|
|
524
771
|
<div className="mb-4 border border-border">
|
|
525
772
|
<div className="flex items-center justify-between px-4 py-2.5">
|
|
526
|
-
|
|
527
|
-
<Button
|
|
528
|
-
type="button"
|
|
529
|
-
variant="ghost"
|
|
530
|
-
className="h-auto flex-1 justify-start gap-2 px-0 text-left"
|
|
531
|
-
onClick={() => setLoanScenarioExpanded((v) => !v)}
|
|
532
|
-
aria-expanded={loanScenarioExpanded}
|
|
533
|
-
>
|
|
534
|
-
<FinancialSectionLabel>Loan Scenario</FinancialSectionLabel>
|
|
535
|
-
{loanScenarioExpanded ? (
|
|
536
|
-
<ChevronUp className="size-3.5 text-muted-foreground" />
|
|
537
|
-
) : (
|
|
538
|
-
<ChevronDown className="size-3.5 text-muted-foreground" />
|
|
539
|
-
)}
|
|
540
|
-
</Button>
|
|
541
|
-
{/* Right: edit button */}
|
|
773
|
+
<FinancialSectionLabel>Loan Scenario</FinancialSectionLabel>
|
|
542
774
|
<SectionEditButton
|
|
543
775
|
onClick={() => setEditLoanOpen(true)}
|
|
544
776
|
title="Edit Loan Scenario"
|
|
545
777
|
/>
|
|
546
778
|
</div>
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
<
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
/>
|
|
591
|
-
</div>
|
|
592
|
-
)}
|
|
779
|
+
<div className="grid grid-cols-4 gap-x-6 gap-y-4 border-t border-border p-4">
|
|
780
|
+
<FinancialDetailField label="Lending Type" value="Home Loan" />
|
|
781
|
+
<FinancialDetailField
|
|
782
|
+
label="Purpose of Loan"
|
|
783
|
+
value={loanScenario.loanPurpose}
|
|
784
|
+
/>
|
|
785
|
+
<FinancialDetailField
|
|
786
|
+
label="Loan Amount"
|
|
787
|
+
value={formatCurrency(loanScenario.loanAmount)}
|
|
788
|
+
/>
|
|
789
|
+
<FinancialDetailField
|
|
790
|
+
label="Property Estimate"
|
|
791
|
+
value={formatCurrency(loanScenario.propertyEstimate)}
|
|
792
|
+
/>
|
|
793
|
+
<FinancialDetailField
|
|
794
|
+
label="Cash / Deposit"
|
|
795
|
+
value={formatCurrency(loanScenario.cashEquity)}
|
|
796
|
+
/>
|
|
797
|
+
<FinancialDetailField label="Property Address" value="—" />
|
|
798
|
+
<FinancialDetailField
|
|
799
|
+
label="Duration"
|
|
800
|
+
value={`${loanScenario.loanDuration} years`}
|
|
801
|
+
/>
|
|
802
|
+
<FinancialDetailField
|
|
803
|
+
label="Important Features"
|
|
804
|
+
value={
|
|
805
|
+
[
|
|
806
|
+
loanScenario.featureVariableRate && "Variable Rate",
|
|
807
|
+
loanScenario.featureFixedRate && "Fixed Rate",
|
|
808
|
+
loanScenario.featureRedrawFacility && "Redraw Facility",
|
|
809
|
+
loanScenario.feature100Offset && "100% Offset",
|
|
810
|
+
loanScenario.featureSplitLoan && "Split Loan",
|
|
811
|
+
loanScenario.featureInterestOnly && "Interest Only",
|
|
812
|
+
]
|
|
813
|
+
.filter(Boolean)
|
|
814
|
+
.join(", ") || "—"
|
|
815
|
+
}
|
|
816
|
+
/>
|
|
817
|
+
<FinancialDetailField
|
|
818
|
+
label="Top Priorities"
|
|
819
|
+
value={loanScenario.priorities.join(", ") || "—"}
|
|
820
|
+
/>
|
|
821
|
+
</div>
|
|
593
822
|
</div>
|
|
594
823
|
|
|
595
824
|
{/* ── Applicant sub-tabs — filled variant, full width ── */}
|
|
@@ -671,16 +900,114 @@ export function OpportunitySummaryTab({
|
|
|
671
900
|
</div>
|
|
672
901
|
</div>
|
|
673
902
|
|
|
674
|
-
{/* Documents
|
|
675
|
-
what's been uploaded vs what's still missing */}
|
|
903
|
+
{/* Documents */}
|
|
676
904
|
<div className="border border-border">
|
|
677
|
-
|
|
905
|
+
{/* Section header */}
|
|
906
|
+
<div className="flex min-h-[40px] items-center justify-between px-4">
|
|
678
907
|
<FinancialSectionLabel>Documents</FinancialSectionLabel>
|
|
908
|
+
<div className="flex items-center gap-3">
|
|
909
|
+
{selectedDocIds.size > 0 && (
|
|
910
|
+
<Button
|
|
911
|
+
size="sm"
|
|
912
|
+
variant="outline-secondary"
|
|
913
|
+
className="h-7 gap-1.5 text-xs"
|
|
914
|
+
onClick={() => {
|
|
915
|
+
const selected = documents.filter((d) =>
|
|
916
|
+
selectedDocIds.has(d.id),
|
|
917
|
+
);
|
|
918
|
+
onDocumentsDownload?.(selected);
|
|
919
|
+
}}
|
|
920
|
+
>
|
|
921
|
+
<Download className="size-3.5" />
|
|
922
|
+
Download ({selectedDocIds.size})
|
|
923
|
+
</Button>
|
|
924
|
+
)}
|
|
925
|
+
{documents.length > 0 && (
|
|
926
|
+
<span className="text-xs text-muted-foreground">
|
|
927
|
+
{documents.length} file
|
|
928
|
+
{documents.length !== 1 ? "s" : ""}
|
|
929
|
+
</span>
|
|
930
|
+
)}
|
|
931
|
+
</div>
|
|
679
932
|
</div>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
933
|
+
|
|
934
|
+
{/* Checklist progress — derived from docs grouped by checklistItem */}
|
|
935
|
+
{checklistProgress.length > 0 && (
|
|
936
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1.5 border-t border-border px-4 py-3">
|
|
937
|
+
{checklistProgress.map((cat) => (
|
|
938
|
+
<span
|
|
939
|
+
key={cat.name}
|
|
940
|
+
className={cn(
|
|
941
|
+
"flex items-center gap-1 text-xs",
|
|
942
|
+
cat.hasVerified ? "text-success" : "text-warning",
|
|
943
|
+
)}
|
|
944
|
+
>
|
|
945
|
+
{cat.hasVerified ? (
|
|
946
|
+
<CheckCircle2 className="size-3.5 shrink-0" />
|
|
947
|
+
) : (
|
|
948
|
+
<AlertCircle className="size-3.5 shrink-0" />
|
|
949
|
+
)}
|
|
950
|
+
{cat.name}
|
|
951
|
+
<span className="text-muted-foreground">({cat.count})</span>
|
|
952
|
+
</span>
|
|
953
|
+
))}
|
|
954
|
+
</div>
|
|
955
|
+
)}
|
|
956
|
+
|
|
957
|
+
{/* Document list */}
|
|
958
|
+
<div className="border-t border-border">
|
|
959
|
+
{documents.length === 0 ? (
|
|
960
|
+
<p className="px-4 py-4 text-body-small text-muted-foreground">
|
|
961
|
+
No documents uploaded yet.
|
|
962
|
+
</p>
|
|
963
|
+
) : groupedDocs ? (
|
|
964
|
+
// Grouped by checklistItem
|
|
965
|
+
Object.entries(groupedDocs).map(([category, docs]) => (
|
|
966
|
+
<div key={category}>
|
|
967
|
+
<div className="bg-muted/30 px-4 py-1.5 text-xs font-medium text-muted-foreground">
|
|
968
|
+
{category}
|
|
969
|
+
</div>
|
|
970
|
+
<div className="flex flex-col divide-y divide-border">
|
|
971
|
+
{docs.map((doc) => (
|
|
972
|
+
<DocRow
|
|
973
|
+
key={doc.id}
|
|
974
|
+
doc={doc}
|
|
975
|
+
status={effectiveStatus(doc)}
|
|
976
|
+
isSelected={selectedDocIds.has(doc.id)}
|
|
977
|
+
uploaderName={
|
|
978
|
+
doc.uploadedBy === "main"
|
|
979
|
+
? mainAbout.firstName || "Main"
|
|
980
|
+
: coAbout?.firstName || "Co"
|
|
981
|
+
}
|
|
982
|
+
onRowClick={() => toggleDocSelection(doc.id)}
|
|
983
|
+
onStatusChange={(s) =>
|
|
984
|
+
handleDocStatusChange(doc.id, s)
|
|
985
|
+
}
|
|
986
|
+
/>
|
|
987
|
+
))}
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
))
|
|
991
|
+
) : (
|
|
992
|
+
// Flat list
|
|
993
|
+
<div className="flex flex-col divide-y divide-border">
|
|
994
|
+
{documents.map((doc) => (
|
|
995
|
+
<DocRow
|
|
996
|
+
key={doc.id}
|
|
997
|
+
doc={doc}
|
|
998
|
+
status={effectiveStatus(doc)}
|
|
999
|
+
isSelected={selectedDocIds.has(doc.id)}
|
|
1000
|
+
uploaderName={
|
|
1001
|
+
doc.uploadedBy === "main"
|
|
1002
|
+
? mainAbout.firstName || "Main"
|
|
1003
|
+
: coAbout?.firstName || "Co"
|
|
1004
|
+
}
|
|
1005
|
+
onRowClick={() => toggleDocSelection(doc.id)}
|
|
1006
|
+
onStatusChange={(s) => handleDocStatusChange(doc.id, s)}
|
|
1007
|
+
/>
|
|
1008
|
+
))}
|
|
1009
|
+
</div>
|
|
1010
|
+
)}
|
|
684
1011
|
</div>
|
|
685
1012
|
</div>
|
|
686
1013
|
|
|
@@ -725,7 +1052,7 @@ export function OpportunitySummaryTab({
|
|
|
725
1052
|
title="Edit Debts"
|
|
726
1053
|
/>
|
|
727
1054
|
</div>
|
|
728
|
-
<div className="grid grid-cols-2 gap-3 p-4">
|
|
1055
|
+
<div className="grid grid-cols-2 gap-3 border-t border-border p-4">
|
|
729
1056
|
{debts.map((debt) => (
|
|
730
1057
|
<DebtCard key={debt.id} {...debtToCard(debt)} />
|
|
731
1058
|
))}
|