@wealthx/shadcn 1.2.2 → 1.3.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.
- package/.turbo/turbo-build.log +193 -149
- package/CHANGELOG.md +28 -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-VGSESELX.mjs → chunk-5FQIKDKP.mjs} +5 -5
- 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-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-5MEWU56Z.mjs → chunk-DUJTAXMH.mjs} +11 -6
- package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
- package/dist/chunk-EWRB4PAD.mjs +468 -0
- package/dist/{chunk-ZSHYDDRB.mjs → chunk-FAKPBKLT.mjs} +6 -2
- package/dist/{chunk-A6AAWBPF.mjs → chunk-GHC7LLUX.mjs} +13 -4
- package/dist/chunk-HBZLGDIN.mjs +507 -0
- package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
- package/dist/{chunk-CGH4DRNG.mjs → chunk-HVY6KCCF.mjs} +10 -7
- package/dist/chunk-I3RZS7V2.mjs +136 -0
- package/dist/chunk-IAE3F7DR.mjs +1962 -0
- package/dist/{chunk-UT4KJR7V.mjs → chunk-IHMFS7NZ.mjs} +35 -74
- 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-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-MLNEWRWV.mjs +449 -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-Y4QFWRNR.mjs → chunk-PU4YZQXV.mjs} +17 -18
- package/dist/chunk-Q2BGOAMG.mjs +202 -0
- 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-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/components/ui/add-column-modal.js +42 -14
- package/dist/components/ui/add-column-modal.mjs +5 -5
- 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 +530 -0
- package/dist/components/ui/advisor-card.mjs +15 -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 +158 -28
- package/dist/components/ui/color-picker.mjs +3 -1
- package/dist/components/ui/data-table.js +140 -119
- 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/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 +1367 -1263
- package/dist/components/ui/opportunity-edit-modals.mjs +8 -8
- package/dist/components/ui/opportunity-summary-tab.js +2744 -2157
- package/dist/components/ui/opportunity-summary-tab.mjs +14 -14
- 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 +205 -191
- package/dist/components/ui/pipeline-board.mjs +9 -7
- package/dist/components/ui/pipeline-dialogs.js +114 -65
- package/dist/components/ui/pipeline-dialogs.mjs +7 -6
- 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 +426 -191
- package/dist/components/ui/sidebar-nav.mjs +5 -1
- package/dist/components/ui/stage-timeline.js +6 -6
- package/dist/components/ui/stage-timeline.mjs +3 -3
- 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 +12258 -8611
- package/dist/index.mjs +258 -190
- package/dist/styles.css +1 -1
- package/package.json +71 -1
- package/src/components/index.tsx +115 -9
- 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 +284 -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 +4 -2
- package/src/components/ui/data-table.tsx +28 -74
- 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 +213 -157
- 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/styles/globals.css +17 -15
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +14 -0
- package/dist/chunk-S4QRUQNW.mjs +0 -475
- package/dist/chunk-URGMJAE3.mjs +0 -1885
- package/dist/chunk-WNGWBVLV.mjs +0 -148
- package/dist/chunk-ZRSDX6OW.mjs +0 -385
- package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Calendar as CalendarIcon,
|
|
4
|
+
CalendarCheck,
|
|
5
|
+
Clock,
|
|
6
|
+
ExternalLink,
|
|
7
|
+
} from "lucide-react";
|
|
8
|
+
import { Avatar, AvatarFallback } from "./avatar";
|
|
9
|
+
import { Badge } from "./badge";
|
|
10
|
+
import { Button } from "./button";
|
|
11
|
+
import { Calendar as CalendarPicker } from "./calendar";
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogClose,
|
|
15
|
+
DialogContent,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
DialogFooter,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
20
|
+
} from "./dialog";
|
|
21
|
+
import { Label } from "./label";
|
|
22
|
+
import { Separator } from "./separator";
|
|
23
|
+
import { Textarea } from "./textarea";
|
|
24
|
+
import {
|
|
25
|
+
AppointmentSlotSection,
|
|
26
|
+
type AppointmentStatus,
|
|
27
|
+
type AppointmentTimeSlot,
|
|
28
|
+
} from "./appointment-time-slot-picker";
|
|
29
|
+
|
|
30
|
+
// Re-export so consumers who import these types from this module still work
|
|
31
|
+
export type { AppointmentStatus } from "./appointment-time-slot-picker";
|
|
32
|
+
export type { AppointmentTimeSlot as AppointmentUpcomingSlot } from "./appointment-time-slot-picker";
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Types
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** @deprecated Use AppointmentStatus from appointment-time-slot-picker */
|
|
39
|
+
export type AppointmentUpcomingStatus = AppointmentStatus;
|
|
40
|
+
|
|
41
|
+
export interface AppointmentUpcomingAdvisor {
|
|
42
|
+
name: string;
|
|
43
|
+
role: string;
|
|
44
|
+
company: string;
|
|
45
|
+
avatarInitials: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AppointmentUpcomingData {
|
|
49
|
+
status: AppointmentStatus;
|
|
50
|
+
advisor: AppointmentUpcomingAdvisor;
|
|
51
|
+
/** Human-readable appointment type — shown as the card header title */
|
|
52
|
+
appointmentType?: string;
|
|
53
|
+
/** Display date string — e.g. "2026-04-22" */
|
|
54
|
+
date: string;
|
|
55
|
+
/** Display start time — e.g. "10:00 AM" */
|
|
56
|
+
timeStart: string;
|
|
57
|
+
/** Display end time — e.g. "10:30 AM" */
|
|
58
|
+
timeEnd: string;
|
|
59
|
+
/** Optional Google Calendar / iCal link shown on Confirmed appointments */
|
|
60
|
+
calendarLink?: string;
|
|
61
|
+
/** Original date before advisor rescheduled — only set when status is "rescheduled" */
|
|
62
|
+
originalDate?: string;
|
|
63
|
+
originalTimeStart?: string;
|
|
64
|
+
originalTimeEnd?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface AppointmentUpcomingCardProps {
|
|
68
|
+
/** Pass null or undefined to show the empty state with a Book CTA */
|
|
69
|
+
appointment?: AppointmentUpcomingData | null;
|
|
70
|
+
/** AM time slots shown in the reschedule dialog */
|
|
71
|
+
amSlots?: AppointmentTimeSlot[];
|
|
72
|
+
/** PM time slots shown in the reschedule dialog */
|
|
73
|
+
pmSlots?: AppointmentTimeSlot[];
|
|
74
|
+
onBookAppointment?: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Called when the client submits a reschedule request.
|
|
77
|
+
* @param date The preferred date the client chose.
|
|
78
|
+
* @param slot The preferred time slot (undefined when no slot data is provided).
|
|
79
|
+
* @param note Optional note from the client.
|
|
80
|
+
*/
|
|
81
|
+
onReschedule?: (
|
|
82
|
+
date: Date,
|
|
83
|
+
slot: AppointmentTimeSlot | undefined,
|
|
84
|
+
note: string,
|
|
85
|
+
) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Called when the client confirms cancellation.
|
|
88
|
+
* @param reason Optional cancellation reason entered by the client.
|
|
89
|
+
*/
|
|
90
|
+
onCancel?: (reason: string) => void;
|
|
91
|
+
onAcceptReschedule?: () => void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// AppointmentUpcomingCard
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Client-facing upcoming appointment card.
|
|
100
|
+
*
|
|
101
|
+
* Five states:
|
|
102
|
+
* - **Empty** — no appointment; prompts user to book
|
|
103
|
+
* - **Pending** — client booked, waiting for advisor confirmation
|
|
104
|
+
* - **Confirmed** — advisor confirmed; shows Add to Calendar if link provided
|
|
105
|
+
* - **Rescheduled** — advisor proposed a new time; Accept or Request Another Time
|
|
106
|
+
* - **Cancelled** — advisor cancelled; prompts user to rebook
|
|
107
|
+
*/
|
|
108
|
+
export function AppointmentUpcomingCard({
|
|
109
|
+
appointment,
|
|
110
|
+
amSlots = [],
|
|
111
|
+
pmSlots = [],
|
|
112
|
+
onBookAppointment,
|
|
113
|
+
onReschedule,
|
|
114
|
+
onCancel,
|
|
115
|
+
onAcceptReschedule,
|
|
116
|
+
}: AppointmentUpcomingCardProps) {
|
|
117
|
+
// ── Dialog state ─────────────────────────────────────────────────────────────
|
|
118
|
+
const [calendarDialogOpen, setCalendarDialogOpen] = React.useState(false);
|
|
119
|
+
const [acceptDialogOpen, setAcceptDialogOpen] = React.useState(false);
|
|
120
|
+
const [rescheduleDialogOpen, setRescheduleDialogOpen] = React.useState(false);
|
|
121
|
+
const [cancelDialogOpen, setCancelDialogOpen] = React.useState(false);
|
|
122
|
+
|
|
123
|
+
// Reschedule form
|
|
124
|
+
const [rescheduleDate, setRescheduleDate] = React.useState<Date | undefined>(
|
|
125
|
+
new Date(),
|
|
126
|
+
);
|
|
127
|
+
const [rescheduleSlot, setRescheduleSlot] = React.useState<
|
|
128
|
+
AppointmentTimeSlot | undefined
|
|
129
|
+
>(undefined);
|
|
130
|
+
const [rescheduleNote, setRescheduleNote] = React.useState("");
|
|
131
|
+
|
|
132
|
+
// Cancel form
|
|
133
|
+
const [cancelReason, setCancelReason] = React.useState("");
|
|
134
|
+
|
|
135
|
+
const resetRescheduleForm = () => {
|
|
136
|
+
setRescheduleDate(new Date());
|
|
137
|
+
setRescheduleSlot(undefined);
|
|
138
|
+
setRescheduleNote("");
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const hasSlotsData = amSlots.length > 0 || pmSlots.length > 0;
|
|
142
|
+
const canSubmitReschedule = hasSlotsData
|
|
143
|
+
? !!rescheduleDate && !!rescheduleSlot
|
|
144
|
+
: !!rescheduleDate;
|
|
145
|
+
|
|
146
|
+
// ── Empty state ─────────────────────────────────────────────────────────────
|
|
147
|
+
if (!appointment) {
|
|
148
|
+
return (
|
|
149
|
+
<div className="flex flex-col items-center gap-4 border border-border bg-card px-6 py-10 text-center">
|
|
150
|
+
<div className="flex h-12 w-12 items-center justify-center bg-muted">
|
|
151
|
+
<CalendarIcon className="h-6 w-6 text-muted-foreground" />
|
|
152
|
+
</div>
|
|
153
|
+
<div className="flex flex-col gap-1.5">
|
|
154
|
+
<p className="text-base font-semibold">No upcoming appointments</p>
|
|
155
|
+
<p className="text-sm text-muted-foreground">
|
|
156
|
+
Schedule time with your advisor to discuss your financial goals.
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
<Button onClick={onBookAppointment} className="w-full">
|
|
160
|
+
Book an Appointment
|
|
161
|
+
</Button>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const isPending = appointment.status === "pending";
|
|
167
|
+
const isRescheduled = appointment.status === "rescheduled";
|
|
168
|
+
const isCancelled = appointment.status === "cancelled";
|
|
169
|
+
|
|
170
|
+
// ── Status badge ─────────────────────────────────────────────────────────────
|
|
171
|
+
const statusBadge = (() => {
|
|
172
|
+
if (isPending) return <Badge variant="warning">Pending</Badge>;
|
|
173
|
+
if (isRescheduled) return <Badge variant="info">Rescheduled</Badge>;
|
|
174
|
+
if (isCancelled) return <Badge variant="destructive">Cancelled</Badge>;
|
|
175
|
+
return <Badge variant="success">Confirmed</Badge>;
|
|
176
|
+
})();
|
|
177
|
+
|
|
178
|
+
// ── Status notice banner ──────────────────────────────────────────────────────
|
|
179
|
+
const noticeBanner = (() => {
|
|
180
|
+
if (isPending) {
|
|
181
|
+
return (
|
|
182
|
+
<div className="bg-warning/10 px-5 py-3">
|
|
183
|
+
<p className="text-sm text-foreground">
|
|
184
|
+
Your booking request has been sent. Your advisor will confirm the
|
|
185
|
+
appointment shortly.
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (isRescheduled) {
|
|
191
|
+
return (
|
|
192
|
+
<div className="bg-info/10 px-5 py-3">
|
|
193
|
+
<p className="text-sm text-foreground">
|
|
194
|
+
Your advisor has proposed a new time. Please review and respond
|
|
195
|
+
below.
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (isCancelled) {
|
|
201
|
+
return (
|
|
202
|
+
<div className="bg-destructive/10 px-5 py-3">
|
|
203
|
+
<p className="text-sm text-foreground">
|
|
204
|
+
This appointment has been cancelled by your advisor.
|
|
205
|
+
</p>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
})();
|
|
211
|
+
|
|
212
|
+
const headerTitle = appointment.appointmentType
|
|
213
|
+
? appointment.appointmentType.toUpperCase()
|
|
214
|
+
: "UPCOMING APPOINTMENT";
|
|
215
|
+
|
|
216
|
+
// ── Filled state ──────────────────────────────────────────────────────────────
|
|
217
|
+
return (
|
|
218
|
+
<>
|
|
219
|
+
<div className="flex flex-col border border-border bg-card">
|
|
220
|
+
{/* Header */}
|
|
221
|
+
<div className="flex items-center justify-between px-5 py-4">
|
|
222
|
+
<div className="flex items-center gap-2">
|
|
223
|
+
<CalendarCheck className="h-4 w-4 text-muted-foreground" />
|
|
224
|
+
<p className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
|
225
|
+
{headerTitle}
|
|
226
|
+
</p>
|
|
227
|
+
</div>
|
|
228
|
+
{statusBadge}
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<Separator />
|
|
232
|
+
|
|
233
|
+
{/* Advisor info */}
|
|
234
|
+
<div className="flex items-center gap-4 px-5 py-4">
|
|
235
|
+
<Avatar className="h-12 w-12 shrink-0">
|
|
236
|
+
<AvatarFallback className="text-base">
|
|
237
|
+
{appointment.advisor.avatarInitials}
|
|
238
|
+
</AvatarFallback>
|
|
239
|
+
</Avatar>
|
|
240
|
+
<div className="flex flex-col gap-0.5">
|
|
241
|
+
<p className="text-base font-semibold">
|
|
242
|
+
{appointment.advisor.name}
|
|
243
|
+
</p>
|
|
244
|
+
<p className="text-sm text-muted-foreground">
|
|
245
|
+
{appointment.advisor.role}
|
|
246
|
+
</p>
|
|
247
|
+
<p className="text-xs text-muted-foreground">
|
|
248
|
+
{appointment.advisor.company}
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<Separator />
|
|
254
|
+
|
|
255
|
+
{/* Date/time */}
|
|
256
|
+
<div className="flex flex-col gap-4 px-5 py-4">
|
|
257
|
+
{isRescheduled ? (
|
|
258
|
+
<>
|
|
259
|
+
<div className="flex flex-col gap-2">
|
|
260
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
261
|
+
Original time
|
|
262
|
+
</p>
|
|
263
|
+
<div className="flex items-center gap-2.5 text-sm text-muted-foreground/50 line-through">
|
|
264
|
+
<CalendarIcon className="h-4 w-4 shrink-0" />
|
|
265
|
+
<span>{appointment.originalDate ?? appointment.date}</span>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="flex items-center gap-2.5 text-sm text-muted-foreground/50 line-through">
|
|
268
|
+
<Clock className="h-4 w-4 shrink-0" />
|
|
269
|
+
<span>
|
|
270
|
+
{appointment.originalTimeStart ?? appointment.timeStart} –{" "}
|
|
271
|
+
{appointment.originalTimeEnd ?? appointment.timeEnd}
|
|
272
|
+
</span>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
<div className="flex flex-col gap-2">
|
|
276
|
+
<p className="text-xs font-medium uppercase tracking-wide text-info">
|
|
277
|
+
New proposed time
|
|
278
|
+
</p>
|
|
279
|
+
<div className="flex items-center gap-2.5 text-sm text-foreground">
|
|
280
|
+
<CalendarIcon className="h-4 w-4 shrink-0" />
|
|
281
|
+
<span>{appointment.date}</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div className="flex items-center gap-2.5 text-sm text-foreground">
|
|
284
|
+
<Clock className="h-4 w-4 shrink-0" />
|
|
285
|
+
<span>
|
|
286
|
+
{appointment.timeStart} – {appointment.timeEnd}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</>
|
|
291
|
+
) : (
|
|
292
|
+
<>
|
|
293
|
+
<div
|
|
294
|
+
className={`flex items-center gap-2.5 text-sm ${
|
|
295
|
+
isCancelled
|
|
296
|
+
? "text-muted-foreground/50 line-through"
|
|
297
|
+
: "text-muted-foreground"
|
|
298
|
+
}`}
|
|
299
|
+
>
|
|
300
|
+
<CalendarIcon className="h-4 w-4 shrink-0" />
|
|
301
|
+
<span>{appointment.date}</span>
|
|
302
|
+
</div>
|
|
303
|
+
<div
|
|
304
|
+
className={`flex items-center gap-2.5 text-sm ${
|
|
305
|
+
isCancelled
|
|
306
|
+
? "text-muted-foreground/50 line-through"
|
|
307
|
+
: "text-muted-foreground"
|
|
308
|
+
}`}
|
|
309
|
+
>
|
|
310
|
+
<Clock className="h-4 w-4 shrink-0" />
|
|
311
|
+
<span>
|
|
312
|
+
{appointment.timeStart} – {appointment.timeEnd}
|
|
313
|
+
</span>
|
|
314
|
+
</div>
|
|
315
|
+
</>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
{/* Status notice */}
|
|
320
|
+
{noticeBanner && (
|
|
321
|
+
<>
|
|
322
|
+
<Separator />
|
|
323
|
+
{noticeBanner}
|
|
324
|
+
</>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
<Separator />
|
|
328
|
+
|
|
329
|
+
{/* Actions */}
|
|
330
|
+
<div className="flex flex-col gap-2 px-5 py-4">
|
|
331
|
+
{/* Confirmed — add to calendar */}
|
|
332
|
+
{!isPending &&
|
|
333
|
+
!isRescheduled &&
|
|
334
|
+
!isCancelled &&
|
|
335
|
+
appointment.calendarLink && (
|
|
336
|
+
<Button
|
|
337
|
+
className="w-full gap-2"
|
|
338
|
+
onClick={() => setCalendarDialogOpen(true)}
|
|
339
|
+
>
|
|
340
|
+
<ExternalLink className="h-4 w-4" />
|
|
341
|
+
Add to Calendar
|
|
342
|
+
</Button>
|
|
343
|
+
)}
|
|
344
|
+
|
|
345
|
+
{/* Rescheduled — accept or request another time */}
|
|
346
|
+
{isRescheduled && (
|
|
347
|
+
<>
|
|
348
|
+
<Button
|
|
349
|
+
className="w-full"
|
|
350
|
+
onClick={() => setAcceptDialogOpen(true)}
|
|
351
|
+
>
|
|
352
|
+
Accept New Time
|
|
353
|
+
</Button>
|
|
354
|
+
<Button
|
|
355
|
+
variant="outline"
|
|
356
|
+
className="w-full"
|
|
357
|
+
onClick={() => {
|
|
358
|
+
resetRescheduleForm();
|
|
359
|
+
setRescheduleDialogOpen(true);
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
Request Another Time
|
|
363
|
+
</Button>
|
|
364
|
+
</>
|
|
365
|
+
)}
|
|
366
|
+
|
|
367
|
+
{/* Cancelled — rebook */}
|
|
368
|
+
{isCancelled && (
|
|
369
|
+
<Button className="w-full" onClick={onBookAppointment}>
|
|
370
|
+
Book a New Appointment
|
|
371
|
+
</Button>
|
|
372
|
+
)}
|
|
373
|
+
|
|
374
|
+
{/* Pending / Confirmed — reschedule + cancel */}
|
|
375
|
+
{!isRescheduled && !isCancelled && (
|
|
376
|
+
<>
|
|
377
|
+
<Button
|
|
378
|
+
variant="ghost"
|
|
379
|
+
className="w-full"
|
|
380
|
+
onClick={() => {
|
|
381
|
+
resetRescheduleForm();
|
|
382
|
+
setRescheduleDialogOpen(true);
|
|
383
|
+
}}
|
|
384
|
+
>
|
|
385
|
+
Request Reschedule
|
|
386
|
+
</Button>
|
|
387
|
+
<Button
|
|
388
|
+
variant="ghost"
|
|
389
|
+
className="w-full text-destructive hover:text-destructive"
|
|
390
|
+
onClick={() => {
|
|
391
|
+
setCancelReason("");
|
|
392
|
+
setCancelDialogOpen(true);
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
Cancel Booking
|
|
396
|
+
</Button>
|
|
397
|
+
</>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
{/* ── Accept New Time dialog ────────────────────────────────────────── */}
|
|
403
|
+
<Dialog open={acceptDialogOpen} onOpenChange={setAcceptDialogOpen}>
|
|
404
|
+
<DialogContent>
|
|
405
|
+
<DialogHeader>
|
|
406
|
+
<DialogTitle>Accept New Time</DialogTitle>
|
|
407
|
+
<DialogDescription>
|
|
408
|
+
Confirm you want to accept{" "}
|
|
409
|
+
<span className="font-medium text-foreground">
|
|
410
|
+
{appointment.date}
|
|
411
|
+
</span>{" "}
|
|
412
|
+
from{" "}
|
|
413
|
+
<span className="font-medium text-foreground">
|
|
414
|
+
{appointment.timeStart} – {appointment.timeEnd}
|
|
415
|
+
</span>{" "}
|
|
416
|
+
as your new appointment time. Your advisor will be notified.
|
|
417
|
+
</DialogDescription>
|
|
418
|
+
</DialogHeader>
|
|
419
|
+
<DialogFooter>
|
|
420
|
+
<DialogClose render={<Button variant="outline" />}>
|
|
421
|
+
Keep Waiting
|
|
422
|
+
</DialogClose>
|
|
423
|
+
<Button
|
|
424
|
+
onClick={() => {
|
|
425
|
+
onAcceptReschedule?.();
|
|
426
|
+
setAcceptDialogOpen(false);
|
|
427
|
+
}}
|
|
428
|
+
>
|
|
429
|
+
Accept New Time
|
|
430
|
+
</Button>
|
|
431
|
+
</DialogFooter>
|
|
432
|
+
</DialogContent>
|
|
433
|
+
</Dialog>
|
|
434
|
+
|
|
435
|
+
{/* ── Add to Calendar dialog ─────────────────────────────────────────── */}
|
|
436
|
+
<Dialog open={calendarDialogOpen} onOpenChange={setCalendarDialogOpen}>
|
|
437
|
+
<DialogContent>
|
|
438
|
+
<DialogHeader>
|
|
439
|
+
<DialogTitle>Add to Google Calendar</DialogTitle>
|
|
440
|
+
<DialogDescription>
|
|
441
|
+
You'll be taken to Google Calendar to save this appointment.
|
|
442
|
+
Make sure you're signed in to the correct account.
|
|
443
|
+
</DialogDescription>
|
|
444
|
+
</DialogHeader>
|
|
445
|
+
<DialogFooter>
|
|
446
|
+
<DialogClose render={<Button variant="outline" />}>
|
|
447
|
+
Cancel
|
|
448
|
+
</DialogClose>
|
|
449
|
+
<Button
|
|
450
|
+
className="gap-1.5"
|
|
451
|
+
onClick={() => {
|
|
452
|
+
window.open(appointment.calendarLink, "_blank");
|
|
453
|
+
setCalendarDialogOpen(false);
|
|
454
|
+
}}
|
|
455
|
+
>
|
|
456
|
+
<ExternalLink className="h-4 w-4" />
|
|
457
|
+
Open Google Calendar
|
|
458
|
+
</Button>
|
|
459
|
+
</DialogFooter>
|
|
460
|
+
</DialogContent>
|
|
461
|
+
</Dialog>
|
|
462
|
+
|
|
463
|
+
{/* ── Request Reschedule dialog ──────────────────────────────────────── */}
|
|
464
|
+
<Dialog
|
|
465
|
+
open={rescheduleDialogOpen}
|
|
466
|
+
onOpenChange={(next) => {
|
|
467
|
+
if (!next) resetRescheduleForm();
|
|
468
|
+
setRescheduleDialogOpen(next);
|
|
469
|
+
}}
|
|
470
|
+
>
|
|
471
|
+
<DialogContent size="2xl">
|
|
472
|
+
<DialogHeader>
|
|
473
|
+
<DialogTitle>Request Reschedule</DialogTitle>
|
|
474
|
+
<DialogDescription>
|
|
475
|
+
Choose your preferred date and time. Your advisor will review the
|
|
476
|
+
request and confirm a new slot.
|
|
477
|
+
</DialogDescription>
|
|
478
|
+
</DialogHeader>
|
|
479
|
+
|
|
480
|
+
<Separator />
|
|
481
|
+
|
|
482
|
+
{/* Current booking */}
|
|
483
|
+
<div className="flex flex-col gap-1.5">
|
|
484
|
+
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
485
|
+
Current booking
|
|
486
|
+
</p>
|
|
487
|
+
<div className="flex items-center gap-4 border border-border bg-muted/30 px-3 py-2.5 text-sm text-muted-foreground">
|
|
488
|
+
<span className="flex items-center gap-1.5">
|
|
489
|
+
<CalendarIcon className="h-3.5 w-3.5 shrink-0" />
|
|
490
|
+
{appointment.date}
|
|
491
|
+
</span>
|
|
492
|
+
<span className="flex items-center gap-1.5">
|
|
493
|
+
<Clock className="h-3.5 w-3.5 shrink-0" />
|
|
494
|
+
{appointment.timeStart} – {appointment.timeEnd}
|
|
495
|
+
</span>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div className="grid grid-cols-[auto_1fr] items-start gap-5">
|
|
500
|
+
{/* Date picker */}
|
|
501
|
+
<div className="flex flex-col gap-1.5">
|
|
502
|
+
<Label>Preferred date</Label>
|
|
503
|
+
<CalendarPicker
|
|
504
|
+
mode="single"
|
|
505
|
+
selected={rescheduleDate}
|
|
506
|
+
onSelect={(d) => {
|
|
507
|
+
setRescheduleDate(d);
|
|
508
|
+
setRescheduleSlot(undefined);
|
|
509
|
+
}}
|
|
510
|
+
captionLayout="label"
|
|
511
|
+
fromDate={new Date()}
|
|
512
|
+
disabled={{ before: new Date() }}
|
|
513
|
+
className="border border-border"
|
|
514
|
+
/>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
{/* Time slots */}
|
|
518
|
+
<div className="flex flex-col gap-4">
|
|
519
|
+
{hasSlotsData ? (
|
|
520
|
+
rescheduleDate ? (
|
|
521
|
+
<>
|
|
522
|
+
<p className="text-sm font-semibold">Preferred time</p>
|
|
523
|
+
<AppointmentSlotSection
|
|
524
|
+
label="Morning"
|
|
525
|
+
slots={amSlots}
|
|
526
|
+
selectedSlotId={rescheduleSlot?.id}
|
|
527
|
+
onSelect={setRescheduleSlot}
|
|
528
|
+
/>
|
|
529
|
+
<AppointmentSlotSection
|
|
530
|
+
label="Afternoon"
|
|
531
|
+
slots={pmSlots}
|
|
532
|
+
selectedSlotId={rescheduleSlot?.id}
|
|
533
|
+
onSelect={setRescheduleSlot}
|
|
534
|
+
/>
|
|
535
|
+
</>
|
|
536
|
+
) : (
|
|
537
|
+
<div className="flex h-full flex-col items-center justify-center gap-2 py-8 text-center">
|
|
538
|
+
<p className="text-sm font-semibold">Preferred time</p>
|
|
539
|
+
<p className="text-xs text-muted-foreground">
|
|
540
|
+
Pick a date on the left to see available slots.
|
|
541
|
+
</p>
|
|
542
|
+
</div>
|
|
543
|
+
)
|
|
544
|
+
) : null}
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
{/* Note — full width */}
|
|
549
|
+
<div className="flex flex-col gap-1.5">
|
|
550
|
+
<Label htmlFor="reschedule-note">
|
|
551
|
+
Note{" "}
|
|
552
|
+
<span className="font-normal text-muted-foreground">
|
|
553
|
+
(optional)
|
|
554
|
+
</span>
|
|
555
|
+
</Label>
|
|
556
|
+
<Textarea
|
|
557
|
+
id="reschedule-note"
|
|
558
|
+
placeholder="e.g. I have a conflict at this time and would like to move to a later slot…"
|
|
559
|
+
value={rescheduleNote}
|
|
560
|
+
onChange={(e) => setRescheduleNote(e.target.value)}
|
|
561
|
+
className="w-full resize-none"
|
|
562
|
+
rows={2}
|
|
563
|
+
/>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<DialogFooter>
|
|
567
|
+
<DialogClose render={<Button variant="outline" />}>
|
|
568
|
+
Cancel
|
|
569
|
+
</DialogClose>
|
|
570
|
+
<Button
|
|
571
|
+
disabled={!canSubmitReschedule}
|
|
572
|
+
onClick={() => {
|
|
573
|
+
onReschedule?.(rescheduleDate!, rescheduleSlot, rescheduleNote);
|
|
574
|
+
resetRescheduleForm();
|
|
575
|
+
setRescheduleDialogOpen(false);
|
|
576
|
+
}}
|
|
577
|
+
>
|
|
578
|
+
Submit Request
|
|
579
|
+
</Button>
|
|
580
|
+
</DialogFooter>
|
|
581
|
+
</DialogContent>
|
|
582
|
+
</Dialog>
|
|
583
|
+
|
|
584
|
+
{/* ── Cancel Booking dialog ──────────────────────────────────────────── */}
|
|
585
|
+
<Dialog
|
|
586
|
+
open={cancelDialogOpen}
|
|
587
|
+
onOpenChange={(next) => {
|
|
588
|
+
if (!next) setCancelReason("");
|
|
589
|
+
setCancelDialogOpen(next);
|
|
590
|
+
}}
|
|
591
|
+
>
|
|
592
|
+
<DialogContent>
|
|
593
|
+
<DialogHeader>
|
|
594
|
+
<DialogTitle>Cancel Booking</DialogTitle>
|
|
595
|
+
<DialogDescription>
|
|
596
|
+
Are you sure you want to cancel this appointment? Your advisor
|
|
597
|
+
will be notified. This action cannot be undone.
|
|
598
|
+
</DialogDescription>
|
|
599
|
+
</DialogHeader>
|
|
600
|
+
<div className="flex flex-col gap-1.5">
|
|
601
|
+
<Label htmlFor="cancel-reason">
|
|
602
|
+
Reason{" "}
|
|
603
|
+
<span className="font-normal text-muted-foreground">
|
|
604
|
+
(optional)
|
|
605
|
+
</span>
|
|
606
|
+
</Label>
|
|
607
|
+
<Textarea
|
|
608
|
+
id="cancel-reason"
|
|
609
|
+
placeholder="e.g. I need to reschedule due to a conflict…"
|
|
610
|
+
value={cancelReason}
|
|
611
|
+
onChange={(e) => setCancelReason(e.target.value)}
|
|
612
|
+
className="w-full resize-none"
|
|
613
|
+
rows={2}
|
|
614
|
+
/>
|
|
615
|
+
</div>
|
|
616
|
+
<DialogFooter>
|
|
617
|
+
<DialogClose render={<Button variant="outline" />}>
|
|
618
|
+
Keep Booking
|
|
619
|
+
</DialogClose>
|
|
620
|
+
<Button
|
|
621
|
+
variant="destructive"
|
|
622
|
+
onClick={() => {
|
|
623
|
+
onCancel?.(cancelReason);
|
|
624
|
+
setCancelReason("");
|
|
625
|
+
setCancelDialogOpen(false);
|
|
626
|
+
}}
|
|
627
|
+
>
|
|
628
|
+
Cancel Booking
|
|
629
|
+
</Button>
|
|
630
|
+
</DialogFooter>
|
|
631
|
+
</DialogContent>
|
|
632
|
+
</Dialog>
|
|
633
|
+
</>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
@@ -25,7 +25,13 @@ import {
|
|
|
25
25
|
ChartPeriodButton,
|
|
26
26
|
} from "./chart-shared";
|
|
27
27
|
|
|
28
|
-
ChartJS.register(
|
|
28
|
+
ChartJS.register(
|
|
29
|
+
CategoryScale,
|
|
30
|
+
LinearScale,
|
|
31
|
+
BarController,
|
|
32
|
+
BarElement,
|
|
33
|
+
Tooltip,
|
|
34
|
+
);
|
|
29
35
|
|
|
30
36
|
// ---------------------------------------------------------------------------
|
|
31
37
|
// Types
|
|
@@ -219,7 +225,9 @@ export function BackofficeAlertHistoryChart({
|
|
|
219
225
|
style={{ maxWidth: width, fontFamily }}
|
|
220
226
|
>
|
|
221
227
|
<CardHeader className="px-3 sm:px-6">
|
|
222
|
-
<CardTitle className="text-
|
|
228
|
+
<CardTitle className="text-xs font-semibold uppercase tracking-wide">
|
|
229
|
+
{title}
|
|
230
|
+
</CardTitle>
|
|
223
231
|
<CardAction>
|
|
224
232
|
<div className="flex gap-0.5 sm:gap-1">
|
|
225
233
|
{ALERT_PERIODS.map((p) => (
|