medos-sdk 1.1.9 → 1.1.10

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 (52) hide show
  1. package/dist/components/AppointmentCalender.js +44 -27
  2. package/dist/components/AppointmentConfirmationStep.d.ts +3 -0
  3. package/dist/components/AppointmentConfirmationStep.js +26 -20
  4. package/dist/components/AppointmentDateTimeModal.d.ts +3 -0
  5. package/dist/components/AppointmentDateTimeModal.js +24 -6
  6. package/dist/components/ContactInformationStep.js +6 -5
  7. package/dist/components/ContactPreferenceStep.js +6 -5
  8. package/dist/components/DoctorSelectModal.d.ts +5 -0
  9. package/dist/components/DoctorSelectModal.js +65 -16
  10. package/dist/components/EnquiryForm.js +4 -4
  11. package/dist/components/InquiryDetailsStep.js +7 -6
  12. package/dist/components/appointment-booking/AppointmentCalender.d.ts +5 -0
  13. package/dist/components/appointment-booking/AppointmentCalender.js +107 -0
  14. package/dist/components/appointment-booking/hooks/index.d.ts +3 -0
  15. package/dist/components/appointment-booking/hooks/index.js +3 -0
  16. package/dist/components/appointment-booking/hooks/useAppointmentFlow.d.ts +9 -0
  17. package/dist/components/appointment-booking/hooks/useAppointmentFlow.js +233 -0
  18. package/dist/components/appointment-booking/hooks/useAppointmentState.d.ts +9 -0
  19. package/dist/components/appointment-booking/hooks/useAppointmentState.js +93 -0
  20. package/dist/components/appointment-booking/hooks/useInitializeAddresses.d.ts +1 -0
  21. package/dist/components/appointment-booking/hooks/useInitializeAddresses.js +56 -0
  22. package/dist/components/appointment-booking/index.d.ts +5 -0
  23. package/dist/components/appointment-booking/index.js +3 -0
  24. package/dist/components/appointment-booking/types.d.ts +128 -0
  25. package/dist/components/appointment-booking/types.js +33 -0
  26. package/dist/components/types.d.ts +10 -139
  27. package/dist/components/types.js +1 -33
  28. package/dist/components/uiComponents/SelectDropdown.d.ts +1 -1
  29. package/dist/components/uiComponents/SelectDropdown.js +24 -24
  30. package/dist/enquiry-form/validation.js +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/react/index.d.ts +2 -1
  34. package/dist/react/index.js +1 -1
  35. package/dist/vanilla/AppointmentCalendarWidget.js +20 -13
  36. package/dist/vanilla/components/AppointmentConfirmationStep.d.ts +3 -0
  37. package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +3 -0
  38. package/dist/vanilla/components/DoctorSelectModal.d.ts +5 -0
  39. package/dist/vanilla/components/appointment-booking/AppointmentCalender.d.ts +5 -0
  40. package/dist/vanilla/components/appointment-booking/hooks/index.d.ts +3 -0
  41. package/dist/vanilla/components/appointment-booking/hooks/useAppointmentFlow.d.ts +9 -0
  42. package/dist/vanilla/components/appointment-booking/hooks/useAppointmentState.d.ts +9 -0
  43. package/dist/vanilla/components/appointment-booking/hooks/useInitializeAddresses.d.ts +1 -0
  44. package/dist/vanilla/components/appointment-booking/index.d.ts +5 -0
  45. package/dist/vanilla/components/appointment-booking/types.d.ts +128 -0
  46. package/dist/vanilla/components/types.d.ts +10 -139
  47. package/dist/vanilla/components/uiComponents/SelectDropdown.d.ts +1 -1
  48. package/dist/vanilla/enquiry-widget.js +1 -1
  49. package/dist/vanilla/index.d.ts +1 -1
  50. package/dist/vanilla/react/index.d.ts +2 -1
  51. package/dist/vanilla/widget.js +44 -36
  52. package/package.json +1 -1
@@ -90,39 +90,38 @@ const appointmentReducer = (state, action) => {
90
90
  };
91
91
  export const AppointmentCalender = ({ onError, }) => {
92
92
  const [state, dispatch] = useReducer(appointmentReducer, INITIAL_STATE);
93
- const handleDateChange = useCallback(async (date) => {
94
- dispatch({ type: "SET_SELECTED_DATE", payload: date });
93
+ const fetchSlotsForDate = useCallback(async (date) => {
95
94
  if (!state.workspaceId ||
96
95
  !state.selectedAddress ||
97
96
  !state.selectedDoctor) {
98
97
  dispatch({ type: "SET_SLOTS", payload: [] });
99
98
  return;
100
99
  }
101
- let mounted = true;
102
100
  dispatch({ type: "SET_LOADING", payload: true });
103
101
  dispatch({ type: "SET_ERROR", payload: null });
104
102
  try {
105
103
  const dateStr = formatDateToISO(date);
106
104
  const fetchedSlots = await AppointmentService.fetchSlots(state.workspaceId, state.selectedAddress, state.selectedDoctor, dateStr);
107
- if (mounted) {
108
- dispatch({ type: "SET_SLOTS", payload: fetchedSlots || [] });
109
- }
105
+ dispatch({ type: "SET_SLOTS", payload: fetchedSlots || [] });
110
106
  }
111
107
  catch (e) {
112
- if (mounted) {
113
- const msg = e.message || "Failed to load slots";
114
- dispatch({ type: "SET_ERROR", payload: msg });
115
- onError?.(e);
116
- }
108
+ const msg = e.message || "Failed to load slots";
109
+ dispatch({ type: "SET_ERROR", payload: msg });
110
+ onError?.(e);
117
111
  }
118
112
  finally {
119
- if (mounted)
120
- dispatch({ type: "SET_LOADING", payload: false });
113
+ dispatch({ type: "SET_LOADING", payload: false });
121
114
  }
122
- return () => {
123
- mounted = false;
124
- };
125
115
  }, [state.workspaceId, state.selectedAddress, state.selectedDoctor, onError]);
116
+ const handleDateChange = useCallback(async (date) => {
117
+ dispatch({ type: "SET_SELECTED_DATE", payload: date });
118
+ fetchSlotsForDate(date);
119
+ }, [fetchSlotsForDate]);
120
+ useEffect(() => {
121
+ if (state.step === 1 && state.selectedDate) {
122
+ fetchSlotsForDate(state.selectedDate);
123
+ }
124
+ }, [state.step, state.selectedDate, fetchSlotsForDate]);
126
125
  useEffect(() => {
127
126
  let mounted = true;
128
127
  (async () => {
@@ -149,6 +148,8 @@ export const AppointmentCalender = ({ onError, }) => {
149
148
  payload: address.doctors[0].id,
150
149
  });
151
150
  dispatch({ type: "SET_STEP", payload: 1 });
151
+ const today = new Date();
152
+ dispatch({ type: "SET_SELECTED_DATE", payload: today });
152
153
  }
153
154
  }
154
155
  }
@@ -174,14 +175,20 @@ export const AppointmentCalender = ({ onError, }) => {
174
175
  return () => {
175
176
  mounted = false;
176
177
  };
177
- }, [onError]);
178
+ }, []);
178
179
  const goBack = useCallback(() => {
179
180
  if (state.step === 3) {
180
181
  dispatch({ type: "SET_OTP_SENT", payload: false });
181
182
  dispatch({ type: "SET_OTP_CODE", payload: "" });
182
183
  dispatch({ type: "SET_OTP_VERIFIED", payload: false });
184
+ dispatch({ type: "SET_STEP", payload: 1 });
185
+ }
186
+ else if (state.step === 4) {
187
+ dispatch({ type: "SET_STEP", payload: 3 });
188
+ }
189
+ else {
190
+ dispatch({ type: "SET_STEP", payload: Math.max(0, state.step - 1) });
183
191
  }
184
- dispatch({ type: "SET_STEP", payload: Math.max(0, state.step - 1) });
185
192
  }, [state.step]);
186
193
  const goToNext = useCallback(() => {
187
194
  dispatch({ type: "SET_STEP", payload: Math.min(5, state.step + 1) });
@@ -227,7 +234,7 @@ export const AppointmentCalender = ({ onError, }) => {
227
234
  finally {
228
235
  dispatch({ type: "SET_OTP_SENDING", payload: false });
229
236
  }
230
- }, [state.countryCode, state.patientPhone, onError]);
237
+ }, [state.countryCode, state.patientPhone]);
231
238
  const verifyOtp = useCallback(async () => {
232
239
  dispatch({ type: "SET_ERROR", payload: null });
233
240
  if (!state.countryCode || !state.patientPhone || !state.otpCode) {
@@ -262,7 +269,7 @@ export const AppointmentCalender = ({ onError, }) => {
262
269
  finally {
263
270
  dispatch({ type: "SET_OTP_VERIFYING", payload: false });
264
271
  }
265
- }, [state.countryCode, state.patientPhone, state.otpCode, onError]);
272
+ }, [state.countryCode, state.patientPhone, state.otpCode]);
266
273
  const submitAppointment = useCallback(async () => {
267
274
  dispatch({ type: "SET_ERROR", payload: null });
268
275
  if (!state.selectedDoctor ||
@@ -355,6 +362,8 @@ export const AppointmentCalender = ({ onError, }) => {
355
362
  dispatch({ type: "SET_SELECTED_ADDRESS", payload: addrId });
356
363
  dispatch({ type: "SET_SELECTED_DOCTOR", payload: docId });
357
364
  dispatch({ type: "SET_STEP", payload: 1 });
365
+ const today = new Date();
366
+ dispatch({ type: "SET_SELECTED_DATE", payload: today });
358
367
  }, []);
359
368
  const handleDateTimeModalContinue = useCallback((mode, date, slot, charge) => {
360
369
  dispatch({ type: "SET_CONSULTATION_MODE", payload: mode });
@@ -365,17 +374,25 @@ export const AppointmentCalender = ({ onError, }) => {
365
374
  }, []);
366
375
  const theme = useTheme();
367
376
  const styles = getStyles(theme);
368
- return (_jsx("div", { style: styles.container, children: _jsxs("div", { style: styles.card, children: [_jsx("div", { style: styles.header, children: _jsx("h2", { style: styles.title, children: "Book Appointment" }) }), _jsx("hr", {}), _jsxs("div", { style: styles.content, children: [state.loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), state.error && _jsx("div", { style: styles.errorMessage, children: state.error }), state.step === 0 && (_jsx(DoctorSelectModal, { onCancel: () => dispatch({ type: "SET_STEP", payload: 0 }), onContinue: handleDoctorSelect })), state.step === 1 && (_jsx(AppointmentDateTimeModal, { onlineFee: 500, offlineFee: 300, slots: state.slots, onCancel: goBack, onDateChange: handleDateChange, onContinue: handleDateTimeModalContinue })), state.step === 3 && (_jsx(PhoneVerificationStep, { state: state, dispatch: dispatch, onSendOtp: sendOtp, onVerifyOtp: verifyOtp, onBack: goBack, onContinue: goToNext })), state.step === 4 && (_jsx(PatientDetailsStep, { state: state, dispatch: dispatch, onBack: goBack, onSubmit: submitAppointment })), state.step === 5 && (_jsx(AppointmentConfirmationStep, { appointment: {
377
+ return (_jsx("div", { style: styles.container, children: _jsxs("div", { style: styles.card, children: [_jsx("div", { style: styles.header, children: _jsx("h2", { style: styles.title, children: "Book Appointment" }) }), _jsx("hr", {}), _jsxs("div", { style: styles.content, children: [state.loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), state.error && _jsx("div", { style: styles.errorMessage, children: state.error }), state.step === 0 && (_jsx(DoctorSelectModal, { addresses: state.addresses, addressDoctorsMap: state.addressDoctorsMap, selectedAddressId: state.selectedAddress, selectedDoctorId: state.selectedDoctor, onCancel: () => dispatch({ type: "SET_STEP", payload: 0 }), onContinue: handleDoctorSelect })), state.step === 1 && (_jsx(AppointmentDateTimeModal, { onlineFee: 500, offlineFee: 300, slots: state.slots, selectedDate: state.selectedDate, selectedSlot: state.selectedSlot, consultationMode: state.consultationMode, onCancel: goBack, onDateChange: handleDateChange, onContinue: handleDateTimeModalContinue })), state.step === 3 && (_jsx(PhoneVerificationStep, { state: state, dispatch: dispatch, onSendOtp: sendOtp, onVerifyOtp: verifyOtp, onBack: goBack, onContinue: goToNext })), state.step === 4 && (_jsx(PatientDetailsStep, { state: state, dispatch: dispatch, onBack: goBack, onSubmit: submitAppointment })), state.step === 5 && (_jsx(AppointmentConfirmationStep, { appointment: {
369
378
  patientName: state.patientName,
370
- visitationType: state.consultationMode === "ONLINE" ? "Online Consultation" : "In-Person Visit",
379
+ visitationType: state.consultationMode === "ONLINE"
380
+ ? "Online Consultation"
381
+ : "In-Person Visit",
371
382
  appointmentDate: formatDateToISO(state.selectedDate),
372
- fromTime: state.selectedSlot?.start ? new Date(state.selectedSlot.start).toTimeString().slice(0, 5) : "",
373
- toTime: state.selectedSlot?.end ? new Date(state.selectedSlot.end).toTimeString().slice(0, 5) : "",
374
- location: state.addresses.find(addr => addr.id === state.selectedAddress)?.label || "Clinic",
383
+ fromTime: state.selectedSlot?.start
384
+ ? new Date(state.selectedSlot.start)
385
+ .toTimeString()
386
+ .slice(0, 5)
387
+ : "",
388
+ toTime: state.selectedSlot?.end
389
+ ? new Date(state.selectedSlot.end).toTimeString().slice(0, 5)
390
+ : "",
391
+ location: state.addresses.find((addr) => addr.id === state.selectedAddress)?.label || "Clinic",
375
392
  mode: state.consultationMode,
376
- paymentMode: "Cash"
393
+ paymentMode: "Cash",
377
394
  }, patient: {
378
- patientName: state.patientName
395
+ patientName: state.patientName,
379
396
  }, onClose: resetForm })), _jsxs("div", { style: styles.branding, children: [_jsx("span", { style: styles.poweredBy, children: "Powered by" }), _jsx("a", { href: "https://medos.one", target: "_blank", rel: "noopener noreferrer", children: _jsx(MedosLogo, { style: { height: 50, width: "auto" } }) })] })] })] }) }));
380
397
  };
381
398
  const getStyles = (theme) => ({
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { Doctor, AddressItem } from "../services/AppointmentService";
2
3
  interface AppointmentDetails {
3
4
  patientName?: string;
4
5
  visitationType?: string;
@@ -18,6 +19,8 @@ interface AppointmentConfirmationStepProps {
18
19
  lastName?: string;
19
20
  patientName?: string;
20
21
  };
22
+ selectedDoctor?: Doctor | null;
23
+ selectedAddress?: AddressItem | null;
21
24
  onClose: () => void;
22
25
  }
23
26
  declare const AppointmentConfirmationStep: React.FC<AppointmentConfirmationStepProps>;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useTheme } from "../react/hooks/useTheme";
3
3
  import { SuccessIcon } from "./Icons/SuccessIcon";
4
- const AppointmentConfirmationStep = ({ appointment, patient, onClose, }) => {
4
+ const AppointmentConfirmationStep = ({ appointment, patient, selectedDoctor, selectedAddress, onClose }) => {
5
5
  const theme = useTheme();
6
6
  const calculateDuration = () => {
7
7
  if (appointment.duration)
@@ -20,17 +20,21 @@ const AppointmentConfirmationStep = ({ appointment, patient, onClose, }) => {
20
20
  return appointment.patientName;
21
21
  if (patient.patientName)
22
22
  return patient.patientName;
23
- const parts = [patient.firstName, patient.middleName, patient.lastName].filter(Boolean);
23
+ const parts = [
24
+ patient.firstName,
25
+ patient.middleName,
26
+ patient.lastName,
27
+ ].filter(Boolean);
24
28
  return parts.join(" ") || "Patient";
25
29
  };
26
30
  const formatDate = (dateStr) => {
27
31
  try {
28
32
  const date = new Date(dateStr);
29
- return date.toLocaleDateString('en-US', {
30
- weekday: 'long',
31
- year: 'numeric',
32
- month: 'long',
33
- day: 'numeric'
33
+ return date.toLocaleDateString("en-US", {
34
+ weekday: "long",
35
+ year: "numeric",
36
+ month: "long",
37
+ day: "numeric",
34
38
  });
35
39
  }
36
40
  catch {
@@ -40,10 +44,10 @@ const AppointmentConfirmationStep = ({ appointment, patient, onClose, }) => {
40
44
  const formatTime = (timeStr) => {
41
45
  try {
42
46
  const time = new Date(`2000-01-01T${timeStr}`);
43
- return time.toLocaleTimeString('en-US', {
44
- hour: 'numeric',
45
- minute: '2-digit',
46
- hour12: true
47
+ return time.toLocaleTimeString("en-US", {
48
+ hour: "numeric",
49
+ minute: "2-digit",
50
+ hour12: true,
47
51
  });
48
52
  }
49
53
  catch {
@@ -58,14 +62,14 @@ const AppointmentConfirmationStep = ({ appointment, patient, onClose, }) => {
58
62
  padding: "0",
59
63
  fontFamily: theme.typography.fontFamily,
60
64
  background: theme.colors.background,
61
- minHeight: "500px"
65
+ minHeight: "500px",
62
66
  }, children: [_jsx("div", { style: {
63
67
  padding: "20px 24px",
64
68
  fontSize: 24,
65
69
  fontWeight: "bold",
66
70
  color: theme.colors.primary,
67
71
  borderBottom: `2px solid ${theme.colors.border}`,
68
- background: theme.colors.surface
72
+ background: theme.colors.surface,
69
73
  }, children: "Appointment Confirmed" }), _jsxs("div", { style: {
70
74
  flex: 1,
71
75
  display: "flex",
@@ -76,35 +80,37 @@ const AppointmentConfirmationStep = ({ appointment, patient, onClose, }) => {
76
80
  border: `2px solid ${theme.colors.border}`,
77
81
  borderTop: "none",
78
82
  background: theme.colors.surface,
79
- textAlign: "center"
83
+ textAlign: "center",
80
84
  }, children: [_jsx("h2", { style: {
81
85
  fontSize: 20,
82
86
  fontWeight: "600",
83
87
  color: theme.colors.success,
84
- margin: "0 0 24px 0"
88
+ margin: "0 0 24px 0",
85
89
  }, children: "Appointment Confirmed" }), _jsx("div", { style: { marginBottom: 32 }, children: _jsx(SuccessIcon, { size: 64, checkColor: "white", shapeColor: theme.colors.success }) }), _jsxs("div", { style: {
86
90
  width: "100%",
87
91
  maxWidth: 500,
88
- marginBottom: 32
92
+ marginBottom: 32,
89
93
  }, children: [_jsx("h3", { style: {
90
94
  fontSize: 18,
91
95
  fontWeight: "600",
92
96
  color: theme.colors.success,
93
- marginBottom: 24
97
+ marginBottom: 24,
94
98
  }, children: "Appointment Details" }), _jsxs("div", { style: {
95
99
  display: "flex",
96
100
  flexDirection: "column",
97
101
  gap: 12,
98
102
  fontSize: 16,
99
103
  lineHeight: 1.6,
100
- color: theme.colors.text
101
- }, children: [_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Patient:" }), " ", patientName] }), appointment.visitationType && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Visitation Type:" }), " ", appointment.visitationType] })), appointment.appointmentDate && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Date:" }), " ", formatDate(appointment.appointmentDate)] })), appointment.fromTime && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Time:" }), " ", formatTime(appointment.fromTime)] })), _jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Duration:" }), " ~", duration, " minutes"] }), appointment.location && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Location:" }), " ", appointment.location] }))] })] }), _jsx("div", { style: {
104
+ color: theme.colors.text,
105
+ }, children: [_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Patient:" }), " ", patientName] }), selectedDoctor && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Doctor:" }), " ", selectedDoctor.name] })), appointment.visitationType && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Visitation Type:" }), " ", appointment.visitationType] })), appointment.appointmentDate && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Date:" }), " ", formatDate(appointment.appointmentDate)] })), appointment.fromTime && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Time:" }), " ", formatTime(appointment.fromTime)] })), _jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Duration:" }), " ", "~", duration, " minutes"] }), selectedAddress && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Location:" }), " ", selectedAddress.completeAddress ||
106
+ selectedAddress.address ||
107
+ "Address not available"] })), appointment.location && !selectedAddress && (_jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.success }, children: "Location:" }), " ", appointment.location] }))] })] }), _jsx("div", { style: {
102
108
  fontSize: 16,
103
109
  fontStyle: "italic",
104
110
  color: theme.colors.textSecondary,
105
111
  textAlign: "center",
106
112
  maxWidth: 600,
107
- lineHeight: 1.5
113
+ lineHeight: 1.5,
108
114
  }, children: "A confirmation email has been sent to the Patient's Email address." })] })] }));
109
115
  };
110
116
  export default AppointmentConfirmationStep;
@@ -6,6 +6,9 @@ type AppointmentModalProps = {
6
6
  onlineFee?: number | null;
7
7
  offlineFee?: number | null;
8
8
  slots: Slot[];
9
+ selectedDate?: Date;
10
+ selectedSlot?: Slot | null;
11
+ consultationMode?: AppointmentMode;
9
12
  onCancel: () => void;
10
13
  onContinue: (mode: AppointmentMode, date: Date, slot: Slot, charge: string, paymentMode: PaymentMode) => void;
11
14
  onDateChange?: (date: Date) => void;
@@ -39,18 +39,36 @@ const formatDate = (date) => {
39
39
  const year = date.getFullYear();
40
40
  return `${day}${getOrdinalSuffix(day)} ${month} ${year}`;
41
41
  };
42
- export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, onCancel, onContinue, onDateChange, }) => {
42
+ export const AppointmentDateTimeModal = ({ onlineFee, offlineFee, slots, selectedDate: initialSelectedDate, selectedSlot: initialSelectedSlot, consultationMode: initialConsultationMode, onCancel, onContinue, onDateChange, }) => {
43
43
  const theme = useTheme();
44
- const [mode, setMode] = useState("OFFLINE");
44
+ const [mode, setMode] = useState(initialConsultationMode || "OFFLINE");
45
45
  const [consultationCharge, setConsultationCharge] = useState("");
46
46
  const [paymentMode, setPaymentMode] = useState("CASH");
47
- const [selectedDate, setSelectedDate] = useState(new Date());
48
- const [selectedSlot, setSelectedSlot] = useState(null);
47
+ const [selectedDate, setSelectedDate] = useState(initialSelectedDate || new Date());
48
+ const [selectedSlot, setSelectedSlot] = useState(initialSelectedSlot || null);
49
+ useEffect(() => {
50
+ if (initialSelectedDate) {
51
+ setSelectedDate(initialSelectedDate);
52
+ }
53
+ }, [initialSelectedDate]);
54
+ useEffect(() => {
55
+ if (initialSelectedSlot) {
56
+ setSelectedSlot(initialSelectedSlot);
57
+ }
58
+ }, [initialSelectedSlot]);
59
+ useEffect(() => {
60
+ if (initialConsultationMode) {
61
+ setMode(initialConsultationMode);
62
+ }
63
+ }, [initialConsultationMode]);
49
64
  const handleDateSelect = useCallback((date) => {
65
+ const dateChanged = date.toDateString() !== selectedDate.toDateString();
50
66
  setSelectedDate(date);
51
- setSelectedSlot(null);
67
+ if (dateChanged) {
68
+ setSelectedSlot(null);
69
+ }
52
70
  onDateChange?.(date);
53
- }, [onDateChange]);
71
+ }, [selectedDate, onDateChange]);
54
72
  useEffect(() => {
55
73
  const charge = mode === "ONLINE" && onlineFee
56
74
  ? String(onlineFee)
@@ -1,14 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import { useTheme } from "../react/hooks/useTheme";
3
4
  import { getContainerStyles, getPhoneVerifyStyles, getButtonStyles, } from "./theme-styles";
4
- export const ContactInformationStep = ({ patientName, patientEmail, countryCode, patientPhone, onNameChange, onEmailChange, onCountryCodeChange, onPhoneChange, onNext, }) => {
5
+ export const ContactInformationStep = React.memo(({ patientName, patientEmail, countryCode, patientPhone, onNameChange, onEmailChange, onCountryCodeChange, onPhoneChange, onNext, }) => {
5
6
  const theme = useTheme();
6
- const PHONE_VERIFY_STYLES = getPhoneVerifyStyles(theme);
7
- const BUTTON_STYLES = getButtonStyles(theme);
8
- const CONTAINER_STYLES = getContainerStyles(theme);
7
+ const PHONE_VERIFY_STYLES = React.useMemo(() => getPhoneVerifyStyles(theme), [theme]);
8
+ const BUTTON_STYLES = React.useMemo(() => getButtonStyles(theme), [theme]);
9
+ const CONTAINER_STYLES = React.useMemo(() => getContainerStyles(theme), [theme]);
9
10
  return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Name" }), _jsx("input", { type: "text", value: patientName, onChange: (e) => onNameChange(e.target.value), placeholder: "Enter your full name", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Email" }), _jsx("input", { type: "email", value: patientEmail, onChange: (e) => onEmailChange(e.target.value), placeholder: "Enter your email", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Phone Number" }), _jsxs("div", { style: PHONE_VERIFY_STYLES.phoneInputContainer, children: [_jsx("input", { type: "text", value: countryCode, onChange: (e) => onCountryCodeChange(e.target.value), placeholder: "+91", style: {
10
11
  ...PHONE_VERIFY_STYLES.phoneInput,
11
12
  width: "80px",
12
13
  flex: "none",
13
14
  } }), _jsx("input", { type: "tel", value: patientPhone, onChange: (e) => onPhoneChange(e.target.value), placeholder: "Enter phone number", style: PHONE_VERIFY_STYLES.phoneInput })] })] }), _jsx("div", { style: CONTAINER_STYLES.actions, children: _jsx("button", { onClick: onNext, style: BUTTON_STYLES.primary, children: "Next" }) })] }));
14
- };
15
+ });
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import { useTheme } from "../react/hooks/useTheme";
3
4
  import { getPhoneVerifyStyles, getButtonStyles, getContainerStyles, } from "./theme-styles";
4
- export const ContactPreferenceStep = ({ preferredContactMethod, onContactMethodChange, onBack, onSubmit, isLoading = false, }) => {
5
+ export const ContactPreferenceStep = React.memo(({ preferredContactMethod, onContactMethodChange, onBack, onSubmit, isLoading = false, }) => {
5
6
  const theme = useTheme();
6
- const PHONE_VERIFY_STYLES = getPhoneVerifyStyles(theme);
7
- const BUTTON_STYLES = getButtonStyles(theme);
8
- const CONTAINER_STYLES = getContainerStyles(theme);
7
+ const PHONE_VERIFY_STYLES = React.useMemo(() => getPhoneVerifyStyles(theme), [theme]);
8
+ const BUTTON_STYLES = React.useMemo(() => getButtonStyles(theme), [theme]);
9
+ const CONTAINER_STYLES = React.useMemo(() => getContainerStyles(theme), [theme]);
9
10
  return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Preferred Contact Method" }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "PHONE", checked: preferredContactMethod === "PHONE", onChange: (e) => onContactMethodChange(e.target.value) }), "Phone"] }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "EMAIL", checked: preferredContactMethod === "EMAIL", onChange: (e) => onContactMethodChange(e.target.value) }), "Email"] }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsx("input", { type: "radio", name: "contactMethod", value: "BOTH", checked: preferredContactMethod === "BOTH", onChange: (e) => onContactMethodChange(e.target.value) }), "Both"] })] })] }), _jsxs("div", { style: CONTAINER_STYLES.actions, children: [_jsx("button", { onClick: onBack, style: BUTTON_STYLES.secondary, children: "Back" }), _jsx("button", { onClick: onSubmit, style: BUTTON_STYLES.primary, children: isLoading ? "Submitting..." : "Submit" })] })] }));
10
- };
11
+ });
@@ -1,5 +1,10 @@
1
1
  import React from "react";
2
+ import { Doctor, AddressItem } from "../services/AppointmentService";
2
3
  type DoctorSelectModalProps = {
4
+ addresses?: AddressItem[];
5
+ addressDoctorsMap?: Record<number, Doctor[]>;
6
+ selectedAddressId?: number | null;
7
+ selectedDoctorId?: number | null;
3
8
  onCancel: () => void;
4
9
  onContinue: (addressId: number, doctorId: number, notes?: string) => void;
5
10
  };
@@ -1,29 +1,66 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
3
  import { AppointmentService, } from "../services/AppointmentService";
4
- import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "./uiComponents/SelectDropdown";
4
+ import { Select, SelectTrigger, SelectContent, SelectItem, } from "./uiComponents/SelectDropdown";
5
5
  import { useTheme } from "../react/hooks/useTheme";
6
- export const DoctorSelectModal = ({ onCancel, onContinue, }) => {
6
+ export const DoctorSelectModal = ({ addresses: initialAddresses, addressDoctorsMap: initialAddressDoctorsMap, selectedAddressId: initialSelectedAddressId, selectedDoctorId: initialSelectedDoctorId, onCancel, onContinue, }) => {
7
7
  const theme = useTheme();
8
- const [addresses, setAddresses] = useState([]);
9
- const [addressDoctorsMap, setAddressDoctorsMap] = useState({});
10
- const [selectedAddressId, setSelectedAddressId] = useState("");
11
- const [selectedDoctorId, setSelectedDoctorId] = useState("");
8
+ const [addresses, setAddresses] = useState(initialAddresses || []);
9
+ const [addressDoctorsMap, setAddressDoctorsMap] = useState(initialAddressDoctorsMap || {});
10
+ const [selectedAddressId, setSelectedAddressId] = useState(initialSelectedAddressId ? String(initialSelectedAddressId) : "");
11
+ const [selectedDoctorId, setSelectedDoctorId] = useState(initialSelectedDoctorId ? String(initialSelectedDoctorId) : "");
12
12
  const [appointmentNotes, setAppointmentNotes] = useState("");
13
13
  useEffect(() => {
14
- (async () => {
15
- const res = await AppointmentService.getAddresses();
16
- const map = {};
17
- res.addresses.forEach((addr) => {
18
- map[addr.id] = addr.doctors || [];
19
- });
20
- setAddresses(res.addresses);
21
- setAddressDoctorsMap(map);
22
- })();
14
+ if (initialAddresses && initialAddresses.length > 0) {
15
+ setAddresses(initialAddresses);
16
+ }
17
+ if (initialAddressDoctorsMap &&
18
+ Object.keys(initialAddressDoctorsMap).length > 0) {
19
+ setAddressDoctorsMap(initialAddressDoctorsMap);
20
+ }
21
+ if (initialSelectedAddressId !== undefined &&
22
+ initialSelectedAddressId !== null) {
23
+ setSelectedAddressId(String(initialSelectedAddressId));
24
+ }
25
+ if (initialSelectedDoctorId !== undefined &&
26
+ initialSelectedDoctorId !== null) {
27
+ setSelectedDoctorId(String(initialSelectedDoctorId));
28
+ }
29
+ }, [
30
+ initialAddresses,
31
+ initialAddressDoctorsMap,
32
+ initialSelectedAddressId,
33
+ initialSelectedDoctorId,
34
+ ]);
35
+ useEffect(() => {
36
+ if (!initialAddresses || initialAddresses.length === 0) {
37
+ (async () => {
38
+ try {
39
+ const response = await AppointmentService.getAddresses();
40
+ if (response.addresses?.length > 0) {
41
+ setAddresses(response.addresses);
42
+ const doctorMap = {};
43
+ response.addresses.forEach((addr) => {
44
+ doctorMap[addr.id] = addr.doctors || [];
45
+ });
46
+ setAddressDoctorsMap(doctorMap);
47
+ }
48
+ }
49
+ catch (error) {
50
+ console.error("Failed to fetch addresses:", error);
51
+ }
52
+ })();
53
+ }
23
54
  }, []);
24
55
  const doctorsForAddress = selectedAddressId
25
56
  ? addressDoctorsMap[Number(selectedAddressId)] || []
26
57
  : [];
58
+ const selectedAddressLabel = addresses.find((a) => String(a.id) === selectedAddressId)?.label ||
59
+ addresses.find((a) => String(a.id) === selectedAddressId)
60
+ ?.completeAddress ||
61
+ "";
62
+ const selectedDoctorLabel = doctorsForAddress.find((d) => String(d.id) === selectedDoctorId)?.name ||
63
+ "";
27
64
  const canContinue = !!selectedAddressId && !!selectedDoctorId;
28
65
  const styles = getStyles(theme);
29
66
  return (_jsx("div", { style: {
@@ -32,7 +69,19 @@ export const DoctorSelectModal = ({ onCancel, onContinue, }) => {
32
69
  }, children: _jsxs("div", { style: styles.modalWrapper, children: [_jsx("div", { style: styles.headerWrapper, children: _jsx("h3", { style: styles.title, children: "Location & Doctor" }) }), _jsx("div", { style: { borderBottom: `1px solid ${theme.colors.border}` } }), _jsxs("div", { style: styles.contentWrapper, children: [_jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Location ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedAddressId, onValueChange: (value) => {
33
70
  setSelectedAddressId(value);
34
71
  setSelectedDoctorId("");
35
- }, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx(SelectValue, { placeholder: "Select Address" }) }), _jsx(SelectContent, { children: addresses.map((a) => (_jsx(SelectItem, { value: String(a.id), children: a.completeAddress || a.label || `Address ${a.id}` }, a.id))) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Doctor ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedDoctorId, onValueChange: setSelectedDoctorId, disabled: !selectedAddressId, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx(SelectValue, { placeholder: "Select Doctor" }) }), _jsx(SelectContent, { children: doctorsForAddress.length > 0 ? (doctorsForAddress.map((d) => (_jsxs(SelectItem, { value: String(d.id), children: [d.name, " ", d.specialty ? `- ${d.specialty}` : ""] }, d.id)))) : (_jsx(SelectItem, { value: "no-doctors", disabled: true, children: "No doctors available" })) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Chief Complaint ", _jsx("span", { style: styles.optional, children: "(optional)" })] }), _jsx("textarea", { style: styles.textarea, placeholder: "Enter Chief Complaint or Appointment Notes", value: appointmentNotes, onChange: (e) => setAppointmentNotes(e.target.value) })] }), _jsx("div", { style: styles.footer, children: _jsx("button", { style: {
72
+ }, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx("span", { style: {
73
+ color: selectedAddressId ? "#1e293b" : "#94a3b8",
74
+ overflow: "hidden",
75
+ textOverflow: "ellipsis",
76
+ whiteSpace: "nowrap",
77
+ flex: 1,
78
+ }, children: selectedAddressLabel || "Select Address" }) }), _jsx(SelectContent, { children: addresses.map((a) => (_jsx(SelectItem, { value: String(a.id), children: a.completeAddress || a.label || `Address ${a.id}` }, a.id))) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Preferred Doctor ", _jsx("span", { style: styles.mandatory, children: "*" })] }), _jsxs(Select, { value: selectedDoctorId, onValueChange: setSelectedDoctorId, disabled: !selectedAddressId, children: [_jsx(SelectTrigger, { style: { width: "100%" }, children: _jsx("span", { style: {
79
+ color: selectedDoctorId ? "#1e293b" : "#94a3b8",
80
+ overflow: "hidden",
81
+ textOverflow: "ellipsis",
82
+ whiteSpace: "nowrap",
83
+ flex: 1,
84
+ }, children: selectedDoctorLabel || "Select Doctor" }) }), _jsx(SelectContent, { children: doctorsForAddress.length > 0 ? (doctorsForAddress.map((d) => (_jsxs(SelectItem, { value: String(d.id), children: [d.name, " ", d.specialty ? `- ${d.specialty}` : ""] }, d.id)))) : (_jsx(SelectItem, { value: "no-doctors", disabled: true, children: "No doctors available" })) })] })] }), _jsxs("div", { style: styles.fieldGroup, children: [_jsxs("label", { style: styles.label, children: ["Chief Complaint ", _jsx("span", { style: styles.optional, children: "(optional)" })] }), _jsx("textarea", { style: styles.textarea, placeholder: "Enter Chief Complaint or Appointment Notes", value: appointmentNotes, onChange: (e) => setAppointmentNotes(e.target.value) })] }), _jsx("div", { style: styles.footer, children: _jsx("button", { style: {
36
85
  ...styles.continueBtn,
37
86
  opacity: canContinue ? 1 : 0.6,
38
87
  }, disabled: !canContinue, onClick: () => onContinue(Number(selectedAddressId), Number(selectedDoctorId), appointmentNotes), children: "Next" }) })] })] }) }));
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useReducer, useCallback } from "react";
2
+ import React, { useReducer, useCallback } from "react";
3
3
  import { EnquiryService } from "../services/EnquiryService";
4
4
  import { validateName, validateEmail, validatePhoneNumber, validateSubject, validateMessage, validateCountryCode, } from "../enquiry-form/validation";
5
5
  import { ContactInformationStep } from "./ContactInformationStep";
@@ -100,7 +100,7 @@ export const EnquiryForm = ({ onSuccess, onError, }) => {
100
100
  if (!validateMessage(state.inquiryMessage)) {
101
101
  dispatch({
102
102
  type: "SET_ERROR",
103
- payload: "Please enter a valid message (max 1000 characters).",
103
+ payload: "Please enter a valid message.",
104
104
  });
105
105
  return false;
106
106
  }
@@ -151,9 +151,9 @@ export const EnquiryForm = ({ onSuccess, onError, }) => {
151
151
  finally {
152
152
  dispatch({ type: "SET_LOADING", payload: false });
153
153
  }
154
- }, [state, validateContactStep, validateInquiryStep, onSuccess, onError]);
154
+ }, [state, validateContactStep, validateInquiryStep]);
155
155
  const theme = useTheme();
156
- const styles = getStyles(theme);
156
+ const styles = React.useMemo(() => getStyles(theme), [theme]);
157
157
  return (_jsx("div", { style: styles.container, children: _jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("h2", { style: styles.title, children: "Submit Inquiry" }), _jsxs("p", { style: styles.stepIndicator, children: ["Step ", state.step + 1, " of 4"] })] }), _jsxs("div", { style: styles.content, children: [state.loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), state.error && _jsx("div", { style: styles.errorMessage, children: state.error }), state.step === 0 && (_jsx(ContactInformationStep, { patientName: state.patientName, patientEmail: state.patientEmail, countryCode: state.countryCode, patientPhone: state.patientPhone, onNameChange: (value) => dispatch({ type: "SET_PATIENT_NAME", payload: value }), onEmailChange: (value) => dispatch({ type: "SET_PATIENT_EMAIL", payload: value }), onCountryCodeChange: (value) => dispatch({ type: "SET_COUNTRY_CODE", payload: value }), onPhoneChange: (value) => dispatch({ type: "SET_PATIENT_PHONE", payload: value }), onNext: goToNext })), state.step === 1 && (_jsx(InquiryDetailsStep, { inquirySubject: state.inquirySubject, inquiryMessage: state.inquiryMessage, onSubjectChange: (value) => dispatch({ type: "SET_INQUIRY_SUBJECT", payload: value }), onMessageChange: (value) => dispatch({ type: "SET_INQUIRY_MESSAGE", payload: value }), onBack: goBack, onNext: goToNext })), state.step === 2 && (_jsx(ContactPreferenceStep, { preferredContactMethod: state.preferredContactMethod, onContactMethodChange: (method) => dispatch({
158
158
  type: "SET_PREFERRED_CONTACT_METHOD",
159
159
  payload: method,
@@ -1,15 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import { useTheme } from "../react/hooks/useTheme";
3
4
  import { getContainerStyles, getPhoneVerifyStyles, getButtonStyles, } from "./theme-styles";
4
- export const InquiryDetailsStep = ({ inquirySubject, inquiryMessage, onSubjectChange, onMessageChange, onBack, onNext, }) => {
5
+ export const InquiryDetailsStep = React.memo(({ inquirySubject, inquiryMessage, onSubjectChange, onMessageChange, onBack, onNext, }) => {
5
6
  const theme = useTheme();
6
- const PHONE_VERIFY_STYLES = getPhoneVerifyStyles(theme);
7
- const BUTTON_STYLES = getButtonStyles(theme);
8
- const CONTAINER_STYLES = getContainerStyles(theme);
9
- return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Subject" }), _jsx("input", { type: "text", value: inquirySubject, onChange: (e) => onSubjectChange(e.target.value), placeholder: "Enter inquiry subject", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsxs("label", { style: PHONE_VERIFY_STYLES.label, children: ["Message (", inquiryMessage.length, "/1000)"] }), _jsx("textarea", { value: inquiryMessage, onChange: (e) => onMessageChange(e.target.value), placeholder: "Enter your inquiry message", rows: 5, style: {
7
+ const PHONE_VERIFY_STYLES = React.useMemo(() => getPhoneVerifyStyles(theme), [theme]);
8
+ const BUTTON_STYLES = React.useMemo(() => getButtonStyles(theme), [theme]);
9
+ const CONTAINER_STYLES = React.useMemo(() => getContainerStyles(theme), [theme]);
10
+ return (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Subject" }), _jsx("input", { type: "text", value: inquirySubject, onChange: (e) => onSubjectChange(e.target.value), placeholder: "Enter inquiry subject", style: PHONE_VERIFY_STYLES.phoneInput })] }), _jsxs("div", { style: { marginBottom: "16px" }, children: [_jsx("label", { style: PHONE_VERIFY_STYLES.label, children: "Message" }), _jsx("textarea", { value: inquiryMessage, onChange: (e) => onMessageChange(e.target.value), placeholder: "Enter your inquiry message", rows: 5, style: {
10
11
  ...PHONE_VERIFY_STYLES.otpInput,
11
12
  minHeight: "120px",
12
13
  resize: "vertical",
13
14
  fontFamily: "inherit",
14
15
  } })] }), _jsxs("div", { style: CONTAINER_STYLES.actions, children: [_jsx("button", { onClick: onBack, style: BUTTON_STYLES.secondary, children: "Back" }), _jsx("button", { onClick: onNext, style: BUTTON_STYLES.primary, children: "Next" })] })] }));
15
- };
16
+ });
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface AppointmentCalenderProps {
3
+ onError?: (err: Error) => void;
4
+ }
5
+ export declare const AppointmentCalender: React.FC<AppointmentCalenderProps>;