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