@wealthx/shadcn 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +203 -150
- package/CHANGELOG.md +29 -0
- package/dist/{chunk-4Y6R4WEC.mjs → chunk-2A5RRQGG.mjs} +9 -22
- package/dist/{chunk-TS2ZX2VS.mjs → chunk-2UM72RJ7.mjs} +11 -15
- package/dist/{chunk-A56YQQHG.mjs → chunk-3NCUZIFP.mjs} +2 -2
- package/dist/chunk-3OYFOX3X.mjs +79 -0
- package/dist/{chunk-RP3SQYA3.mjs → chunk-3TTACBDP.mjs} +9 -4
- package/dist/chunk-4GAWMKMI.mjs +710 -0
- package/dist/{chunk-SYOD63OZ.mjs → chunk-5FQIKDKP.mjs} +6 -6
- package/dist/{chunk-K3JYD4IU.mjs → chunk-5IS7G74I.mjs} +11 -4
- package/dist/chunk-6AW4KJHE.mjs +235 -0
- package/dist/chunk-6CR5N2JW.mjs +302 -0
- package/dist/{chunk-XIRTEFKH.mjs → chunk-6DZEXFNB.mjs} +36 -8
- package/dist/chunk-6O6KD7CE.mjs +271 -0
- package/dist/chunk-7PV3IWCN.mjs +33 -0
- package/dist/{chunk-SPJ5KXW7.mjs → chunk-7S5AESZO.mjs} +5 -5
- package/dist/{chunk-RYCLWMZ7.mjs → chunk-ABFDMHOR.mjs} +9 -7
- package/dist/{chunk-SWGT756Z.mjs → chunk-AMQZRHEZ.mjs} +10 -4
- package/dist/{chunk-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -4
- package/dist/{chunk-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
- package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
- package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
- package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
- package/dist/chunk-DOEO3CDL.mjs +27 -0
- package/dist/{chunk-KUDCQ4FI.mjs → chunk-DUJTAXMH.mjs} +9 -4
- package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
- package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
- package/dist/chunk-EWRB4PAD.mjs +468 -0
- package/dist/chunk-FAKPBKLT.mjs +253 -0
- package/dist/chunk-FNQXOAYJ.mjs +169 -0
- package/dist/{chunk-4CX4SBRO.mjs → chunk-GHC7LLUX.mjs} +14 -5
- package/dist/chunk-HBZLGDIN.mjs +507 -0
- package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
- package/dist/{chunk-PR6V5XKM.mjs → chunk-HVY6KCCF.mjs} +7 -4
- package/dist/chunk-I3RZS7V2.mjs +136 -0
- package/dist/chunk-IAE3F7DR.mjs +1962 -0
- package/dist/{chunk-ZRO5JO3H.mjs → chunk-IHMFS7NZ.mjs} +81 -84
- package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
- package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
- package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
- package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
- package/dist/{chunk-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
- package/dist/chunk-LV35NGVG.mjs +272 -0
- package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
- package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
- package/dist/chunk-MIZQHHUO.mjs +441 -0
- package/dist/chunk-MN5NYQCL.mjs +29 -0
- package/dist/chunk-NL3ZO62D.mjs +31 -0
- package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
- package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
- package/dist/chunk-PBL4OQV2.mjs +283 -0
- package/dist/{chunk-3WMX6KWS.mjs → chunk-PU4YZQXV.mjs} +11 -12
- package/dist/chunk-QMY3AZJH.mjs +80 -0
- package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
- package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
- package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
- package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
- package/dist/chunk-U4NDAF2P.mjs +207 -0
- package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.mjs} +1 -1
- package/dist/chunk-UMTOX62O.mjs +415 -0
- package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
- package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
- package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
- package/dist/chunk-ZC45IGZO.mjs +388 -0
- package/dist/components/ui/add-column-modal.js +42 -14
- package/dist/components/ui/add-column-modal.mjs +4 -4
- package/dist/components/ui/add-lead-modal.js +42 -11
- package/dist/components/ui/add-lead-modal.mjs +3 -3
- package/dist/components/ui/advisor-card.js +497 -0
- package/dist/components/ui/advisor-card.mjs +13 -0
- package/dist/components/ui/ai-assistant-drawer.js +11 -10
- package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
- package/dist/components/ui/alert-dialog.js +2 -2
- package/dist/components/ui/alert-dialog.mjs +2 -2
- package/dist/components/ui/appointment-action-dialogs.js +1160 -0
- package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
- package/dist/components/ui/appointment-availability-settings.js +1590 -0
- package/dist/components/ui/appointment-availability-settings.mjs +23 -0
- package/dist/components/ui/appointment-book-dialog.js +1744 -0
- package/dist/components/ui/appointment-book-dialog.mjs +27 -0
- package/dist/components/ui/appointment-calendar-view.js +833 -0
- package/dist/components/ui/appointment-calendar-view.mjs +14 -0
- package/dist/components/ui/appointment-detail-sheet.js +1517 -0
- package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
- package/dist/components/ui/appointment-gmail-connect.js +467 -0
- package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
- package/dist/components/ui/appointment-mini-card.js +345 -0
- package/dist/components/ui/appointment-mini-card.mjs +11 -0
- package/dist/components/ui/appointment-time-slot-picker.js +311 -0
- package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
- package/dist/components/ui/appointment-upcoming-card.js +1268 -0
- package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
- package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
- package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
- package/dist/components/ui/backoffice-alerts-chart.js +786 -0
- package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
- package/dist/components/ui/backoffice-connections-chart.js +817 -0
- package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
- package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
- package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
- package/dist/components/ui/badge.js +6 -6
- package/dist/components/ui/badge.mjs +1 -1
- package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
- package/dist/components/ui/button.js +2 -2
- package/dist/components/ui/button.mjs +1 -1
- package/dist/components/ui/calendar.js +2 -2
- package/dist/components/ui/calendar.mjs +2 -2
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/card.mjs +1 -1
- package/dist/components/ui/cash-balance-line-chart.js +31 -23
- package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
- package/dist/components/ui/cashflow-bar-chart.js +12 -5
- package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
- package/dist/components/ui/chip.js +97 -18
- package/dist/components/ui/chip.mjs +3 -2
- package/dist/components/ui/color-picker.js +547 -0
- package/dist/components/ui/color-picker.mjs +24 -0
- package/dist/components/ui/data-table.js +182 -129
- package/dist/components/ui/data-table.mjs +3 -2
- package/dist/components/ui/date-picker.js +48 -27
- package/dist/components/ui/date-picker.mjs +4 -3
- package/dist/components/ui/dialog.js +37 -9
- package/dist/components/ui/dialog.mjs +2 -2
- package/dist/components/ui/expense-bar-chart.js +12 -5
- package/dist/components/ui/expense-bar-chart.mjs +5 -4
- package/dist/components/ui/field.mjs +2 -2
- package/dist/components/ui/financial-cards.js +322 -155
- package/dist/components/ui/financial-cards.mjs +5 -3
- package/dist/components/ui/financial-drawers.js +2 -2
- package/dist/components/ui/financial-drawers.mjs +3 -3
- package/dist/components/ui/financial-sections.js +14 -10
- package/dist/components/ui/financial-sections.mjs +6 -5
- package/dist/components/ui/form-primitives.js +4 -4
- package/dist/components/ui/form-primitives.mjs +3 -3
- package/dist/components/ui/income-bar-chart.js +12 -5
- package/dist/components/ui/income-bar-chart.mjs +5 -4
- package/dist/components/ui/input-group.js +2 -2
- package/dist/components/ui/input-group.mjs +2 -2
- package/dist/components/ui/kanban-column.js +52 -44
- package/dist/components/ui/kanban-column.mjs +7 -5
- package/dist/components/ui/opportunity-card.js +52 -44
- package/dist/components/ui/opportunity-card.mjs +6 -4
- package/dist/components/ui/opportunity-edit-modals.js +1371 -1267
- package/dist/components/ui/opportunity-edit-modals.mjs +10 -10
- package/dist/components/ui/opportunity-summary-tab.js +2748 -2161
- package/dist/components/ui/opportunity-summary-tab.mjs +16 -16
- package/dist/components/ui/page-header.js +92 -0
- package/dist/components/ui/page-header.mjs +8 -0
- package/dist/components/ui/page-top-bar.js +88 -0
- package/dist/components/ui/page-top-bar.mjs +8 -0
- package/dist/components/ui/pagination.js +303 -19
- package/dist/components/ui/pagination.mjs +11 -4
- package/dist/components/ui/pipeline-board.js +209 -195
- package/dist/components/ui/pipeline-board.mjs +10 -8
- package/dist/components/ui/pipeline-dialogs.js +118 -69
- package/dist/components/ui/pipeline-dialogs.mjs +8 -7
- package/dist/components/ui/pipeline-primitives.js +6 -6
- package/dist/components/ui/pipeline-primitives.mjs +2 -2
- package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
- package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
- package/dist/components/ui/sidebar-nav.js +679 -0
- package/dist/components/ui/sidebar-nav.mjs +14 -0
- package/dist/components/ui/stage-timeline.js +6 -6
- package/dist/components/ui/stage-timeline.mjs +3 -3
- package/dist/components/ui/stepper.js +283 -0
- package/dist/components/ui/stepper.mjs +18 -0
- package/dist/components/ui/toggle-group.js +4 -4
- package/dist/components/ui/toggle-group.mjs +2 -2
- package/dist/components/ui/toggle.js +4 -4
- package/dist/components/ui/toggle.mjs +1 -1
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
- package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
- package/dist/index.js +12927 -8522
- package/dist/index.mjs +288 -190
- package/dist/lib/typography.js +10 -10
- package/dist/lib/typography.mjs +1 -1
- package/dist/styles.css +1 -1
- package/package.json +86 -1
- package/src/components/index.tsx +146 -0
- package/src/components/ui/add-column-modal.tsx +7 -7
- package/src/components/ui/add-lead-modal.tsx +6 -3
- package/src/components/ui/advisor-card.tsx +227 -0
- package/src/components/ui/ai-assistant-drawer.tsx +4 -3
- package/src/components/ui/appointment-action-dialogs.tsx +297 -0
- package/src/components/ui/appointment-availability-settings.tsx +645 -0
- package/src/components/ui/appointment-book-dialog.tsx +618 -0
- package/src/components/ui/appointment-calendar-view.tsx +510 -0
- package/src/components/ui/appointment-detail-sheet.tsx +415 -0
- package/src/components/ui/appointment-gmail-connect.tsx +188 -0
- package/src/components/ui/appointment-mini-card.tsx +104 -0
- package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
- package/src/components/ui/appointment-upcoming-card.tsx +635 -0
- package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
- package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
- package/src/components/ui/backoffice-connections-chart.tsx +339 -0
- package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
- package/src/components/ui/badge.tsx +12 -6
- package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
- package/src/components/ui/button.tsx +2 -2
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/cash-balance-line-chart.tsx +4 -23
- package/src/components/ui/cashflow-bar-chart.tsx +9 -2
- package/src/components/ui/chart-shared.tsx +4 -11
- package/src/components/ui/chip.tsx +23 -19
- package/src/components/ui/color-picker.tsx +309 -0
- package/src/components/ui/data-table.tsx +117 -83
- package/src/components/ui/date-picker.tsx +42 -37
- package/src/components/ui/dialog.tsx +72 -6
- package/src/components/ui/expense-bar-chart.tsx +11 -2
- package/src/components/ui/financial-cards.tsx +99 -10
- package/src/components/ui/income-bar-chart.tsx +11 -2
- package/src/components/ui/opportunity-card.tsx +10 -39
- package/src/components/ui/opportunity-edit-modals.tsx +98 -36
- package/src/components/ui/opportunity-summary-tab.tsx +548 -232
- package/src/components/ui/page-header.tsx +57 -0
- package/src/components/ui/page-top-bar.tsx +48 -0
- package/src/components/ui/pagination.tsx +171 -22
- package/src/components/ui/pipeline-board.tsx +12 -5
- package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
- package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
- package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
- package/src/components/ui/sidebar-nav.tsx +516 -0
- package/src/components/ui/stepper.tsx +347 -0
- package/src/components/ui/toggle.tsx +4 -4
- package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
- package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
- package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
- package/src/lib/format-currency.ts +44 -0
- package/src/lib/format-date.ts +50 -0
- package/src/lib/opportunity-constants.ts +12 -0
- package/src/lib/typography.ts +11 -11
- package/src/styles/globals.css +36 -34
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +17 -0
- package/dist/chunk-PG6K5XEC.mjs +0 -475
- package/dist/chunk-WA6O6EUR.mjs +0 -1885
- package/dist/chunk-WNGWBVLV.mjs +0 -148
- package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Avatar, AvatarFallback } from "./avatar";
|
|
3
|
+
import { Badge } from "./badge";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { Separator } from "./separator";
|
|
6
|
+
import { Sheet, SheetContent } from "./sheet";
|
|
7
|
+
import {
|
|
8
|
+
AppointmentConfirmAction,
|
|
9
|
+
AppointmentConfirmDialog,
|
|
10
|
+
AppointmentRescheduleDialog,
|
|
11
|
+
} from "./appointment-action-dialogs";
|
|
12
|
+
import type {
|
|
13
|
+
AppointmentMeetingFormat,
|
|
14
|
+
AppointmentStatus,
|
|
15
|
+
AppointmentTimeSlot,
|
|
16
|
+
} from "./appointment-time-slot-picker";
|
|
17
|
+
import {
|
|
18
|
+
AlertCircle,
|
|
19
|
+
Calendar,
|
|
20
|
+
CalendarClock,
|
|
21
|
+
Check,
|
|
22
|
+
CircleUser,
|
|
23
|
+
Clock,
|
|
24
|
+
FileText,
|
|
25
|
+
Mail,
|
|
26
|
+
MapPin,
|
|
27
|
+
Phone,
|
|
28
|
+
RefreshCw,
|
|
29
|
+
Users,
|
|
30
|
+
Video,
|
|
31
|
+
X,
|
|
32
|
+
} from "lucide-react";
|
|
33
|
+
|
|
34
|
+
// Re-export so consumers who import these types from this module still work
|
|
35
|
+
export type {
|
|
36
|
+
AppointmentStatus,
|
|
37
|
+
AppointmentMeetingFormat,
|
|
38
|
+
} from "./appointment-time-slot-picker";
|
|
39
|
+
|
|
40
|
+
export interface AppointmentDetailItem {
|
|
41
|
+
id: string;
|
|
42
|
+
status: AppointmentStatus;
|
|
43
|
+
clientName: string;
|
|
44
|
+
clientAvatarInitials: string;
|
|
45
|
+
/** Client ID — used to pre-select client when rebooking after cancellation */
|
|
46
|
+
clientId?: string;
|
|
47
|
+
date: string;
|
|
48
|
+
timeStart: string;
|
|
49
|
+
timeEnd: string;
|
|
50
|
+
notes?: string;
|
|
51
|
+
/** Reason provided when the appointment was cancelled */
|
|
52
|
+
cancelReason?: string;
|
|
53
|
+
/** How the meeting is conducted */
|
|
54
|
+
meetingFormat?: AppointmentMeetingFormat;
|
|
55
|
+
/** Formatted location string — shown for offline meetings */
|
|
56
|
+
meetingLocation?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type LoanApplicationStatus =
|
|
60
|
+
| "finished"
|
|
61
|
+
| "in-progress"
|
|
62
|
+
| "sent-request"
|
|
63
|
+
| "not-start";
|
|
64
|
+
|
|
65
|
+
/** Extra client profile fields not stored on the appointment itself */
|
|
66
|
+
export interface AppointmentClientProfile {
|
|
67
|
+
phone?: string;
|
|
68
|
+
email?: string;
|
|
69
|
+
accountType?: "Individual" | "Joint";
|
|
70
|
+
loanApplicationStatus?: LoanApplicationStatus;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AppointmentDetailSheetProps {
|
|
74
|
+
appointment: AppointmentDetailItem | undefined;
|
|
75
|
+
open: boolean;
|
|
76
|
+
onOpenChange: (v: boolean) => void;
|
|
77
|
+
clientProfile?: AppointmentClientProfile;
|
|
78
|
+
amSlots: AppointmentTimeSlot[];
|
|
79
|
+
pmSlots: AppointmentTimeSlot[];
|
|
80
|
+
onAccept?: (id: string) => void;
|
|
81
|
+
onDecline?: (id: string) => void;
|
|
82
|
+
onReschedule?: (
|
|
83
|
+
id: string,
|
|
84
|
+
date: Date,
|
|
85
|
+
slot: AppointmentTimeSlot,
|
|
86
|
+
note: string,
|
|
87
|
+
) => void;
|
|
88
|
+
/** Called when "Book a New Appointment" is clicked on a cancelled appointment */
|
|
89
|
+
onBookNew?: (clientId?: string) => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Status config
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
const STATUS_CONFIG: Record<
|
|
97
|
+
AppointmentStatus,
|
|
98
|
+
{
|
|
99
|
+
variant: "warning" | "success" | "destructive" | "info";
|
|
100
|
+
label: string;
|
|
101
|
+
icon: React.ReactNode;
|
|
102
|
+
}
|
|
103
|
+
> = {
|
|
104
|
+
pending: {
|
|
105
|
+
variant: "warning",
|
|
106
|
+
label: "Pending",
|
|
107
|
+
icon: <CalendarClock size={12} />,
|
|
108
|
+
},
|
|
109
|
+
confirmed: {
|
|
110
|
+
variant: "success",
|
|
111
|
+
label: "Confirmed",
|
|
112
|
+
icon: <Check size={12} />,
|
|
113
|
+
},
|
|
114
|
+
cancelled: {
|
|
115
|
+
variant: "destructive",
|
|
116
|
+
label: "Cancelled",
|
|
117
|
+
icon: <X size={12} />,
|
|
118
|
+
},
|
|
119
|
+
rescheduled: {
|
|
120
|
+
variant: "info",
|
|
121
|
+
label: "Rescheduled",
|
|
122
|
+
icon: <RefreshCw size={12} />,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Component
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
export function AppointmentDetailSheet({
|
|
131
|
+
appointment,
|
|
132
|
+
open,
|
|
133
|
+
onOpenChange,
|
|
134
|
+
clientProfile,
|
|
135
|
+
amSlots,
|
|
136
|
+
pmSlots,
|
|
137
|
+
onAccept,
|
|
138
|
+
onDecline,
|
|
139
|
+
onReschedule,
|
|
140
|
+
onBookNew,
|
|
141
|
+
}: AppointmentDetailSheetProps) {
|
|
142
|
+
const [confirmAction, setConfirmAction] =
|
|
143
|
+
React.useState<AppointmentConfirmAction | null>(null);
|
|
144
|
+
const [rescheduleOpen, setRescheduleOpen] = React.useState(false);
|
|
145
|
+
|
|
146
|
+
if (!appointment) return null;
|
|
147
|
+
|
|
148
|
+
const { variant, label, icon } = STATUS_CONFIG[appointment.status];
|
|
149
|
+
const isCancelled = appointment.status === "cancelled";
|
|
150
|
+
const isConfirmed = appointment.status === "confirmed";
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
155
|
+
<SheetContent
|
|
156
|
+
side="right"
|
|
157
|
+
className="flex w-[440px] flex-col gap-0 overflow-y-auto p-0"
|
|
158
|
+
>
|
|
159
|
+
{/* ── Header: avatar + name/badge + action buttons ── */}
|
|
160
|
+
<div className="border-b border-border px-6 py-5">
|
|
161
|
+
<div className="flex items-start gap-4">
|
|
162
|
+
<Avatar className="h-12 w-12 shrink-0">
|
|
163
|
+
<AvatarFallback className="text-base">
|
|
164
|
+
{appointment.clientAvatarInitials}
|
|
165
|
+
</AvatarFallback>
|
|
166
|
+
</Avatar>
|
|
167
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
168
|
+
<p className="text-lg font-semibold leading-tight">
|
|
169
|
+
{appointment.clientName}
|
|
170
|
+
</p>
|
|
171
|
+
<Badge variant={variant} className="w-fit">
|
|
172
|
+
{icon}
|
|
173
|
+
{label}
|
|
174
|
+
</Badge>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Action buttons */}
|
|
179
|
+
<div className="mt-4 flex gap-2">
|
|
180
|
+
{isCancelled ? (
|
|
181
|
+
<Button
|
|
182
|
+
size="sm"
|
|
183
|
+
className="w-full"
|
|
184
|
+
onClick={() => onBookNew?.(appointment.clientId)}
|
|
185
|
+
>
|
|
186
|
+
Book a New Appointment
|
|
187
|
+
</Button>
|
|
188
|
+
) : (
|
|
189
|
+
<>
|
|
190
|
+
<Button
|
|
191
|
+
size="sm"
|
|
192
|
+
disabled={isConfirmed}
|
|
193
|
+
onClick={() => setConfirmAction("accept")}
|
|
194
|
+
className="flex-1 gap-1.5"
|
|
195
|
+
>
|
|
196
|
+
<Check className="h-3.5 w-3.5" />
|
|
197
|
+
Accept
|
|
198
|
+
</Button>
|
|
199
|
+
<Button
|
|
200
|
+
size="sm"
|
|
201
|
+
variant="ghost"
|
|
202
|
+
onClick={() => setConfirmAction("decline")}
|
|
203
|
+
className="flex-1 gap-1.5 text-destructive hover:text-destructive"
|
|
204
|
+
>
|
|
205
|
+
<X className="h-3.5 w-3.5" />
|
|
206
|
+
Decline
|
|
207
|
+
</Button>
|
|
208
|
+
<Button
|
|
209
|
+
size="sm"
|
|
210
|
+
variant="outline"
|
|
211
|
+
onClick={() => setRescheduleOpen(true)}
|
|
212
|
+
className="flex-1 gap-1.5"
|
|
213
|
+
>
|
|
214
|
+
<RefreshCw className="h-3.5 w-3.5" />
|
|
215
|
+
Reschedule
|
|
216
|
+
</Button>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* ── Appointment details ── */}
|
|
223
|
+
<div className="flex flex-col gap-4 px-6 py-5">
|
|
224
|
+
<div className="flex items-center gap-3 text-sm">
|
|
225
|
+
<Calendar className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
226
|
+
<span className="font-medium">{appointment.date}</span>
|
|
227
|
+
</div>
|
|
228
|
+
<div className="flex items-center gap-3 text-sm">
|
|
229
|
+
<Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
230
|
+
<span className="font-medium">
|
|
231
|
+
{appointment.timeStart} – {appointment.timeEnd}
|
|
232
|
+
</span>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Meeting format — for offline, location is shown inline to avoid a second MapPin */}
|
|
236
|
+
{appointment.meetingFormat && (
|
|
237
|
+
<div className="flex items-start gap-3 text-sm">
|
|
238
|
+
{appointment.meetingFormat === "call" && (
|
|
239
|
+
<Phone className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
240
|
+
)}
|
|
241
|
+
{appointment.meetingFormat === "google-meet" && (
|
|
242
|
+
<Video className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
243
|
+
)}
|
|
244
|
+
{appointment.meetingFormat === "offline" && (
|
|
245
|
+
<MapPin className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
246
|
+
)}
|
|
247
|
+
<div className="flex flex-col gap-0.5">
|
|
248
|
+
<span className="font-medium">
|
|
249
|
+
{appointment.meetingFormat === "call" && "Phone Call"}
|
|
250
|
+
{appointment.meetingFormat === "google-meet" &&
|
|
251
|
+
"Google Meet"}
|
|
252
|
+
{appointment.meetingFormat === "offline" && "In Person"}
|
|
253
|
+
</span>
|
|
254
|
+
{/* Location shown below format label — only for offline, avoids duplicate MapPin */}
|
|
255
|
+
{appointment.meetingFormat === "offline" &&
|
|
256
|
+
appointment.meetingLocation && (
|
|
257
|
+
<span className="text-muted-foreground">
|
|
258
|
+
{appointment.meetingLocation}
|
|
259
|
+
</span>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
{/* ── Client profile ── */}
|
|
267
|
+
{clientProfile &&
|
|
268
|
+
(clientProfile.phone !== undefined ||
|
|
269
|
+
clientProfile.email !== undefined ||
|
|
270
|
+
clientProfile.accountType !== undefined ||
|
|
271
|
+
clientProfile.loanApplicationStatus !== undefined) && (
|
|
272
|
+
<>
|
|
273
|
+
<Separator />
|
|
274
|
+
<div className="flex flex-col gap-3 px-6 py-5">
|
|
275
|
+
<p className="text-sm font-semibold">Client Profile</p>
|
|
276
|
+
<div className="flex flex-col gap-2">
|
|
277
|
+
{clientProfile.phone && (
|
|
278
|
+
<div className="flex items-center gap-3 text-sm">
|
|
279
|
+
<Phone className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
280
|
+
<span className="text-muted-foreground">
|
|
281
|
+
{clientProfile.phone}
|
|
282
|
+
</span>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
{clientProfile.email && (
|
|
286
|
+
<div className="flex items-center gap-3 text-sm">
|
|
287
|
+
<Mail className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
288
|
+
<span className="text-muted-foreground">
|
|
289
|
+
{clientProfile.email}
|
|
290
|
+
</span>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
{clientProfile.accountType !== undefined && (
|
|
294
|
+
<div className="flex items-center justify-between text-sm">
|
|
295
|
+
<span className="text-muted-foreground">
|
|
296
|
+
Account type
|
|
297
|
+
</span>
|
|
298
|
+
<div className="flex items-center gap-1.5">
|
|
299
|
+
{clientProfile.accountType === "Joint" ? (
|
|
300
|
+
<Users className="h-3.5 w-3.5 text-muted-foreground" />
|
|
301
|
+
) : (
|
|
302
|
+
<CircleUser className="h-3.5 w-3.5 text-muted-foreground" />
|
|
303
|
+
)}
|
|
304
|
+
<span className="font-medium">
|
|
305
|
+
{clientProfile.accountType}
|
|
306
|
+
</span>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
{clientProfile.loanApplicationStatus !== undefined && (
|
|
311
|
+
<div className="flex items-center justify-between text-sm">
|
|
312
|
+
<span className="text-muted-foreground">
|
|
313
|
+
Loan application
|
|
314
|
+
</span>
|
|
315
|
+
<div className="flex items-center gap-1.5">
|
|
316
|
+
<FileText className="h-3.5 w-3.5 text-muted-foreground" />
|
|
317
|
+
{clientProfile.loanApplicationStatus ===
|
|
318
|
+
"finished" && (
|
|
319
|
+
<Badge variant="success" className="text-[10px]">
|
|
320
|
+
Finished
|
|
321
|
+
</Badge>
|
|
322
|
+
)}
|
|
323
|
+
{clientProfile.loanApplicationStatus ===
|
|
324
|
+
"in-progress" && (
|
|
325
|
+
<Badge variant="warning" className="text-[10px]">
|
|
326
|
+
In Progress
|
|
327
|
+
</Badge>
|
|
328
|
+
)}
|
|
329
|
+
{clientProfile.loanApplicationStatus ===
|
|
330
|
+
"sent-request" && (
|
|
331
|
+
<Badge variant="info" className="text-[10px]">
|
|
332
|
+
Sent Request
|
|
333
|
+
</Badge>
|
|
334
|
+
)}
|
|
335
|
+
{clientProfile.loanApplicationStatus ===
|
|
336
|
+
"not-start" && (
|
|
337
|
+
<Badge variant="secondary" className="text-[10px]">
|
|
338
|
+
Not Started
|
|
339
|
+
</Badge>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
{/* ── Notes ── */}
|
|
350
|
+
{appointment.notes && (
|
|
351
|
+
<>
|
|
352
|
+
<Separator />
|
|
353
|
+
<div className="flex flex-col gap-2 px-6 py-5">
|
|
354
|
+
<div className="flex items-center gap-2">
|
|
355
|
+
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
356
|
+
<p className="text-sm font-semibold">Notes</p>
|
|
357
|
+
</div>
|
|
358
|
+
<p className="text-sm text-muted-foreground">
|
|
359
|
+
{appointment.notes}
|
|
360
|
+
</p>
|
|
361
|
+
</div>
|
|
362
|
+
</>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{/* ── Cancellation Reason ── */}
|
|
366
|
+
{appointment.cancelReason && (
|
|
367
|
+
<>
|
|
368
|
+
<Separator />
|
|
369
|
+
<div className="flex flex-col gap-2 px-6 py-5">
|
|
370
|
+
<div className="flex items-center gap-2">
|
|
371
|
+
<AlertCircle className="h-4 w-4 text-destructive" />
|
|
372
|
+
<p className="text-sm font-semibold">Cancellation Reason</p>
|
|
373
|
+
</div>
|
|
374
|
+
<p className="text-sm text-muted-foreground">
|
|
375
|
+
{appointment.cancelReason}
|
|
376
|
+
</p>
|
|
377
|
+
</div>
|
|
378
|
+
</>
|
|
379
|
+
)}
|
|
380
|
+
</SheetContent>
|
|
381
|
+
</Sheet>
|
|
382
|
+
|
|
383
|
+
{/* Dialogs rendered outside Sheet to avoid z-index issues */}
|
|
384
|
+
<AppointmentConfirmDialog
|
|
385
|
+
open={confirmAction !== null}
|
|
386
|
+
onOpenChange={(v) => !v && setConfirmAction(null)}
|
|
387
|
+
action={confirmAction ?? "accept"}
|
|
388
|
+
clientName={appointment.clientName}
|
|
389
|
+
onConfirm={() => {
|
|
390
|
+
if (confirmAction === "accept") {
|
|
391
|
+
onAccept?.(appointment.id);
|
|
392
|
+
} else {
|
|
393
|
+
onDecline?.(appointment.id);
|
|
394
|
+
}
|
|
395
|
+
setConfirmAction(null);
|
|
396
|
+
onOpenChange(false);
|
|
397
|
+
}}
|
|
398
|
+
/>
|
|
399
|
+
|
|
400
|
+
<AppointmentRescheduleDialog
|
|
401
|
+
open={rescheduleOpen}
|
|
402
|
+
onOpenChange={setRescheduleOpen}
|
|
403
|
+
amSlots={amSlots}
|
|
404
|
+
pmSlots={pmSlots}
|
|
405
|
+
currentDate={appointment.date}
|
|
406
|
+
currentTimeStart={appointment.timeStart}
|
|
407
|
+
currentTimeEnd={appointment.timeEnd}
|
|
408
|
+
onReschedule={(date, slot, note) => {
|
|
409
|
+
onReschedule?.(appointment.id, date, slot, note);
|
|
410
|
+
setRescheduleOpen(false);
|
|
411
|
+
}}
|
|
412
|
+
/>
|
|
413
|
+
</>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Badge } from "./badge";
|
|
2
|
+
import { Button } from "./button";
|
|
3
|
+
import { Input } from "./input";
|
|
4
|
+
import { Separator } from "./separator";
|
|
5
|
+
import { Spinner } from "./spinner";
|
|
6
|
+
import { Check, Copy, Link2, Mail, ShieldCheck, Unlink } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Types
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export type GmailConnectionState = "disconnected" | "connecting" | "connected";
|
|
13
|
+
|
|
14
|
+
export interface GmailConnection {
|
|
15
|
+
connected: boolean;
|
|
16
|
+
email?: string;
|
|
17
|
+
/** ISO date string — "2026-03-15" */
|
|
18
|
+
connectedAt?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AppointmentGmailConnectProps {
|
|
22
|
+
connection: GmailConnection;
|
|
23
|
+
state?: GmailConnectionState;
|
|
24
|
+
onConnect?: () => void;
|
|
25
|
+
onDisconnect?: () => void;
|
|
26
|
+
/** Booking link to display in the calendar link section (connected state only) */
|
|
27
|
+
calendarLink?: string;
|
|
28
|
+
/** Called when the user clicks the copy button next to the calendar link */
|
|
29
|
+
onCopyLink?: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Constants
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const PERMISSIONS = [
|
|
37
|
+
"Read your calendar availability",
|
|
38
|
+
"Send appointment confirmation emails",
|
|
39
|
+
"Create calendar events on your behalf",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Component
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
export function AppointmentGmailConnect({
|
|
47
|
+
connection,
|
|
48
|
+
state = "disconnected",
|
|
49
|
+
onConnect,
|
|
50
|
+
onDisconnect,
|
|
51
|
+
calendarLink,
|
|
52
|
+
onCopyLink,
|
|
53
|
+
}: AppointmentGmailConnectProps) {
|
|
54
|
+
if (state === "connecting") {
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex flex-col items-center gap-4 border border-border bg-card px-6 py-8 text-center">
|
|
57
|
+
<Spinner className="h-6 w-6 text-primary" />
|
|
58
|
+
<div className="flex flex-col gap-1">
|
|
59
|
+
<p className="text-sm font-medium">Connecting to Gmail…</p>
|
|
60
|
+
<p className="text-xs text-muted-foreground">
|
|
61
|
+
Complete the sign-in in the popup window.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (connection.connected) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex flex-col border border-border bg-card">
|
|
71
|
+
{/* Header — mail icon + title/email + badge + connected-since + disconnect */}
|
|
72
|
+
<div className="flex items-center gap-3 px-5 py-4">
|
|
73
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center bg-primary/10">
|
|
74
|
+
<Mail className="h-5 w-5 text-primary" />
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex-1 min-w-0">
|
|
77
|
+
<p className="text-base font-semibold">Gmail Connected</p>
|
|
78
|
+
<p className="truncate text-sm text-muted-foreground">
|
|
79
|
+
{connection.email}
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
<Badge variant="success">
|
|
83
|
+
<Check size={10} />
|
|
84
|
+
Active
|
|
85
|
+
</Badge>
|
|
86
|
+
{connection.connectedAt && (
|
|
87
|
+
<p className="shrink-0 text-sm text-muted-foreground">
|
|
88
|
+
Connected since {connection.connectedAt}
|
|
89
|
+
</p>
|
|
90
|
+
)}
|
|
91
|
+
<Button
|
|
92
|
+
size="sm"
|
|
93
|
+
variant="ghost"
|
|
94
|
+
className="shrink-0 gap-1.5 text-destructive hover:text-destructive"
|
|
95
|
+
onClick={onDisconnect}
|
|
96
|
+
>
|
|
97
|
+
<Unlink className="h-3.5 w-3.5" />
|
|
98
|
+
Disconnect
|
|
99
|
+
</Button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<Separator />
|
|
103
|
+
|
|
104
|
+
{/* Permissions granted */}
|
|
105
|
+
<div className="flex flex-col gap-2 px-5 py-4">
|
|
106
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
107
|
+
Permissions granted
|
|
108
|
+
</p>
|
|
109
|
+
{PERMISSIONS.map((perm) => (
|
|
110
|
+
<div key={perm} className="flex items-center gap-2">
|
|
111
|
+
<ShieldCheck className="h-4 w-4 shrink-0 text-success" />
|
|
112
|
+
<span className="text-sm text-muted-foreground">{perm}</span>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Calendar appointment link — only rendered when a link is available */}
|
|
118
|
+
{calendarLink && (
|
|
119
|
+
<>
|
|
120
|
+
<Separator />
|
|
121
|
+
<div className="flex flex-col gap-2.5 px-5 py-4">
|
|
122
|
+
<div className="flex items-center gap-2">
|
|
123
|
+
<Link2 className="h-4 w-4 text-muted-foreground" />
|
|
124
|
+
<p className="text-sm font-semibold">
|
|
125
|
+
Calendar Appointment Link
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
<p className="text-sm text-muted-foreground">
|
|
129
|
+
Share with clients so they can self-book.
|
|
130
|
+
</p>
|
|
131
|
+
<div className="relative">
|
|
132
|
+
<Input
|
|
133
|
+
id="calendar-link"
|
|
134
|
+
readOnly
|
|
135
|
+
value={calendarLink}
|
|
136
|
+
className="cursor-default select-all pr-10 text-muted-foreground"
|
|
137
|
+
/>
|
|
138
|
+
<Button
|
|
139
|
+
type="button"
|
|
140
|
+
variant="ghost"
|
|
141
|
+
size="icon"
|
|
142
|
+
onClick={onCopyLink}
|
|
143
|
+
className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
|
|
144
|
+
>
|
|
145
|
+
<Copy className="h-4 w-4" />
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Disconnected state
|
|
156
|
+
return (
|
|
157
|
+
<div className="flex flex-col items-center gap-4 border border-border bg-card px-6 py-8 text-center">
|
|
158
|
+
<div className="flex h-12 w-12 items-center justify-center bg-muted">
|
|
159
|
+
<Mail className="h-6 w-6 text-muted-foreground" />
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div className="flex flex-col gap-1.5">
|
|
163
|
+
<p className="text-base font-semibold">Connect your Gmail account</p>
|
|
164
|
+
<p className="max-w-xs text-sm text-muted-foreground">
|
|
165
|
+
Link your Gmail to let clients book appointments and receive
|
|
166
|
+
confirmation emails automatically.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div className="flex w-full max-w-xs flex-col gap-2 text-left">
|
|
171
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
172
|
+
This will allow WealthX to:
|
|
173
|
+
</p>
|
|
174
|
+
{PERMISSIONS.map((perm) => (
|
|
175
|
+
<div key={perm} className="flex items-center gap-2">
|
|
176
|
+
<Check className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
177
|
+
<span className="text-sm text-muted-foreground">{perm}</span>
|
|
178
|
+
</div>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<Button onClick={onConnect} className="gap-2">
|
|
183
|
+
<Mail className="h-4 w-4" />
|
|
184
|
+
Connect Gmail
|
|
185
|
+
</Button>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|