medos-sdk 1.0.1 → 1.0.3
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/README.md +208 -73
- package/dist/client/MedosClient.d.ts +1 -1
- package/dist/client/MedosClient.js +1 -1
- package/dist/components/AppointmentCalender.d.ts +1 -4
- package/dist/components/AppointmentCalender.js +282 -486
- package/dist/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/components/AppointmentDateTimeModal.js +206 -0
- package/dist/components/ConfigurableCard.d.ts +12 -0
- package/dist/components/ConfigurableCard.js +29 -0
- package/dist/components/DoctorSelectModal.d.ts +7 -0
- package/dist/components/DoctorSelectModal.js +80 -0
- package/dist/components/Icons/Check.d.ts +6 -0
- package/dist/components/Icons/Check.js +2 -0
- package/dist/components/Icons/ChevronDownIcon.d.ts +4 -0
- package/dist/components/Icons/ChevronDownIcon.js +2 -0
- package/dist/components/Icons/ChevronLeft.d.ts +3 -0
- package/dist/components/Icons/ChevronLeft.js +3 -0
- package/dist/components/Icons/ChevronRight.d.ts +3 -0
- package/dist/components/Icons/ChevronRight.js +3 -0
- package/dist/components/Icons/ConfirmationCheck.d.ts +1 -0
- package/dist/components/Icons/ConfirmationCheck.js +9 -0
- package/dist/components/Icons/ConsultationType.d.ts +1 -0
- package/dist/components/Icons/ConsultationType.js +2 -0
- package/dist/components/Icons/Date&TimeIcon.d.ts +1 -0
- package/dist/components/Icons/Date&TimeIcon.js +2 -0
- package/dist/components/Icons/MapIcon.d.ts +1 -0
- package/dist/components/Icons/MapIcon.js +2 -0
- package/dist/components/Icons/PaymentMethodIcon.d.ts +1 -0
- package/dist/components/Icons/PaymentMethodIcon.js +2 -0
- package/dist/components/Icons/UserIcon.d.ts +1 -0
- package/dist/components/Icons/UserIcon.js +2 -0
- package/dist/components/PatientDetailsStep.d.ts +3 -0
- package/dist/components/PatientDetailsStep.js +76 -0
- package/dist/components/PhoneVerificationStep.d.ts +3 -0
- package/dist/components/PhoneVerificationStep.js +39 -0
- package/dist/components/SuccessStep.d.ts +3 -0
- package/dist/components/SuccessStep.js +17 -0
- package/dist/components/custom-calendar.d.ts +5 -0
- package/dist/components/custom-calendar.js +153 -0
- package/dist/components/styles.d.ts +6 -0
- package/dist/components/styles.js +257 -0
- package/dist/components/types.d.ts +182 -0
- package/dist/components/types.js +55 -0
- package/dist/components/ui/select.d.ts +10 -0
- package/dist/components/ui/select.js +21 -0
- package/dist/components/uiComponents/SelectDropdown.d.ts +41 -0
- package/dist/components/uiComponents/SelectDropdown.js +302 -0
- package/dist/components/utils.d.ts +5 -0
- package/dist/components/utils.js +15 -0
- package/dist/components/validation.d.ts +2 -0
- package/dist/components/validation.js +7 -0
- package/dist/context/TemplateContext.d.ts +12 -0
- package/dist/context/TemplateContext.js +19 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +8 -0
- package/dist/lib/templateUtils.d.ts +3 -0
- package/dist/lib/templateUtils.js +28 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/services/AppointmentService.d.ts +9 -10
- package/dist/services/AppointmentService.js +12 -10
- package/dist/templates/registry.d.ts +12 -0
- package/dist/templates/registry.js +58 -0
- package/dist/vanilla/AppointmentCalendarWidget.d.ts +41 -0
- package/dist/vanilla/AppointmentCalendarWidget.js +848 -0
- package/dist/vanilla/appointment-calendar/provider.d.ts +13 -0
- package/dist/vanilla/appointment-calendar/types.d.ts +144 -0
- package/dist/vanilla/appointments/provider.d.ts +13 -0
- package/dist/vanilla/appointments/types.d.ts +81 -0
- package/dist/vanilla/client/MedosClient.d.ts +32 -0
- package/dist/vanilla/components/AppointmentCalender.d.ts +3 -0
- package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +14 -0
- package/dist/vanilla/components/ConfigurableCard.d.ts +12 -0
- package/dist/vanilla/components/DoctorSelectModal.d.ts +7 -0
- package/dist/vanilla/components/Icons/Check.d.ts +6 -0
- package/dist/vanilla/components/Icons/ChevronDownIcon.d.ts +4 -0
- package/dist/vanilla/components/Icons/ChevronLeft.d.ts +3 -0
- package/dist/vanilla/components/Icons/ChevronRight.d.ts +3 -0
- package/dist/vanilla/components/Icons/ConfirmationCheck.d.ts +1 -0
- package/dist/vanilla/components/Icons/ConsultationType.d.ts +1 -0
- package/dist/vanilla/components/Icons/Date&TimeIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/MapIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/PaymentMethodIcon.d.ts +1 -0
- package/dist/vanilla/components/Icons/UserIcon.d.ts +1 -0
- package/dist/vanilla/components/PatientDetailsStep.d.ts +3 -0
- package/dist/vanilla/components/PhoneVerificationStep.d.ts +3 -0
- package/dist/vanilla/components/SuccessStep.d.ts +3 -0
- package/dist/vanilla/components/custom-calendar.d.ts +5 -0
- package/dist/vanilla/components/styles.d.ts +6 -0
- package/dist/vanilla/components/types.d.ts +182 -0
- package/dist/vanilla/components/ui/select.d.ts +10 -0
- package/dist/vanilla/components/uiComponents/SelectDropdown.d.ts +41 -0
- package/dist/vanilla/components/utils.d.ts +5 -0
- package/dist/vanilla/components/validation.d.ts +2 -0
- package/dist/vanilla/context/TemplateContext.d.ts +12 -0
- package/dist/vanilla/core/index.d.ts +8 -0
- package/dist/vanilla/index.d.ts +7 -0
- package/dist/vanilla/index.js +2 -0
- package/dist/vanilla/lib/templateUtils.d.ts +3 -0
- package/dist/vanilla/react/index.d.ts +2 -0
- package/dist/vanilla/services/AppointmentService.d.ts +85 -0
- package/dist/vanilla/services/AuthService.d.ts +6 -0
- package/dist/vanilla/services/PatientService.d.ts +14 -0
- package/dist/vanilla/templates/alternative.css +13 -0
- package/dist/vanilla/templates/default.css +13 -0
- package/dist/vanilla/templates/registry.d.ts +12 -0
- package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +41 -0
- package/dist/vanilla/vanilla/index.d.ts +2 -0
- package/dist/vanilla/vanilla/widget.d.ts +10 -0
- package/dist/vanilla/widget.css +217 -0
- package/dist/vanilla/widget.d.ts +10 -0
- package/dist/vanilla/widget.js +5084 -0
- package/package.json +40 -5
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
import { AppointmentService, } from "../services/AppointmentService";
|
|
2
|
+
import { PatientService } from "../services/PatientService";
|
|
3
|
+
import { MedosClient } from "../client/MedosClient";
|
|
4
|
+
import { INITIAL_STATE, } from "../components/types";
|
|
5
|
+
import { validatePhoneNumber, validateCountryCode, } from "../components/validation";
|
|
6
|
+
import { formatDateToISO, parsePatientName } from "../components/utils";
|
|
7
|
+
class AppointmentCalendarWidget {
|
|
8
|
+
constructor(container, options) {
|
|
9
|
+
this.mounted = true;
|
|
10
|
+
this.doctors = [];
|
|
11
|
+
if (typeof container === "string") {
|
|
12
|
+
const el = document.getElementById(container);
|
|
13
|
+
if (!el) {
|
|
14
|
+
throw new Error(`Container element with id "${container}" not found`);
|
|
15
|
+
}
|
|
16
|
+
this.container = el;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.container = container;
|
|
20
|
+
}
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.state = { ...INITIAL_STATE };
|
|
23
|
+
this.init();
|
|
24
|
+
}
|
|
25
|
+
async init() {
|
|
26
|
+
if (this.options.apiKey) {
|
|
27
|
+
await MedosClient.init({
|
|
28
|
+
apiKey: this.options.apiKey,
|
|
29
|
+
baseURL: this.options.baseURL,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (this.options.sessionToken) {
|
|
33
|
+
await MedosClient.initWithSession({
|
|
34
|
+
sessionToken: this.options.sessionToken,
|
|
35
|
+
baseURL: this.options.baseURL,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new Error("Either apiKey or sessionToken must be provided");
|
|
40
|
+
}
|
|
41
|
+
await this.loadAddresses();
|
|
42
|
+
this.render();
|
|
43
|
+
}
|
|
44
|
+
async loadAddresses() {
|
|
45
|
+
this.setState({ loading: true, error: null });
|
|
46
|
+
try {
|
|
47
|
+
const addrResp = await AppointmentService.getAddresses();
|
|
48
|
+
if (addrResp && Array.isArray(addrResp.addresses)) {
|
|
49
|
+
const fetchedAddresses = addrResp.addresses;
|
|
50
|
+
if (addrResp.workspaceId) {
|
|
51
|
+
this.state.workspaceId = addrResp.workspaceId;
|
|
52
|
+
}
|
|
53
|
+
if (fetchedAddresses.length > 0) {
|
|
54
|
+
const addrMap = {};
|
|
55
|
+
const mappedAddrs = fetchedAddresses.map((a, idx) => {
|
|
56
|
+
const id = Number(a.id ?? idx);
|
|
57
|
+
const label = a.completeAddress ?? a.label ?? a.address ?? `Address ${idx + 1}`;
|
|
58
|
+
const docs = Array.isArray(a.doctors)
|
|
59
|
+
? a.doctors
|
|
60
|
+
: [];
|
|
61
|
+
addrMap[id] = docs || [];
|
|
62
|
+
return { id, label };
|
|
63
|
+
});
|
|
64
|
+
this.state.addresses = mappedAddrs;
|
|
65
|
+
this.state.addressDoctorsMap = addrMap;
|
|
66
|
+
const anyDoctorsExist = Object.values(addrMap).some((arr) => Array.isArray(arr) && arr.length > 0);
|
|
67
|
+
if (mappedAddrs.length === 1) {
|
|
68
|
+
const only = mappedAddrs[0];
|
|
69
|
+
this.state.selectedAddress = only.id;
|
|
70
|
+
const docsForAddr = addrMap[only.id] || [];
|
|
71
|
+
if (docsForAddr.length > 0) {
|
|
72
|
+
this.doctors = docsForAddr;
|
|
73
|
+
if (docsForAddr.length === 1) {
|
|
74
|
+
this.state.selectedDoctor = docsForAddr[0].id;
|
|
75
|
+
this.state.step = 1;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.state.step = 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (anyDoctorsExist) {
|
|
83
|
+
this.setState({
|
|
84
|
+
error: "No doctors at this address. Please choose a different address.",
|
|
85
|
+
});
|
|
86
|
+
this.doctors = [];
|
|
87
|
+
this.state.step = 0;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.setState({
|
|
91
|
+
error: "No doctors available for the selected location(s).",
|
|
92
|
+
});
|
|
93
|
+
this.doctors = [];
|
|
94
|
+
this.state.step = 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.state.step = 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.setState({ error: "No addresses or doctors available." });
|
|
104
|
+
this.state.addresses = [];
|
|
105
|
+
this.state.addressDoctorsMap = {};
|
|
106
|
+
this.doctors = [];
|
|
107
|
+
this.state.step = 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const msg = e.message || "Failed to load addresses";
|
|
113
|
+
this.setState({ error: msg });
|
|
114
|
+
this.options.onError?.(e);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.setState({ loading: false });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async handleAddressChange(addressId) {
|
|
121
|
+
this.state.selectedAddress = addressId;
|
|
122
|
+
if (!addressId) {
|
|
123
|
+
this.doctors = [];
|
|
124
|
+
this.state.selectedDoctor = null;
|
|
125
|
+
this.render();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.setState({ loading: true, error: null });
|
|
129
|
+
try {
|
|
130
|
+
const docsForAddr = this.state.addressDoctorsMap[addressId] ?? [];
|
|
131
|
+
if (docsForAddr.length > 0) {
|
|
132
|
+
this.doctors = docsForAddr;
|
|
133
|
+
if (docsForAddr.length === 1) {
|
|
134
|
+
this.state.selectedDoctor = docsForAddr[0].id;
|
|
135
|
+
this.state.step = 1;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.state.selectedDoctor = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const otherHasDoctors = Object.entries(this.state.addressDoctorsMap).some(([key, docs]) => Number(key) !== addressId && Array.isArray(docs) && docs.length > 0);
|
|
143
|
+
this.doctors = [];
|
|
144
|
+
this.state.selectedDoctor = null;
|
|
145
|
+
if (otherHasDoctors) {
|
|
146
|
+
this.setState({
|
|
147
|
+
error: "No doctors at this address. Please select a different address.",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.setState({
|
|
152
|
+
error: "No doctors available for the selected location(s).",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
this.setState({
|
|
159
|
+
error: e.message || "Failed to load doctors for address",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
this.setState({ loading: false });
|
|
164
|
+
this.render();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async loadSlots() {
|
|
168
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
169
|
+
if (!this.state.workspaceId ||
|
|
170
|
+
!this.state.selectedAddress ||
|
|
171
|
+
!this.state.selectedDoctor ||
|
|
172
|
+
!dateStr) {
|
|
173
|
+
this.state.slots = [];
|
|
174
|
+
this.state.selectedSlot = null;
|
|
175
|
+
this.render();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.setState({ loading: true, error: null });
|
|
179
|
+
try {
|
|
180
|
+
const s = await AppointmentService.fetchSlots(this.state.workspaceId, this.state.selectedAddress, this.state.selectedDoctor, dateStr);
|
|
181
|
+
this.state.slots = s || [];
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
this.setState({ error: e.message || "Failed to load slots" });
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
this.setState({ loading: false });
|
|
188
|
+
this.render();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
canProceedFromMergedStep() {
|
|
192
|
+
if (this.state.addresses.length === 0 || this.doctors.length === 0)
|
|
193
|
+
return false;
|
|
194
|
+
if (this.state.addresses.length > 1 && !this.state.selectedAddress)
|
|
195
|
+
return false;
|
|
196
|
+
if (this.doctors.length > 1 && !this.state.selectedDoctor)
|
|
197
|
+
return false;
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
async sendOtp() {
|
|
201
|
+
this.setState({ error: null });
|
|
202
|
+
if (!this.state.countryCode) {
|
|
203
|
+
this.setState({ error: "Please enter country code." });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!validateCountryCode(this.state.countryCode)) {
|
|
207
|
+
this.setState({
|
|
208
|
+
error: "Please enter a valid country code (e.g., +91, +1).",
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!this.state.patientPhone) {
|
|
213
|
+
this.setState({ error: "Please enter phone number." });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!validatePhoneNumber(this.state.patientPhone)) {
|
|
217
|
+
this.setState({
|
|
218
|
+
error: "Please enter a valid phone number (7-15 digits).",
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.setState({ otpSending: true });
|
|
223
|
+
this.render();
|
|
224
|
+
try {
|
|
225
|
+
await PatientService.sendPhoneVerificationOtp({
|
|
226
|
+
countryCode: this.state.countryCode,
|
|
227
|
+
phoneNumber: this.state.patientPhone,
|
|
228
|
+
});
|
|
229
|
+
this.setState({ otpSent: true, error: null });
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
const msg = e.message || "Failed to send OTP";
|
|
233
|
+
this.setState({ error: msg });
|
|
234
|
+
this.options.onError?.(e);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
this.setState({ otpSending: false });
|
|
238
|
+
this.render();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async verifyOtp() {
|
|
242
|
+
this.setState({ error: null });
|
|
243
|
+
if (!this.state.countryCode ||
|
|
244
|
+
!this.state.patientPhone ||
|
|
245
|
+
!this.state.otpCode) {
|
|
246
|
+
this.setState({ error: "Please enter all required fields." });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (this.state.otpCode.length !== 6) {
|
|
250
|
+
this.setState({ error: "Please enter a 6-digit OTP code." });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.setState({ otpVerifying: true });
|
|
254
|
+
this.render();
|
|
255
|
+
try {
|
|
256
|
+
await PatientService.verifyPhoneVerificationOtp({
|
|
257
|
+
countryCode: this.state.countryCode,
|
|
258
|
+
phoneNumber: this.state.patientPhone,
|
|
259
|
+
otpCode: this.state.otpCode,
|
|
260
|
+
});
|
|
261
|
+
this.setState({ otpVerified: true, error: null });
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
const msg = e.message || "Invalid OTP code";
|
|
265
|
+
this.setState({ error: msg });
|
|
266
|
+
this.options.onError?.(e);
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
this.setState({ otpVerifying: false });
|
|
270
|
+
this.render();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async submitAppointment() {
|
|
274
|
+
this.setState({ error: null });
|
|
275
|
+
if (!this.state.selectedDoctor ||
|
|
276
|
+
!this.state.selectedSlot ||
|
|
277
|
+
!this.state.workspaceId ||
|
|
278
|
+
!this.state.selectedAddress ||
|
|
279
|
+
!this.state.patientAddress ||
|
|
280
|
+
!this.state.patientCity ||
|
|
281
|
+
!this.state.patientState ||
|
|
282
|
+
!this.state.patientCountry ||
|
|
283
|
+
!this.state.patientZipcode) {
|
|
284
|
+
this.setState({
|
|
285
|
+
error: "Please ensure all required fields are complete.",
|
|
286
|
+
});
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!this.state.otpVerified) {
|
|
290
|
+
this.setState({ error: "Please verify your phone number first." });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
this.setState({ loading: true });
|
|
294
|
+
this.render();
|
|
295
|
+
try {
|
|
296
|
+
const { firstName, lastName } = parsePatientName(this.state.patientName || "Patient");
|
|
297
|
+
const startDate = new Date(this.state.selectedSlot.start);
|
|
298
|
+
const endDate = new Date(this.state.selectedSlot.end);
|
|
299
|
+
const appointmentDate = formatDateToISO(startDate);
|
|
300
|
+
const formatTime = (date) => {
|
|
301
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
302
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
303
|
+
return `${hours}:${minutes}`;
|
|
304
|
+
};
|
|
305
|
+
const fromDateTimeTs = formatTime(startDate);
|
|
306
|
+
const toDateTimeTs = formatTime(endDate);
|
|
307
|
+
const patientAddressPayload = {
|
|
308
|
+
addressLine1: this.state.patientAddress,
|
|
309
|
+
city: this.state.patientCity,
|
|
310
|
+
state: this.state.patientState,
|
|
311
|
+
country: this.state.patientCountry,
|
|
312
|
+
zipcode: this.state.patientZipcode,
|
|
313
|
+
landmark: this.state.patientLandmark || undefined,
|
|
314
|
+
};
|
|
315
|
+
await AppointmentService.createAppointment({
|
|
316
|
+
workspaceId: this.state.workspaceId,
|
|
317
|
+
workspaceAddressId: this.state.selectedAddress,
|
|
318
|
+
doctorId: this.state.selectedDoctor,
|
|
319
|
+
mode: "OFFLINE",
|
|
320
|
+
appointmentDate,
|
|
321
|
+
fromDateTimeTs,
|
|
322
|
+
toDateTimeTs,
|
|
323
|
+
consultationCharge: this.state.consultationCharge || "0",
|
|
324
|
+
type: "CONSULTATION",
|
|
325
|
+
source: "SDK_POWERED_WEBSITE",
|
|
326
|
+
patientPayload: {
|
|
327
|
+
firstName,
|
|
328
|
+
lastName,
|
|
329
|
+
email: this.state.patientEmail || undefined,
|
|
330
|
+
countryCode: this.state.countryCode,
|
|
331
|
+
phoneNumber: this.state.patientPhone,
|
|
332
|
+
age: this.state.patientAge
|
|
333
|
+
? parseInt(this.state.patientAge, 10)
|
|
334
|
+
: undefined,
|
|
335
|
+
gender: this.state.patientGender
|
|
336
|
+
? this.state.patientGender.toUpperCase()
|
|
337
|
+
: undefined,
|
|
338
|
+
},
|
|
339
|
+
patientAddress: patientAddressPayload,
|
|
340
|
+
});
|
|
341
|
+
this.state.step = 5;
|
|
342
|
+
this.options.onSuccess?.();
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
const msg = e.message || "Failed to create appointment";
|
|
346
|
+
this.setState({ error: msg });
|
|
347
|
+
this.options.onError?.(e);
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
this.setState({ loading: false });
|
|
351
|
+
this.render();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
goToNext() {
|
|
355
|
+
if (this.state.step === 0) {
|
|
356
|
+
if (this.state.addresses.length > 1 && !this.state.selectedAddress)
|
|
357
|
+
return;
|
|
358
|
+
if (this.doctors.length > 1 && !this.state.selectedDoctor)
|
|
359
|
+
return;
|
|
360
|
+
this.state.step = 1;
|
|
361
|
+
this.render();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
365
|
+
if (this.state.step === 1 && dateStr) {
|
|
366
|
+
this.state.step = 2;
|
|
367
|
+
this.loadSlots();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (this.state.step === 2 && this.state.selectedSlot) {
|
|
371
|
+
this.state.step = 3;
|
|
372
|
+
this.render();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (this.state.step === 3 && this.state.otpVerified) {
|
|
376
|
+
this.state.step = 4;
|
|
377
|
+
this.render();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.state.step = Math.min(5, this.state.step + 1);
|
|
381
|
+
this.render();
|
|
382
|
+
}
|
|
383
|
+
goBack() {
|
|
384
|
+
this.state.step = Math.max(0, this.state.step - 1);
|
|
385
|
+
this.render();
|
|
386
|
+
}
|
|
387
|
+
reset() {
|
|
388
|
+
this.state = { ...INITIAL_STATE };
|
|
389
|
+
this.doctors = [];
|
|
390
|
+
this.render();
|
|
391
|
+
}
|
|
392
|
+
setState(updates) {
|
|
393
|
+
this.state = { ...this.state, ...updates };
|
|
394
|
+
}
|
|
395
|
+
render() {
|
|
396
|
+
if (!this.mounted)
|
|
397
|
+
return;
|
|
398
|
+
this.container.innerHTML = `
|
|
399
|
+
<div class="medos-appointment-container">
|
|
400
|
+
<div class="medos-appointment-card">
|
|
401
|
+
<div class="medos-appointment-header">
|
|
402
|
+
<h2 class="medos-appointment-title">Book Appointment</h2>
|
|
403
|
+
<div class="medos-appointment-stepper">
|
|
404
|
+
<div class="medos-appointment-step-pill ${this.state.step === 0 ? "active" : ""}">1 Address</div>
|
|
405
|
+
<div class="medos-appointment-step-pill ${this.state.step === 1 ? "active" : ""}">2 Date</div>
|
|
406
|
+
<div class="medos-appointment-step-pill ${this.state.step === 2 ? "active" : ""}">3 Slot</div>
|
|
407
|
+
<div class="medos-appointment-step-pill ${this.state.step === 3 ? "active" : ""}">4 Phone</div>
|
|
408
|
+
<div class="medos-appointment-step-pill ${this.state.step === 4 ? "active" : ""}">5 Details</div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
${this.state.loading
|
|
413
|
+
? '<div class="medos-appointment-loading">Loading...</div>'
|
|
414
|
+
: ""}
|
|
415
|
+
${this.state.error
|
|
416
|
+
? `<div class="medos-appointment-error">${this.escapeHtml(this.state.error)}</div>`
|
|
417
|
+
: ""}
|
|
418
|
+
|
|
419
|
+
${this.renderStep()}
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
`;
|
|
423
|
+
this.attachEventListeners();
|
|
424
|
+
}
|
|
425
|
+
renderStep() {
|
|
426
|
+
switch (this.state.step) {
|
|
427
|
+
case 0:
|
|
428
|
+
return this.renderStep0();
|
|
429
|
+
case 1:
|
|
430
|
+
return this.renderStep1();
|
|
431
|
+
case 2:
|
|
432
|
+
return this.renderStep2();
|
|
433
|
+
case 3:
|
|
434
|
+
return this.renderStep3();
|
|
435
|
+
case 4:
|
|
436
|
+
return this.renderStep4();
|
|
437
|
+
case 5:
|
|
438
|
+
return this.renderStep5();
|
|
439
|
+
default:
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
renderStep0() {
|
|
444
|
+
const canProceed = this.canProceedFromMergedStep();
|
|
445
|
+
return `
|
|
446
|
+
<div class="medos-appointment-section">
|
|
447
|
+
<div class="medos-appointment-form-grid-2col">
|
|
448
|
+
<div>
|
|
449
|
+
<label class="medos-appointment-label">Address</label>
|
|
450
|
+
${this.state.addresses.length === 0
|
|
451
|
+
? '<div class="medos-appointment-small-muted">No addresses available</div>'
|
|
452
|
+
: this.state.addresses.length === 1
|
|
453
|
+
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.state.addresses[0].label || "")}</div>`
|
|
454
|
+
: `
|
|
455
|
+
<select class="medos-appointment-select" id="medos-address-select">
|
|
456
|
+
<option value="">-- choose address --</option>
|
|
457
|
+
${this.state.addresses
|
|
458
|
+
.map((a) => `<option value="${this.escapeHtml(a.id.toString())}" ${this.state.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label || "")}</option>`)
|
|
459
|
+
.join("")}
|
|
460
|
+
</select>
|
|
461
|
+
`}
|
|
462
|
+
</div>
|
|
463
|
+
<div>
|
|
464
|
+
<label class="medos-appointment-label">Doctor</label>
|
|
465
|
+
${this.doctors.length === 0
|
|
466
|
+
? '<div class="medos-appointment-small-muted">No doctors available</div>'
|
|
467
|
+
: this.doctors.length === 1
|
|
468
|
+
? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
|
|
469
|
+
? ` • ${this.escapeHtml(this.doctors[0].specialty)}`
|
|
470
|
+
: ""}</div>`
|
|
471
|
+
: `
|
|
472
|
+
<select class="medos-appointment-select" id="medos-doctor-select">
|
|
473
|
+
<option value="">-- choose doctor --</option>
|
|
474
|
+
${this.doctors
|
|
475
|
+
.map((d) => `<option value="${this.escapeHtml(d.id.toString())}" ${this.state.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty
|
|
476
|
+
? ` (${this.escapeHtml(d.specialty)})`
|
|
477
|
+
: ""}</option>`)
|
|
478
|
+
.join("")}
|
|
479
|
+
</select>
|
|
480
|
+
`}
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="medos-appointment-actions">
|
|
484
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-cancel">Cancel</button>
|
|
485
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!canProceed ? "disabled" : ""} style="opacity: ${canProceed ? 1 : 0.6}">Next</button>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
renderStep1() {
|
|
491
|
+
const dateStr = formatDateToISO(this.state.selectedDate);
|
|
492
|
+
return `
|
|
493
|
+
<div class="medos-appointment-section">
|
|
494
|
+
<label class="medos-appointment-label">Select Date</label>
|
|
495
|
+
<input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(dateStr)}" />
|
|
496
|
+
<div class="medos-appointment-actions">
|
|
497
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
498
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!dateStr ? "disabled" : ""} style="opacity: ${dateStr ? 1 : 0.6}">Next</button>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
renderStep2() {
|
|
504
|
+
return `
|
|
505
|
+
<div class="medos-appointment-section">
|
|
506
|
+
<label class="medos-appointment-label">Choose Time Slot</label>
|
|
507
|
+
${this.state.slots.length === 0
|
|
508
|
+
? '<div class="medos-appointment-small-muted">No slots available for selected date</div>'
|
|
509
|
+
: `
|
|
510
|
+
<div class="medos-appointment-slot-grid">
|
|
511
|
+
${this.state.slots
|
|
512
|
+
.map((s) => {
|
|
513
|
+
const start = new Date(s.start).toLocaleTimeString([], {
|
|
514
|
+
hour: "2-digit",
|
|
515
|
+
minute: "2-digit",
|
|
516
|
+
});
|
|
517
|
+
const end = new Date(s.end).toLocaleTimeString([], {
|
|
518
|
+
hour: "2-digit",
|
|
519
|
+
minute: "2-digit",
|
|
520
|
+
});
|
|
521
|
+
const selected = this.state.selectedSlot?.start === s.start &&
|
|
522
|
+
this.state.selectedSlot?.end === s.end;
|
|
523
|
+
return `
|
|
524
|
+
<div class="medos-appointment-slot-card ${selected ? "selected" : ""}" data-slot-id="${this.escapeHtml(s.id || `${s.start}-${s.end}`)}" data-slot-start="${this.escapeHtml(s.start)}" data-slot-end="${this.escapeHtml(s.end)}">
|
|
525
|
+
<div class="medos-appointment-slot-time">${start} — ${end}</div>
|
|
526
|
+
</div>
|
|
527
|
+
`;
|
|
528
|
+
})
|
|
529
|
+
.join("")}
|
|
530
|
+
</div>
|
|
531
|
+
`}
|
|
532
|
+
<div class="medos-appointment-actions">
|
|
533
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
534
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.state.selectedSlot ? "disabled" : ""} style="opacity: ${this.state.selectedSlot ? 1 : 0.6}">Next</button>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
renderStep3() {
|
|
540
|
+
const countryCodeValid = this.state.countryCode && validateCountryCode(this.state.countryCode);
|
|
541
|
+
const phoneValid = this.state.patientPhone && validatePhoneNumber(this.state.patientPhone);
|
|
542
|
+
const canSendOtp = countryCodeValid && phoneValid && !this.state.otpSending;
|
|
543
|
+
if (!this.state.otpSent) {
|
|
544
|
+
return `
|
|
545
|
+
<div class="medos-appointment-section">
|
|
546
|
+
<label class="medos-appointment-label">Country Code</label>
|
|
547
|
+
<input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.state.countryCode)}" />
|
|
548
|
+
${this.state.countryCode && !countryCodeValid
|
|
549
|
+
? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>'
|
|
550
|
+
: ""}
|
|
551
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Phone Number</label>
|
|
552
|
+
<input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.state.patientPhone)}" maxlength="15" />
|
|
553
|
+
${this.state.patientPhone && !phoneValid
|
|
554
|
+
? '<div class="medos-appointment-validation-error">Phone number should be 7-15 digits</div>'
|
|
555
|
+
: ""}
|
|
556
|
+
<div class="medos-appointment-actions">
|
|
557
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
558
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-send-otp" ${!canSendOtp ? "disabled" : ""} style="opacity: ${canSendOtp ? 1 : 0.6}">${this.state.otpSending ? "Sending..." : "Send OTP"}</button>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
`;
|
|
562
|
+
}
|
|
563
|
+
if (this.state.otpVerified) {
|
|
564
|
+
return `
|
|
565
|
+
<div class="medos-appointment-section">
|
|
566
|
+
<div class="medos-appointment-verified">✓ Phone verified successfully</div>
|
|
567
|
+
<div class="medos-appointment-actions">
|
|
568
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
569
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next">Continue to Details</button>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
`;
|
|
573
|
+
}
|
|
574
|
+
return `
|
|
575
|
+
<div class="medos-appointment-section">
|
|
576
|
+
<label class="medos-appointment-label">Enter OTP</label>
|
|
577
|
+
<input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.state.otpCode)}" maxlength="6" />
|
|
578
|
+
<div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
|
|
579
|
+
<div class="medos-appointment-actions">
|
|
580
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-change-number">Change Number</button>
|
|
581
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length !== 6 || this.state.otpVerifying
|
|
582
|
+
? "disabled"
|
|
583
|
+
: ""} style="opacity: ${this.state.otpCode.length === 6 && !this.state.otpVerifying ? 1 : 0.6}">${this.state.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
`;
|
|
587
|
+
}
|
|
588
|
+
renderStep4() {
|
|
589
|
+
const canSubmit = this.state.patientName &&
|
|
590
|
+
this.state.patientAddress &&
|
|
591
|
+
this.state.patientCity &&
|
|
592
|
+
this.state.patientState &&
|
|
593
|
+
this.state.patientCountry &&
|
|
594
|
+
this.state.patientZipcode &&
|
|
595
|
+
this.state.otpVerified;
|
|
596
|
+
return `
|
|
597
|
+
<div class="medos-appointment-section">
|
|
598
|
+
<label class="medos-appointment-label">Patient Name</label>
|
|
599
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.state.patientName)}" />
|
|
600
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Age</label>
|
|
601
|
+
<input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
|
|
602
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Email (Optional)</label>
|
|
603
|
+
<input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
|
|
604
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Gender (Optional)</label>
|
|
605
|
+
<select class="medos-appointment-select" id="medos-patient-gender">
|
|
606
|
+
<option value="">-- Select Gender --</option>
|
|
607
|
+
<option value="MALE" ${this.state.patientGender === "MALE" ? "selected" : ""}>Male</option>
|
|
608
|
+
<option value="FEMALE" ${this.state.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
|
|
609
|
+
<option value="OTHER" ${this.state.patientGender === "OTHER" ? "selected" : ""}>Other</option>
|
|
610
|
+
</select>
|
|
611
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Address Line 1 *</label>
|
|
612
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
|
|
613
|
+
<div class="medos-appointment-form-grid">
|
|
614
|
+
<div>
|
|
615
|
+
<label class="medos-appointment-label">City *</label>
|
|
616
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.state.patientCity)}" />
|
|
617
|
+
</div>
|
|
618
|
+
<div>
|
|
619
|
+
<label class="medos-appointment-label">State *</label>
|
|
620
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.state.patientState)}" />
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
<div class="medos-appointment-form-grid">
|
|
624
|
+
<div>
|
|
625
|
+
<label class="medos-appointment-label">Country *</label>
|
|
626
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
|
|
627
|
+
</div>
|
|
628
|
+
<div>
|
|
629
|
+
<label class="medos-appointment-label">Zipcode *</label>
|
|
630
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.state.patientZipcode)}" />
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Landmark (Optional)</label>
|
|
634
|
+
<input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
|
|
635
|
+
<label class="medos-appointment-label" style="margin-top: 12px">Problem Facing</label>
|
|
636
|
+
<textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.state.patientName)}</textarea>
|
|
637
|
+
<div class="medos-appointment-actions">
|
|
638
|
+
<button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
|
|
639
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-submit" ${!canSubmit || this.state.loading ? "disabled" : ""} style="opacity: ${canSubmit && !this.state.loading ? 1 : 0.6}">${this.state.loading ? "Booking..." : "Book Appointment"}</button>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
`;
|
|
643
|
+
}
|
|
644
|
+
renderStep5() {
|
|
645
|
+
return `
|
|
646
|
+
<div class="medos-appointment-section" style="text-align: center">
|
|
647
|
+
<div class="medos-appointment-success-card">
|
|
648
|
+
<div class="medos-appointment-success-icon">✓</div>
|
|
649
|
+
<div class="medos-appointment-success-title">Appointment Confirmed</div>
|
|
650
|
+
<div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.state.patientName || "Patient")}. Your appointment is confirmed.</div>
|
|
651
|
+
</div>
|
|
652
|
+
<div style="margin-top: 14px; display: flex; justify-content: center">
|
|
653
|
+
<button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-book-another" style="width: 160px">Book Another</button>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
`;
|
|
657
|
+
}
|
|
658
|
+
attachEventListeners() {
|
|
659
|
+
const addressSelect = this.container.querySelector("#medos-address-select");
|
|
660
|
+
if (addressSelect) {
|
|
661
|
+
addressSelect.addEventListener("change", (e) => {
|
|
662
|
+
const target = e.target;
|
|
663
|
+
this.handleAddressChange(Number(target.value) || null);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
const doctorSelect = this.container.querySelector("#medos-doctor-select");
|
|
667
|
+
if (doctorSelect) {
|
|
668
|
+
doctorSelect.addEventListener("change", (e) => {
|
|
669
|
+
const target = e.target;
|
|
670
|
+
this.state.selectedDoctor = Number(target.value) || null;
|
|
671
|
+
this.render();
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
const dateInput = this.container.querySelector("#medos-date-input");
|
|
675
|
+
if (dateInput) {
|
|
676
|
+
dateInput.addEventListener("change", (e) => {
|
|
677
|
+
const target = e.target;
|
|
678
|
+
this.state.selectedDate = new Date(target.value);
|
|
679
|
+
this.render();
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const slotCards = this.container.querySelectorAll(".medos-appointment-slot-card");
|
|
683
|
+
slotCards.forEach((card) => {
|
|
684
|
+
card.addEventListener("click", () => {
|
|
685
|
+
const slotId = card.getAttribute("data-slot-id");
|
|
686
|
+
const slotStart = card.getAttribute("data-slot-start");
|
|
687
|
+
const slotEnd = card.getAttribute("data-slot-end");
|
|
688
|
+
if (slotStart && slotEnd) {
|
|
689
|
+
this.state.selectedSlot = {
|
|
690
|
+
start: slotStart,
|
|
691
|
+
end: slotEnd,
|
|
692
|
+
id: slotId || undefined,
|
|
693
|
+
};
|
|
694
|
+
this.render();
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
const countryCodeInput = this.container.querySelector("#medos-country-code");
|
|
699
|
+
if (countryCodeInput) {
|
|
700
|
+
countryCodeInput.addEventListener("input", (e) => {
|
|
701
|
+
const target = e.target;
|
|
702
|
+
let value = target.value;
|
|
703
|
+
if (value && !value.startsWith("+")) {
|
|
704
|
+
value = "+" + value;
|
|
705
|
+
}
|
|
706
|
+
value = value.replace(/[^\d+]/g, "");
|
|
707
|
+
this.state.countryCode = value;
|
|
708
|
+
target.value = value;
|
|
709
|
+
this.render();
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
const phoneInput = this.container.querySelector("#medos-phone");
|
|
713
|
+
if (phoneInput) {
|
|
714
|
+
phoneInput.addEventListener("input", (e) => {
|
|
715
|
+
const target = e.target;
|
|
716
|
+
this.state.patientPhone = target.value.replace(/\D/g, "");
|
|
717
|
+
target.value = this.state.patientPhone;
|
|
718
|
+
this.render();
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
const otpInput = this.container.querySelector("#medos-otp");
|
|
722
|
+
if (otpInput) {
|
|
723
|
+
otpInput.addEventListener("input", (e) => {
|
|
724
|
+
const target = e.target;
|
|
725
|
+
this.state.otpCode = target.value;
|
|
726
|
+
this.render();
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const patientNameInput = this.container.querySelector("#medos-patient-name");
|
|
730
|
+
if (patientNameInput) {
|
|
731
|
+
patientNameInput.addEventListener("input", (e) => {
|
|
732
|
+
const target = e.target;
|
|
733
|
+
this.state.patientName = target.value;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
const patientAgeInput = this.container.querySelector("#medos-patient-age");
|
|
737
|
+
if (patientAgeInput) {
|
|
738
|
+
patientAgeInput.addEventListener("input", (e) => {
|
|
739
|
+
const target = e.target;
|
|
740
|
+
this.state.patientAge = target.value;
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
const patientEmailInput = this.container.querySelector("#medos-patient-email");
|
|
744
|
+
if (patientEmailInput) {
|
|
745
|
+
patientEmailInput.addEventListener("input", (e) => {
|
|
746
|
+
const target = e.target;
|
|
747
|
+
this.state.patientEmail = target.value;
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
const patientGenderSelect = this.container.querySelector("#medos-patient-gender");
|
|
751
|
+
if (patientGenderSelect) {
|
|
752
|
+
patientGenderSelect.addEventListener("change", (e) => {
|
|
753
|
+
const target = e.target;
|
|
754
|
+
this.state.patientGender = target.value;
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
const patientAddressInput = this.container.querySelector("#medos-patient-address");
|
|
758
|
+
if (patientAddressInput) {
|
|
759
|
+
patientAddressInput.addEventListener("input", (e) => {
|
|
760
|
+
const target = e.target;
|
|
761
|
+
this.state.patientAddress = target.value;
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
const patientCityInput = this.container.querySelector("#medos-patient-city");
|
|
765
|
+
if (patientCityInput) {
|
|
766
|
+
patientCityInput.addEventListener("input", (e) => {
|
|
767
|
+
const target = e.target;
|
|
768
|
+
this.state.patientCity = target.value;
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
const patientStateInput = this.container.querySelector("#medos-patient-state");
|
|
772
|
+
if (patientStateInput) {
|
|
773
|
+
patientStateInput.addEventListener("input", (e) => {
|
|
774
|
+
const target = e.target;
|
|
775
|
+
this.state.patientState = target.value;
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
const patientCountryInput = this.container.querySelector("#medos-patient-country");
|
|
779
|
+
if (patientCountryInput) {
|
|
780
|
+
patientCountryInput.addEventListener("input", (e) => {
|
|
781
|
+
const target = e.target;
|
|
782
|
+
this.state.patientCountry = target.value;
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const patientZipcodeInput = this.container.querySelector("#medos-patient-zipcode");
|
|
786
|
+
if (patientZipcodeInput) {
|
|
787
|
+
patientZipcodeInput.addEventListener("input", (e) => {
|
|
788
|
+
const target = e.target;
|
|
789
|
+
this.state.patientZipcode = target.value;
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const patientLandmarkInput = this.container.querySelector("#medos-patient-landmark");
|
|
793
|
+
if (patientLandmarkInput) {
|
|
794
|
+
patientLandmarkInput.addEventListener("input", (e) => {
|
|
795
|
+
const target = e.target;
|
|
796
|
+
this.state.patientLandmark = target.value;
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const nextBtn = this.container.querySelector("#medos-btn-next");
|
|
800
|
+
if (nextBtn) {
|
|
801
|
+
nextBtn.addEventListener("click", () => this.goToNext());
|
|
802
|
+
}
|
|
803
|
+
const backBtn = this.container.querySelector("#medos-btn-back");
|
|
804
|
+
if (backBtn) {
|
|
805
|
+
backBtn.addEventListener("click", () => this.goBack());
|
|
806
|
+
}
|
|
807
|
+
const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
|
|
808
|
+
if (sendOtpBtn) {
|
|
809
|
+
sendOtpBtn.addEventListener("click", () => this.sendOtp());
|
|
810
|
+
}
|
|
811
|
+
const verifyOtpBtn = this.container.querySelector("#medos-btn-verify-otp");
|
|
812
|
+
if (verifyOtpBtn) {
|
|
813
|
+
verifyOtpBtn.addEventListener("click", () => this.verifyOtp());
|
|
814
|
+
}
|
|
815
|
+
const changeNumberBtn = this.container.querySelector("#medos-btn-change-number");
|
|
816
|
+
if (changeNumberBtn) {
|
|
817
|
+
changeNumberBtn.addEventListener("click", () => {
|
|
818
|
+
this.setState({ otpSent: false, otpCode: "", otpVerified: false });
|
|
819
|
+
this.render();
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
const submitBtn = this.container.querySelector("#medos-btn-submit");
|
|
823
|
+
if (submitBtn) {
|
|
824
|
+
submitBtn.addEventListener("click", () => this.submitAppointment());
|
|
825
|
+
}
|
|
826
|
+
const bookAnotherBtn = this.container.querySelector("#medos-btn-book-another");
|
|
827
|
+
if (bookAnotherBtn) {
|
|
828
|
+
bookAnotherBtn.addEventListener("click", () => this.reset());
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
escapeHtml(text) {
|
|
832
|
+
const div = document.createElement("div");
|
|
833
|
+
div.textContent = text;
|
|
834
|
+
return div.innerHTML;
|
|
835
|
+
}
|
|
836
|
+
destroy() {
|
|
837
|
+
this.mounted = false;
|
|
838
|
+
this.container.innerHTML = "";
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
export function initAppointmentCalendar(options) {
|
|
842
|
+
const container = document.getElementById(options.containerId);
|
|
843
|
+
if (!container) {
|
|
844
|
+
throw new Error(`Container element with id "${options.containerId}" not found`);
|
|
845
|
+
}
|
|
846
|
+
return new AppointmentCalendarWidget(container, options);
|
|
847
|
+
}
|
|
848
|
+
export { AppointmentCalendarWidget };
|