medos-sdk 1.1.10 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/client/MedosClient.d.ts +1 -0
  2. package/dist/client/MedosClient.js +7 -0
  3. package/dist/components/AppointmentCalender.js +19 -22
  4. package/dist/components/AppointmentConfirmationStep.d.ts +1 -0
  5. package/dist/components/AppointmentConfirmationStep.js +34 -42
  6. package/dist/components/AppointmentDateTimeModal.d.ts +1 -0
  7. package/dist/components/AppointmentDateTimeModal.js +201 -168
  8. package/dist/components/AppointmentSummaryStep.d.ts +12 -0
  9. package/dist/components/AppointmentSummaryStep.js +168 -0
  10. package/dist/components/BookingOptionStep.d.ts +14 -0
  11. package/dist/components/BookingOptionStep.js +346 -0
  12. package/dist/components/ContactInformationStep.js +10 -4
  13. package/dist/components/ContactPreferenceStep.js +10 -1
  14. package/dist/components/DoctorSelectModal.js +105 -59
  15. package/dist/components/EnquiryForm.js +81 -69
  16. package/dist/components/Icons/CloseIcon.d.ts +9 -0
  17. package/dist/components/Icons/CloseIcon.js +5 -0
  18. package/dist/components/InquiryDetailsStep.js +5 -1
  19. package/dist/components/PatientDetailsStep.js +17 -12
  20. package/dist/components/PatientSelectionStep.d.ts +12 -0
  21. package/dist/components/PatientSelectionStep.js +254 -0
  22. package/dist/components/PhoneVerificationStep.js +1 -1
  23. package/dist/components/SuccessStep.js +1 -1
  24. package/dist/components/appointment-booking/AppointmentCalender.js +200 -60
  25. package/dist/components/appointment-booking/hooks/useAppointmentFlow.d.ts +0 -1
  26. package/dist/components/appointment-booking/hooks/useAppointmentFlow.js +110 -25
  27. package/dist/components/appointment-booking/hooks/useAppointmentState.js +32 -0
  28. package/dist/components/appointment-booking/hooks/useInitializeAddresses.js +0 -1
  29. package/dist/components/appointment-booking/types.d.ts +163 -0
  30. package/dist/components/appointment-booking/types.js +16 -0
  31. package/dist/components/appointment-modal-styles.d.ts +259 -0
  32. package/dist/components/appointment-modal-styles.js +395 -0
  33. package/dist/components/constant.d.ts +2 -0
  34. package/dist/components/constant.js +15 -0
  35. package/dist/components/custom-calendar.js +20 -11
  36. package/dist/components/styles.js +93 -52
  37. package/dist/components/theme-styles.d.ts +5 -4
  38. package/dist/components/theme-styles.js +221 -125
  39. package/dist/components/types.d.ts +3 -1
  40. package/dist/components/types.js +15 -0
  41. package/dist/components/utils.d.ts +3 -0
  42. package/dist/components/utils.js +59 -0
  43. package/dist/components/validation.d.ts +2 -0
  44. package/dist/components/validation.js +41 -0
  45. package/dist/core/theme/index.d.ts +1 -0
  46. package/dist/core/theme/index.js +1 -0
  47. package/dist/core/theme/responsive.d.ts +15 -0
  48. package/dist/core/theme/responsive.js +113 -0
  49. package/dist/core/theme/themes.js +16 -4
  50. package/dist/core/theme/types.d.ts +8 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +1 -0
  53. package/dist/react/ThemeProvider.d.ts +2 -1
  54. package/dist/react/ThemeProvider.js +49 -10
  55. package/dist/services/AppointmentService.d.ts +80 -2
  56. package/dist/services/AppointmentService.js +114 -5
  57. package/dist/services/WorkspaceService.d.ts +58 -3
  58. package/dist/services/WorkspaceService.js +10 -1
  59. package/dist/vanilla/AppointmentCalendarWidget.d.ts +9 -7
  60. package/dist/vanilla/AppointmentCalendarWidget.js +820 -377
  61. package/dist/vanilla/EnquiryFormWidget.d.ts +1 -0
  62. package/dist/vanilla/EnquiryFormWidget.js +25 -43
  63. package/dist/vanilla/client/MedosClient.d.ts +1 -0
  64. package/dist/vanilla/components/AppointmentConfirmationStep.d.ts +1 -0
  65. package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +1 -0
  66. package/dist/vanilla/components/AppointmentSummaryStep.d.ts +12 -0
  67. package/dist/vanilla/components/BookingOptionStep.d.ts +14 -0
  68. package/dist/vanilla/components/Icons/CloseIcon.d.ts +9 -0
  69. package/dist/vanilla/components/PatientSelectionStep.d.ts +12 -0
  70. package/dist/vanilla/components/VanillaCalendar.js +33 -18
  71. package/dist/vanilla/components/VanillaIcons.d.ts +5 -0
  72. package/dist/vanilla/components/VanillaIcons.js +92 -0
  73. package/dist/vanilla/components/VanillaSelect.d.ts +3 -0
  74. package/dist/vanilla/components/VanillaSelect.js +93 -5
  75. package/dist/vanilla/components/appointment-booking/hooks/useAppointmentFlow.d.ts +0 -1
  76. package/dist/vanilla/components/appointment-booking/types.d.ts +163 -0
  77. package/dist/vanilla/components/appointment-modal-styles.d.ts +259 -0
  78. package/dist/vanilla/components/constant.d.ts +2 -0
  79. package/dist/vanilla/components/theme-styles.d.ts +5 -4
  80. package/dist/vanilla/components/types.d.ts +3 -1
  81. package/dist/vanilla/components/utils.d.ts +3 -0
  82. package/dist/vanilla/components/validation.d.ts +2 -0
  83. package/dist/vanilla/core/theme/index.d.ts +1 -0
  84. package/dist/vanilla/core/theme/responsive.d.ts +15 -0
  85. package/dist/vanilla/core/theme/types.d.ts +8 -0
  86. package/dist/vanilla/enquiry-widget.js +373 -52
  87. package/dist/vanilla/index.d.ts +2 -0
  88. package/dist/vanilla/react/ThemeProvider.d.ts +2 -1
  89. package/dist/vanilla/services/AppointmentService.d.ts +80 -2
  90. package/dist/vanilla/services/WorkspaceService.d.ts +58 -3
  91. package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +9 -7
  92. package/dist/vanilla/vanilla/EnquiryFormWidget.d.ts +1 -0
  93. package/dist/vanilla/vanilla/components/VanillaIcons.d.ts +5 -0
  94. package/dist/vanilla/vanilla/components/VanillaSelect.d.ts +3 -0
  95. package/dist/vanilla/widget.css +833 -207
  96. package/dist/vanilla/widget.js +6444 -5676
  97. package/package.json +1 -1
@@ -4,21 +4,17 @@ import { CustomCalendarWithDateSelector } from "./custom-calendar";
4
4
  import { PaymentMethodIcon } from "./Icons/PaymentMethodIcon";
5
5
  import { DateTimeIcon } from "./Icons/Date&TimeIcon";
6
6
  import { ConsultationTypeIcon } from "./Icons/ConsultationType";
7
+ import ChevronLeftIcon from "./Icons/ChevronLeft";
8
+ import ChevronRightIcon from "./Icons/ChevronRight";
7
9
  import { useTheme } from "../react/hooks/useTheme";
8
- const MONTHS = [
9
- "January",
10
- "February",
11
- "March",
12
- "April",
13
- "May",
14
- "June",
15
- "July",
16
- "August",
17
- "September",
18
- "October",
19
- "November",
20
- "December",
21
- ];
10
+ import { useBreakpoint } from "../core/theme/responsive";
11
+ import { MONTHS, WEEKDAYS } from "./constant";
12
+ import { getMobileStyles, getDesktopStyles } from "./appointment-modal-styles";
13
+ const formatDateCard = (date) => ({
14
+ month: MONTHS[date.getMonth()],
15
+ day: date.getDate(),
16
+ weekday: WEEKDAYS[date.getDay()],
17
+ });
22
18
  const getOrdinalSuffix = (day) => {
23
19
  if (day > 3 && day < 21)
24
20
  return "th";
@@ -33,40 +29,169 @@ const getOrdinalSuffix = (day) => {
33
29
  return "th";
34
30
  }
35
31
  };
36
- const formatDate = (date) => {
32
+ const formatDesktopDate = (date) => {
37
33
  const day = date.getDate();
38
34
  const month = MONTHS[date.getMonth()];
39
35
  const year = date.getFullYear();
40
36
  return `${day}${getOrdinalSuffix(day)} ${month} ${year}`;
41
37
  };
42
- export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, selectedDate: initialSelectedDate, selectedSlot: initialSelectedSlot, consultationMode: initialConsultationMode, onCancel, onContinue, onDateChange, }) => {
38
+ export const AppointmentDateTimeModal = (props) => {
39
+ const theme = useTheme();
40
+ const breakpoint = useBreakpoint(theme);
41
+ const isMobile = breakpoint === "mobile";
42
+ if (isMobile) {
43
+ return _jsx(MobileAppointmentModal, { ...props });
44
+ }
45
+ return _jsx(DesktopAppointmentModal, { ...props });
46
+ };
47
+ const MobileAppointmentModal = ({ onlineFee, offlineFee, slots, selectedDate: initialSelectedDate, selectedSlot: initialSelectedSlot, consultationMode: initialConsultationMode, onCancel, onContinue, onDateChange, initialStep = "consultation", }) => {
43
48
  const theme = useTheme();
49
+ const breakpoint = useBreakpoint(theme);
50
+ const [currentStep, setCurrentStep] = useState(initialStep);
44
51
  const [mode, setMode] = useState(initialConsultationMode || "OFFLINE");
45
52
  const [consultationCharge, setConsultationCharge] = useState("");
46
53
  const [paymentMode, setPaymentMode] = useState("CASH");
47
54
  const [selectedDate, setSelectedDate] = useState(initialSelectedDate || new Date());
48
55
  const [selectedSlot, setSelectedSlot] = useState(initialSelectedSlot || null);
56
+ const [currentDateIndex, setCurrentDateIndex] = useState(0);
49
57
  useEffect(() => {
50
- if (initialSelectedDate) {
51
- setSelectedDate(initialSelectedDate);
58
+ if (!initialConsultationMode && initialStep === "consultation") {
59
+ setCurrentStep("consultation");
60
+ }
61
+ }, [initialConsultationMode, initialStep]);
62
+ const availableDates = useMemo(() => {
63
+ const dates = [];
64
+ const today = new Date();
65
+ for (let i = 0; i < 30; i++) {
66
+ const date = new Date(today);
67
+ date.setDate(today.getDate() + i);
68
+ dates.push(date);
69
+ }
70
+ return dates;
71
+ }, []);
72
+ const visibleCount = 4;
73
+ const visibleDates = useMemo(() => {
74
+ return availableDates.slice(currentDateIndex, currentDateIndex + visibleCount);
75
+ }, [availableDates, currentDateIndex, visibleCount]);
76
+ const canScrollLeft = currentDateIndex > 0;
77
+ const canScrollRight = currentDateIndex + visibleCount < availableDates.length;
78
+ const scrollLeft = () => {
79
+ if (canScrollLeft)
80
+ setCurrentDateIndex(Math.max(0, currentDateIndex - 1));
81
+ };
82
+ const scrollRight = () => {
83
+ if (canScrollRight) {
84
+ const maxIndex = availableDates.length - visibleCount;
85
+ setCurrentDateIndex(Math.min(maxIndex, currentDateIndex + 1));
52
86
  }
87
+ };
88
+ useEffect(() => {
89
+ const charge = mode === "ONLINE" && onlineFee
90
+ ? String(onlineFee)
91
+ : mode === "OFFLINE" && offlineFee
92
+ ? String(offlineFee)
93
+ : "";
94
+ setConsultationCharge(charge);
95
+ }, [mode, onlineFee, offlineFee]);
96
+ const handleContinueFromConsultation = () => setCurrentStep("datetime");
97
+ const handleBackFromDateTime = () => setCurrentStep("consultation");
98
+ const handleDateSelect = useCallback((date) => {
99
+ const dateChanged = date.toDateString() !== selectedDate.toDateString();
100
+ setSelectedDate(date);
101
+ if (dateChanged)
102
+ setSelectedSlot(null);
103
+ onDateChange?.(date);
104
+ }, [selectedDate, onDateChange]);
105
+ const handleFinalContinue = useCallback(() => {
106
+ if (!selectedSlot)
107
+ return;
108
+ onContinue(mode, selectedDate, selectedSlot, consultationCharge, paymentMode);
109
+ }, [
110
+ mode,
111
+ selectedDate,
112
+ selectedSlot,
113
+ consultationCharge,
114
+ paymentMode,
115
+ onContinue,
116
+ ]);
117
+ const isSlotSelected = useCallback((slot) => selectedSlot?.start === slot.start && selectedSlot?.end === slot.end, [selectedSlot]);
118
+ const STYLES = getMobileStyles(theme, breakpoint);
119
+ if (currentStep === "consultation") {
120
+ return (_jsxs("div", { style: STYLES.container, children: [_jsx("div", { style: STYLES.header, children: _jsx("h2", { style: STYLES.title, children: "New Appointment" }) }), _jsxs("div", { style: STYLES.section, children: [_jsxs("h3", { style: STYLES.sectionTitle, children: ["Consultation Mode ", _jsx("span", { style: STYLES.required, children: "*" })] }), _jsx("div", { style: STYLES.modeContainer, children: ["ONLINE", "OFFLINE"].map((item) => {
121
+ const disabled = (item === "ONLINE" && (!onlineFee || onlineFee <= 0)) ||
122
+ (item === "OFFLINE" && (!offlineFee || offlineFee <= 0));
123
+ return (_jsxs("label", { style: STYLES.modeOption, children: [_jsx("input", { type: "radio", name: "mode", value: item, checked: mode === item, disabled: disabled, onChange: () => !disabled && setMode(item), style: STYLES.radioInput }), item === "ONLINE"
124
+ ? "Online Consultation"
125
+ : "Offline Consultation"] }, item));
126
+ }) })] }), _jsxs("div", { style: STYLES.section, children: [_jsx("h3", { style: STYLES.sectionTitle, children: "Consultation Charges" }), _jsxs("div", { style: STYLES.chargeAmount, children: ["\u20B9", consultationCharge] })] }), _jsxs("div", { style: STYLES.section, children: [_jsxs("h3", { style: STYLES.sectionTitle, children: ["Preferred Mode of Payment ", _jsx("span", { style: STYLES.required, children: "*" })] }), _jsx("div", { style: STYLES.paymentOption, children: mode === "ONLINE" ? "Online Consultation" : "Offline Consultation" })] }), _jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backButton, onClick: onCancel, children: "Back" }), _jsx("button", { style: STYLES.nextButton, onClick: handleContinueFromConsultation, disabled: !consultationCharge, children: "Next" })] })] }));
127
+ }
128
+ return (_jsxs("div", { style: STYLES.container, children: [_jsx("div", { style: STYLES.header, children: _jsx("h2", { style: STYLES.title, children: "Date & Time" }) }), _jsxs("div", { style: STYLES.dateSection, children: [_jsxs("div", { style: STYLES.monthNavigationContainer, children: [_jsx("button", { onClick: scrollLeft, style: {
129
+ ...STYLES.navButton,
130
+ opacity: canScrollLeft ? 1 : 0.3,
131
+ cursor: canScrollLeft ? "pointer" : "not-allowed",
132
+ }, children: _jsx(ChevronLeftIcon, {}) }), _jsx("div", { style: STYLES.monthYearDisplay, children: selectedDate.toLocaleDateString("en-US", {
133
+ month: "long",
134
+ year: "numeric",
135
+ }) }), _jsx("button", { onClick: scrollRight, style: {
136
+ ...STYLES.navButton,
137
+ opacity: canScrollRight ? 1 : 0.3,
138
+ cursor: canScrollRight ? "pointer" : "not-allowed",
139
+ }, children: _jsx(ChevronRightIcon, {}) })] }), _jsx("div", { style: STYLES.dateCardsContainer, children: visibleDates.map((date, index) => {
140
+ const { month, day, weekday } = formatDateCard(date);
141
+ const isSelected = date.toDateString() === selectedDate.toDateString();
142
+ return (_jsxs("div", { style: {
143
+ ...STYLES.dateCard,
144
+ ...(isSelected ? STYLES.dateCardSelected : {}),
145
+ }, onClick: () => handleDateSelect(date), children: [_jsx("div", { style: STYLES.dateCardWeekday, children: weekday }), _jsx("div", { style: STYLES.dateCardDay, children: day })] }, `${date.getTime()}-${index}`));
146
+ }) })] }), _jsx("div", { style: STYLES.slotsContainer, children: slots.length === 0 ? (_jsx("p", { style: STYLES.noSlots, children: "No available slots" })) : (_jsx("div", { style: STYLES.slotGrid, children: slots.map((slot) => {
147
+ const startTime = new Date(slot.start).toLocaleTimeString([], {
148
+ hour: "2-digit",
149
+ minute: "2-digit",
150
+ });
151
+ const endTime = new Date(slot.end).toLocaleTimeString([], {
152
+ hour: "2-digit",
153
+ minute: "2-digit",
154
+ });
155
+ const selected = isSlotSelected(slot);
156
+ return (_jsxs("button", { onClick: () => setSelectedSlot(slot), onTouchStart: (e) => {
157
+ if (!selected) {
158
+ e.currentTarget.style.transform = "scale(0.98)";
159
+ }
160
+ }, onTouchEnd: (e) => {
161
+ if (!selected) {
162
+ e.currentTarget.style.transform = "scale(1)";
163
+ }
164
+ }, style: {
165
+ ...STYLES.slotButton,
166
+ ...(selected ? STYLES.slotButtonSelected : {}),
167
+ }, children: [startTime, " - ", endTime] }, slot.id));
168
+ }) })) }), _jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backButton, onClick: handleBackFromDateTime, children: "Back" }), _jsx("button", { style: STYLES.nextButton, onClick: handleFinalContinue, disabled: !selectedSlot, children: "Next" })] })] }));
169
+ };
170
+ const DesktopAppointmentModal = ({ onlineFee, offlineFee, slots, selectedDate: initialSelectedDate, selectedSlot: initialSelectedSlot, consultationMode: initialConsultationMode, onCancel, onContinue, onDateChange, }) => {
171
+ const theme = useTheme();
172
+ const breakpoint = useBreakpoint(theme);
173
+ const [mode, setMode] = useState(initialConsultationMode || "OFFLINE");
174
+ const [consultationCharge, setConsultationCharge] = useState("");
175
+ const [paymentMode, setPaymentMode] = useState("CASH");
176
+ const [selectedDate, setSelectedDate] = useState(initialSelectedDate || new Date());
177
+ const [selectedSlot, setSelectedSlot] = useState(initialSelectedSlot || null);
178
+ useEffect(() => {
179
+ if (initialSelectedDate)
180
+ setSelectedDate(initialSelectedDate);
53
181
  }, [initialSelectedDate]);
54
182
  useEffect(() => {
55
- if (initialSelectedSlot) {
183
+ if (initialSelectedSlot)
56
184
  setSelectedSlot(initialSelectedSlot);
57
- }
58
185
  }, [initialSelectedSlot]);
59
186
  useEffect(() => {
60
- if (initialConsultationMode) {
187
+ if (initialConsultationMode)
61
188
  setMode(initialConsultationMode);
62
- }
63
189
  }, [initialConsultationMode]);
64
190
  const handleDateSelect = useCallback((date) => {
65
191
  const dateChanged = date.toDateString() !== selectedDate.toDateString();
66
192
  setSelectedDate(date);
67
- if (dateChanged) {
193
+ if (dateChanged)
68
194
  setSelectedSlot(null);
69
- }
70
195
  onDateChange?.(date);
71
196
  }, [selectedDate, onDateChange]);
72
197
  useEffect(() => {
@@ -90,149 +215,57 @@ export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, selecte
90
215
  onContinue,
91
216
  ]);
92
217
  const isSlotSelected = useCallback((slot) => selectedSlot?.start === slot.start && selectedSlot?.end === slot.end, [selectedSlot]);
93
- const formattedDate = useMemo(() => formatDate(selectedDate), [selectedDate]);
218
+ const formattedDate = useMemo(() => formatDesktopDate(selectedDate), [selectedDate]);
94
219
  const isFormValid = selectedDate && selectedSlot && consultationCharge;
95
- const STYLES = getStyles(theme);
96
- return (_jsxs("div", { style: STYLES.modalWrapper, children: [_jsx(ConsultationTypeSection, { mode: mode, onlineFee: onlineFee, offlineFee: offlineFee, onModeChange: setMode, STYLES: STYLES }), _jsx(ChargesSection, { charge: consultationCharge, STYLES: STYLES }), _jsx(DateTimeSection, { selectedDate: selectedDate, formattedDate: formattedDate, slots: slots, selectedSlot: selectedSlot, onDateSelect: handleDateSelect, onSlotSelect: setSelectedSlot, isSlotSelected: isSlotSelected, STYLES: STYLES }), _jsx(FooterSection, { onCancel: onCancel, onContinue: handleContinue, isValid: !!isFormValid, STYLES: STYLES })] }));
220
+ const STYLES = getDesktopStyles(theme, breakpoint);
221
+ return (_jsxs("div", { style: STYLES.modalWrapper, children: [_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(ConsultationTypeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Consultation Type" })] }), _jsxs("div", { style: STYLES.sectionBody, children: [_jsx("p", { style: STYLES.label, children: "Consultation Mode" }), _jsx("div", { style: STYLES.modeContainer, children: ["ONLINE", "OFFLINE"].map((item) => {
222
+ const disabled = (item === "ONLINE" && (!onlineFee || onlineFee <= 0)) ||
223
+ (item === "OFFLINE" && (!offlineFee || offlineFee <= 0));
224
+ return (_jsxs("label", { style: { ...STYLES.radioLabel, opacity: disabled ? 0.5 : 1 }, children: [_jsx("input", { type: "radio", name: "mode", value: item, checked: mode === item, disabled: disabled, onChange: () => !disabled && setMode(item), style: STYLES.radioInput }), item === "ONLINE" ? "Online" : "Offline"] }, item));
225
+ }) })] })] }), _jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(PaymentMethodIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Charges & Mode of Payment" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("span", { style: STYLES.rupee, children: ["\u20B9 ", consultationCharge] }) })] }), _jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(DateTimeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Date & Time" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("div", { style: STYLES.dateTimeContainer, children: [_jsx("div", { style: STYLES.calendarBox, children: _jsx(CustomCalendarWithDateSelector, { selectedDate: selectedDate, onSelect: handleDateSelect, pastDisabled: true }) }), _jsxs("div", { style: STYLES.timesContainer, children: [_jsxs("p", { style: STYLES.timesLabel, children: ["Available times ", _jsx("br", {}), _jsx("span", { style: STYLES.dateLabel, children: formattedDate })] }), slots.length === 0 ? (_jsx("p", { style: STYLES.noSlots, children: "No available slots" })) : (_jsx("div", { style: STYLES.slotGrid, children: slots.map((slot) => {
226
+ const selected = isSlotSelected(slot);
227
+ return (_jsxs("button", { onClick: () => setSelectedSlot(slot), onMouseEnter: (e) => {
228
+ if (!selected) {
229
+ e.currentTarget.style.borderColor =
230
+ theme.colors.secondary;
231
+ e.currentTarget.style.backgroundColor = `${theme.colors.secondary}08`;
232
+ e.currentTarget.style.transform =
233
+ "translateY(-2px)";
234
+ e.currentTarget.style.boxShadow = `0 4px 12px ${theme.colors.secondary}15`;
235
+ }
236
+ }, onMouseLeave: (e) => {
237
+ if (!selected) {
238
+ e.currentTarget.style.borderColor =
239
+ theme.colors.border;
240
+ e.currentTarget.style.backgroundColor =
241
+ theme.colors.surface;
242
+ e.currentTarget.style.transform = "translateY(0)";
243
+ e.currentTarget.style.boxShadow =
244
+ "0 1px 3px rgba(0, 0, 0, 0.04)";
245
+ }
246
+ }, style: {
247
+ ...STYLES.slotButton,
248
+ background: selected
249
+ ? theme.colors.secondary
250
+ : theme.colors.surface,
251
+ color: selected
252
+ ? theme.colors.textOnSecondary
253
+ : theme.colors.text,
254
+ borderColor: selected
255
+ ? theme.colors.secondary
256
+ : theme.colors.border,
257
+ boxShadow: selected
258
+ ? `0 4px 12px ${theme.colors.secondary}30, 0 2px 6px ${theme.colors.secondary}20`
259
+ : "0 1px 3px rgba(0, 0, 0, 0.04)",
260
+ transform: selected
261
+ ? "translateY(-1px)"
262
+ : "translateY(0)",
263
+ }, children: [new Date(slot.start).toLocaleTimeString([], {
264
+ hour: "2-digit",
265
+ minute: "2-digit",
266
+ }), " ", "-", " ", new Date(slot.end).toLocaleTimeString([], {
267
+ hour: "2-digit",
268
+ minute: "2-digit",
269
+ })] }, slot.id));
270
+ }) }))] })] }) })] }), _jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backBtn, onClick: onCancel, children: "Back" }), _jsx("button", { style: { ...STYLES.continueBtn, opacity: isFormValid ? 1 : 0.6 }, onClick: handleContinue, disabled: !isFormValid, children: "Continue" })] })] }));
97
271
  };
98
- const ConsultationTypeSection = ({ mode, onlineFee, offlineFee, onModeChange, STYLES }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(ConsultationTypeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Consultation Type" })] }), _jsxs("div", { style: STYLES.sectionBody, children: [_jsx("p", { style: STYLES.label, children: "Consultation Mode" }), _jsx("div", { style: STYLES.modeContainer, children: ["ONLINE", "OFFLINE"].map((item) => {
99
- const disabled = (item === "ONLINE" && (!onlineFee || onlineFee <= 0)) ||
100
- (item === "OFFLINE" && (!offlineFee || offlineFee <= 0));
101
- return (_jsxs("label", { style: { ...STYLES.radioLabel, opacity: disabled ? 0.5 : 1 }, children: [_jsx("input", { type: "radio", name: "mode", value: item, checked: mode === item, disabled: disabled, onChange: () => !disabled && onModeChange(item), style: STYLES.radioInput }), item === "ONLINE" ? "Online" : "Offline"] }, item));
102
- }) })] })] }));
103
- const ChargesSection = ({ charge, STYLES }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(PaymentMethodIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Charges & Mode of Payment" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("span", { style: STYLES.rupee, children: ["\u20B9 ", charge] }) })] }));
104
- const DateTimeSection = ({ selectedDate, formattedDate, slots, selectedSlot, onDateSelect, onSlotSelect, isSlotSelected, STYLES, }) => (_jsxs("div", { style: STYLES.sectionCard, children: [_jsxs("div", { style: STYLES.sectionHeader, children: [_jsx(DateTimeIcon, {}), _jsx("span", { style: STYLES.sectionTitle, children: "Date & Time" })] }), _jsx("div", { style: STYLES.sectionBody, children: _jsxs("div", { style: STYLES.dateTimeContainer, children: [_jsx("div", { style: STYLES.calendarBox, children: _jsx(CustomCalendarWithDateSelector, { selectedDate: selectedDate, onSelect: onDateSelect, pastDisabled: true }) }), _jsxs("div", { style: STYLES.timesContainer, children: [_jsxs("p", { style: STYLES.timesLabel, children: ["Available times ", _jsx("br", {}), _jsx("span", { style: STYLES.dateLabel, children: formattedDate })] }), slots.length === 0 ? (_jsx("p", { style: STYLES.noSlots, children: "No available slots" })) : (_jsx("div", { style: STYLES.slotGrid, children: slots.map((slot) => (_jsx(SlotButton, { slot: slot, isSelected: isSlotSelected(slot), onSelect: onSlotSelect }, slot.id))) }))] })] }) })] }));
105
- const SlotButton = ({ slot, isSelected, onSelect }) => {
106
- const startTime = useMemo(() => new Date(slot.start).toLocaleTimeString([], {
107
- hour: "2-digit",
108
- minute: "2-digit",
109
- }), [slot.start]);
110
- const theme = useTheme();
111
- const STYLES = getStyles(theme);
112
- return (_jsx("button", { onClick: () => onSelect(slot), style: {
113
- ...STYLES.slotButton,
114
- background: isSelected
115
- ? theme.colors.secondary
116
- : theme.colors.background,
117
- color: isSelected
118
- ? theme.colors.textOnSecondary
119
- : theme.colors.secondary,
120
- borderColor: theme.colors.secondary,
121
- }, children: startTime }));
122
- };
123
- const FooterSection = ({ onCancel, onContinue, isValid, STYLES }) => (_jsxs("div", { style: STYLES.footer, children: [_jsx("button", { style: STYLES.backBtn, onClick: onCancel, children: "Back" }), _jsx("button", { style: { ...STYLES.continueBtn, opacity: isValid ? 1 : 0.6 }, onClick: onContinue, disabled: !isValid, children: "Continue" })] }));
124
- const getStyles = (theme) => ({
125
- modalWrapper: {
126
- backgroundColor: theme.colors.background,
127
- borderRadius: theme.radii.lg,
128
- padding: "20px 24px 24px 24px",
129
- maxWidth: 800,
130
- margin: "0 auto",
131
- fontFamily: theme.typography.fontFamily,
132
- color: theme.colors.text,
133
- boxSizing: "border-box",
134
- },
135
- sectionCard: {
136
- border: `1px solid ${theme.colors.border}`,
137
- borderRadius: theme.radii.lg,
138
- marginBottom: 20,
139
- position: "relative",
140
- },
141
- sectionHeader: {
142
- background: theme.colors.primary,
143
- color: theme.colors.textOnPrimary,
144
- borderBottom: `1px solid ${theme.colors.border}`,
145
- padding: "12px 16px",
146
- display: "flex",
147
- alignItems: "center",
148
- gap: 8,
149
- },
150
- sectionTitle: { fontSize: 15, fontWeight: 600 },
151
- sectionBody: { padding: 16, position: "relative" },
152
- label: {
153
- fontSize: 14,
154
- fontWeight: 500,
155
- marginBottom: 4,
156
- display: "block",
157
- },
158
- modeContainer: {
159
- display: "flex",
160
- gap: 24,
161
- marginTop: 4,
162
- },
163
- radioLabel: {
164
- display: "flex",
165
- alignItems: "center",
166
- gap: 8,
167
- cursor: "pointer",
168
- fontSize: 14,
169
- },
170
- radioInput: {
171
- width: 18,
172
- height: 18,
173
- accentColor: theme.colors.secondary,
174
- cursor: "pointer",
175
- },
176
- rupee: {
177
- fontWeight: 600,
178
- fontSize: 16,
179
- marginRight: 6,
180
- },
181
- dateTimeContainer: { display: "flex", gap: 24 },
182
- calendarBox: {
183
- border: `1px solid ${theme.colors.border}`,
184
- borderRadius: theme.radii.lg,
185
- padding: 8,
186
- },
187
- timesContainer: { flexGrow: 1 },
188
- timesLabel: { fontWeight: 500 },
189
- dateLabel: {
190
- color: theme.colors.textSecondary,
191
- fontSize: 13,
192
- },
193
- noSlots: {
194
- color: theme.colors.textSecondary,
195
- fontSize: 14,
196
- marginTop: 8,
197
- },
198
- slotGrid: {
199
- display: "grid",
200
- gridTemplateColumns: "repeat(auto-fit,minmax(100px,1fr))",
201
- gap: 8,
202
- marginTop: 12,
203
- },
204
- slotButton: {
205
- borderRadius: theme.radii.md,
206
- padding: "8px 10px",
207
- border: `1px solid ${theme.colors.secondary}`,
208
- fontSize: 13,
209
- fontWeight: 500,
210
- cursor: "pointer",
211
- transition: "0.2s",
212
- },
213
- footer: {
214
- display: "flex",
215
- justifyContent: "flex-end",
216
- alignItems: "center",
217
- marginTop: 20,
218
- gap: 12,
219
- },
220
- backBtn: {
221
- border: `1px solid ${theme.colors.primary}`,
222
- background: theme.colors.background,
223
- color: theme.colors.primary,
224
- borderRadius: theme.radii.md,
225
- fontWeight: 600,
226
- padding: "8px 20px",
227
- cursor: "pointer",
228
- },
229
- continueBtn: {
230
- background: theme.colors.secondary,
231
- color: theme.colors.textOnSecondary,
232
- border: "none",
233
- borderRadius: theme.radii.md,
234
- fontWeight: 600,
235
- padding: "8px 20px",
236
- cursor: "pointer",
237
- },
238
- });
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { AppointmentState } from "./appointment-booking/types";
3
+ import { Doctor, AddressItem } from "../services/AppointmentService";
4
+ export interface AppointmentSummaryStepProps {
5
+ state: AppointmentState;
6
+ selectedDoctor: Doctor | null;
7
+ selectedAddress: AddressItem | null;
8
+ onBack: () => void;
9
+ onConfirm: () => void;
10
+ }
11
+ export declare const AppointmentSummaryStep: React.FC<AppointmentSummaryStepProps>;
12
+ export default AppointmentSummaryStep;
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useTheme } from "../react/hooks/useTheme";
3
+ import { useBreakpoint } from "../core/theme/responsive";
4
+ import { formatDate, formatTime, calculateDuration } from "./utils";
5
+ export const AppointmentSummaryStep = ({ state, selectedDoctor, selectedAddress, onBack, onConfirm, }) => {
6
+ const theme = useTheme();
7
+ const breakpoint = useBreakpoint(theme);
8
+ const styles = getStyles(theme, breakpoint);
9
+ const usingSessionPack = state.bookingOptionType === "session-pack" && state.selectedSessionPack;
10
+ const usingNewPackage = state.bookingOptionType === "explore-packages" && state.selectedNewPackage;
11
+ const getDuration = () => {
12
+ if (state.selectedSlot) {
13
+ return calculateDuration(state.selectedSlot.start, state.selectedSlot.end);
14
+ }
15
+ return 60;
16
+ };
17
+ const getPaymentInfo = () => {
18
+ if (usingSessionPack) {
19
+ return {
20
+ type: "Session Pack",
21
+ name: state.selectedSessionPack?.name || "Session Pack",
22
+ amount: "Prepaid",
23
+ remaining: `${(state.selectedSessionPack?.remainingSessions || 1) - 1} sessions remaining after this appointment`,
24
+ };
25
+ }
26
+ else if (usingNewPackage) {
27
+ return {
28
+ type: "New Package",
29
+ name: state.selectedNewPackage?.name || "Package",
30
+ amount: `₹${state.selectedNewPackage?.price?.toLocaleString() || 0}`,
31
+ remaining: `${state.selectedNewPackage?.totalSessions ||
32
+ state.selectedNewPackage?.sessions ||
33
+ 1} sessions included`,
34
+ };
35
+ }
36
+ else {
37
+ return {
38
+ type: "Single Appointment",
39
+ name: state.consultationMode === "ONLINE"
40
+ ? "Online Consultation"
41
+ : "In-Person Visit",
42
+ amount: state.consultationCharge
43
+ ? `₹${state.consultationCharge}`
44
+ : "Pay at clinic",
45
+ remaining: null,
46
+ };
47
+ }
48
+ };
49
+ const paymentInfo = getPaymentInfo();
50
+ const patient = state.selectedPatient;
51
+ return (_jsxs("div", { style: styles.container, children: [_jsx("h3", { style: styles.title, children: "Appointment Summary" }), _jsx("p", { style: styles.subtitle, children: "Please review your appointment details before confirming" }), _jsxs("div", { style: styles.section, children: [_jsx("h4", { style: styles.sectionTitle, children: "\uD83D\uDC64 Patient Information" }), _jsxs("div", { style: styles.summaryCard, children: [_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Name" }), _jsx("span", { style: styles.value, children: patient
52
+ ? `${patient.firstName} ${patient.lastName}`
53
+ : state.patientName || "New Patient" })] }), (patient?.email || state.patientEmail) && (_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Email" }), _jsx("span", { style: styles.value, children: patient?.email || state.patientEmail })] })), _jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Phone" }), _jsxs("span", { style: styles.value, children: [state.countryCode, " ", state.patientPhone] })] }), (patient?.age || state.patientAge) && (_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Age" }), _jsxs("span", { style: styles.value, children: [patient?.age || state.patientAge, " years"] })] })), (patient?.bloodGroup || state.bloodGroup) && (_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Blood Group" }), _jsx("span", { style: styles.value, children: patient?.bloodGroup || state.bloodGroup })] }))] })] }), _jsxs("div", { style: styles.section, children: [_jsx("h4", { style: styles.sectionTitle, children: "\uD83D\uDCC5 Appointment Details" }), _jsxs("div", { style: styles.summaryCard, children: [_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Date" }), _jsx("span", { style: styles.value, children: formatDate(state.selectedDate) })] }), _jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Time" }), _jsx("span", { style: styles.value, children: state.selectedSlot
54
+ ? `${formatTime(state.selectedSlot.start)} - ${formatTime(state.selectedSlot.end)}`
55
+ : "Not selected" })] }), _jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Duration" }), _jsxs("span", { style: styles.value, children: ["~", getDuration(), " minutes"] })] }), _jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Type" }), _jsx("span", { style: styles.value, children: state.consultationMode === "ONLINE"
56
+ ? "Online Consultation"
57
+ : "In-Person Visit" })] })] })] }), _jsxs("div", { style: styles.section, children: [_jsx("h4", { style: styles.sectionTitle, children: "\uD83C\uDFE5 Doctor & Location" }), _jsxs("div", { style: styles.summaryCard, children: [selectedDoctor && (_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Doctor" }), _jsxs("span", { style: styles.value, children: [selectedDoctor.name, selectedDoctor.specialty && ` • ${selectedDoctor.specialty}`] })] })), selectedAddress && (_jsxs(_Fragment, { children: [_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Location" }), _jsx("span", { style: styles.value, children: selectedAddress.completeAddress || "Unknown Location" })] }), selectedAddress.address && (_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Address" }), _jsx("span", { style: styles.value, children: selectedAddress.address })] }))] }))] })] }), _jsxs("div", { style: styles.section, children: [_jsx("h4", { style: styles.sectionTitle, children: "\uD83D\uDCB3 Payment" }), _jsxs("div", { style: styles.summaryCard, children: [_jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: "Booking Type" }), _jsx("span", { style: styles.value, children: paymentInfo.type })] }), _jsxs("div", { style: styles.summaryRow, children: [_jsx("span", { style: styles.label, children: usingSessionPack ? "Pack" : "Service" }), _jsx("span", { style: styles.value, children: paymentInfo.name })] }), _jsxs("div", { style: { ...styles.summaryRow, ...styles.totalRow }, children: [_jsx("span", { style: styles.totalLabel, children: "Amount" }), _jsx("span", { style: styles.totalValue, children: paymentInfo.amount })] }), paymentInfo.remaining && (_jsx("div", { style: styles.remainingInfo, children: paymentInfo.remaining }))] })] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.backBtn, onClick: onBack, children: "Back" }), _jsx("button", { style: styles.confirmBtn, onClick: onConfirm, disabled: state.loading, children: state.loading ? "Confirming..." : "Confirm Appointment" })] })] }));
58
+ };
59
+ const getStyles = (theme, breakpoint) => {
60
+ const isMobile = breakpoint === "mobile";
61
+ return {
62
+ container: {
63
+ display: "flex",
64
+ flexDirection: "column",
65
+ gap: 20,
66
+ },
67
+ title: {
68
+ margin: 0,
69
+ fontSize: isMobile ? 18 : 20,
70
+ fontWeight: 600,
71
+ color: theme.colors.text,
72
+ },
73
+ subtitle: {
74
+ margin: 0,
75
+ fontSize: 14,
76
+ color: theme.colors.textSecondary,
77
+ },
78
+ section: {
79
+ display: "flex",
80
+ flexDirection: "column",
81
+ gap: 12,
82
+ },
83
+ sectionTitle: {
84
+ margin: 0,
85
+ fontSize: 16,
86
+ fontWeight: 600,
87
+ color: theme.colors.text,
88
+ display: "flex",
89
+ alignItems: "center",
90
+ gap: 8,
91
+ },
92
+ summaryCard: {
93
+ display: "flex",
94
+ flexDirection: "column",
95
+ gap: 12,
96
+ padding: 16,
97
+ backgroundColor: theme.colors.backgroundSecondary || "#f9fafb",
98
+ borderRadius: theme.radii.md,
99
+ border: `1px solid ${theme.colors.border}`,
100
+ },
101
+ summaryRow: {
102
+ display: "flex",
103
+ justifyContent: "space-between",
104
+ alignItems: "center",
105
+ },
106
+ label: {
107
+ fontSize: 14,
108
+ color: theme.colors.textSecondary,
109
+ },
110
+ value: {
111
+ fontSize: 14,
112
+ fontWeight: 500,
113
+ color: theme.colors.text,
114
+ textAlign: "right",
115
+ },
116
+ totalRow: {
117
+ paddingTop: 12,
118
+ borderTop: `1px solid ${theme.colors.border}`,
119
+ marginTop: 4,
120
+ },
121
+ totalLabel: {
122
+ fontSize: 16,
123
+ fontWeight: 600,
124
+ color: theme.colors.text,
125
+ },
126
+ totalValue: {
127
+ fontSize: 18,
128
+ fontWeight: 600,
129
+ color: theme.colors.primary,
130
+ },
131
+ remainingInfo: {
132
+ padding: "8px 12px",
133
+ backgroundColor: "#fef3c7",
134
+ color: "#92400e",
135
+ borderRadius: theme.radii.sm,
136
+ fontSize: 13,
137
+ textAlign: "center",
138
+ },
139
+ actions: {
140
+ display: "flex",
141
+ gap: 12,
142
+ marginTop: 24,
143
+ },
144
+ backBtn: {
145
+ flex: 1,
146
+ padding: "14px 24px",
147
+ border: `1px solid ${theme.colors.border}`,
148
+ borderRadius: theme.radii.md,
149
+ backgroundColor: "transparent",
150
+ color: theme.colors.text,
151
+ cursor: "pointer",
152
+ fontSize: 14,
153
+ fontWeight: 500,
154
+ },
155
+ confirmBtn: {
156
+ flex: 1,
157
+ padding: "14px 24px",
158
+ border: "none",
159
+ borderRadius: theme.radii.md,
160
+ backgroundColor: theme.colors.primary,
161
+ color: "#ffffff",
162
+ cursor: "pointer",
163
+ fontSize: 14,
164
+ fontWeight: 600,
165
+ },
166
+ };
167
+ };
168
+ export default AppointmentSummaryStep;
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { SessionPack, AvailablePackage } from "./appointment-booking/types";
3
+ export interface BookingOptionStepProps {
4
+ userSessionPacks: SessionPack[];
5
+ availablePackages: AvailablePackage[];
6
+ showPackageExplorer: boolean;
7
+ selectedSessionPack: SessionPack | null;
8
+ selectedNewPackage: AvailablePackage | null;
9
+ onSelectOption: (optionType: "session-pack" | "new-appointment" | "explore-packages", sessionPack?: SessionPack, newPackage?: AvailablePackage) => void;
10
+ onSelectPackage: (pkg: AvailablePackage) => void;
11
+ onBack: () => void;
12
+ }
13
+ export declare const BookingOptionStep: React.FC<BookingOptionStepProps>;
14
+ export default BookingOptionStep;