medos-sdk 1.1.13 → 1.1.14

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.
@@ -1,10 +1,12 @@
1
1
  import { AppointmentService, } from "../services/AppointmentService";
2
2
  import { PatientService } from "../services/PatientService";
3
+ import { WorkspaceService } from "../services/WorkspaceService";
3
4
  import { MedosClient } from "../client/MedosClient";
4
5
  import { INITIAL_STATE, } from "../components/appointment-booking/types";
5
6
  import { COUNTRY_CODES, GENDER_OPTIONS, BLOOD_GROUP_OPTIONS, mapBloodGroupToApi, } from "../components/constants/options";
6
7
  import { validatePhoneNumber, validateCountryCode, validateDateOfBirth, validateBloodGroup, } from "../components/validation";
7
8
  import { formatDateToISO, parsePatientName } from "../components/utils";
9
+ import { formatDate, formatTime, calculateDuration, } from "../components/utils/date";
8
10
  import { VanillaIcons } from "./components/VanillaIcons";
9
11
  import { VanillaSelect } from "./components/VanillaSelect";
10
12
  import { VanillaCalendar } from "./components/VanillaCalendar";
@@ -48,9 +50,25 @@ class AppointmentCalendarWidget {
48
50
  else {
49
51
  throw new Error("Either apiKey or sessionToken must be provided");
50
52
  }
53
+ await this.loadWorkspaceConfiguration();
51
54
  await this.loadAddresses();
52
55
  this.render();
53
56
  }
57
+ async loadWorkspaceConfiguration() {
58
+ this.setState({ loading: true, error: null });
59
+ try {
60
+ const workspaceResponse = await WorkspaceService.fetchWorkspace();
61
+ console.log("Workspace configuration loaded:", {
62
+ workspaceId: workspaceResponse.workspaceId,
63
+ });
64
+ }
65
+ catch (e) {
66
+ console.error("Failed to load workspace configuration:", e);
67
+ }
68
+ finally {
69
+ this.setState({ loading: false });
70
+ }
71
+ }
54
72
  async loadAddresses() {
55
73
  this.setState({ loading: true, error: null });
56
74
  try {
@@ -82,10 +100,6 @@ class AppointmentCalendarWidget {
82
100
  this.doctors = docsForAddr;
83
101
  if (docsForAddr.length === 1) {
84
102
  this.state.selectedDoctor = docsForAddr[0].id;
85
- this.state.step = 1;
86
- }
87
- else {
88
- this.state.step = 0;
89
103
  }
90
104
  }
91
105
  else {
@@ -94,20 +108,16 @@ class AppointmentCalendarWidget {
94
108
  error: "No doctors at this address. Please choose a different address.",
95
109
  });
96
110
  this.doctors = [];
97
- this.state.step = 0;
98
111
  }
99
112
  else {
100
113
  this.setState({
101
114
  error: "No doctors available for the selected location(s).",
102
115
  });
103
116
  this.doctors = [];
104
- this.state.step = 0;
105
117
  }
106
118
  }
107
119
  }
108
- else {
109
- this.state.step = 0;
110
- }
120
+ this.state.step = 0;
111
121
  }
112
122
  else {
113
123
  this.setState({ error: "No addresses or doctors available." });
@@ -142,7 +152,6 @@ class AppointmentCalendarWidget {
142
152
  this.doctors = docsForAddr;
143
153
  if (docsForAddr.length === 1) {
144
154
  this.state.selectedDoctor = docsForAddr[0].id;
145
- this.state.step = 1;
146
155
  }
147
156
  else {
148
157
  this.state.selectedDoctor = null;
@@ -189,6 +198,13 @@ class AppointmentCalendarWidget {
189
198
  try {
190
199
  const s = await AppointmentService.fetchSlots(this.state.workspaceId, this.state.selectedAddress, this.state.selectedDoctor, dateStr);
191
200
  this.state.slots = s || [];
201
+ if (this.state.selectedSlot) {
202
+ const isSlotStillValid = this.state.slots.some((slot) => slot.start === this.state.selectedSlot?.start &&
203
+ slot.end === this.state.selectedSlot?.end);
204
+ if (!isSlotStillValid) {
205
+ this.state.selectedSlot = null;
206
+ }
207
+ }
192
208
  }
193
209
  catch (e) {
194
210
  this.setState({ error: e.message || "Failed to load slots" });
@@ -220,8 +236,6 @@ class AppointmentCalendarWidget {
220
236
  this.state.patientAge &&
221
237
  this.state.patientGender &&
222
238
  this.state.otpVerified &&
223
- (!(this.state.patientDob && this.state.patientDob.trim() !== "") ||
224
- this.isValidDateOfBirth(this.state.patientDob)) &&
225
239
  (!(this.state.bloodGroup && this.state.bloodGroup.trim() !== "") ||
226
240
  this.isValidBloodGroup(this.state.bloodGroup));
227
241
  submitBtn.disabled = !(canSubmit && !this.state.loading);
@@ -247,8 +261,6 @@ class AppointmentCalendarWidget {
247
261
  this.state.patientState &&
248
262
  this.state.patientCountry &&
249
263
  this.state.patientZipcode &&
250
- (!(this.state.patientDob && this.state.patientDob.trim() !== "") ||
251
- this.isValidDateOfBirth(this.state.patientDob)) &&
252
264
  (!(this.state.bloodGroup && this.state.bloodGroup.trim() !== "") ||
253
265
  this.isValidBloodGroup(this.state.bloodGroup));
254
266
  continueBtn.disabled = !isFormValid;
@@ -334,12 +346,14 @@ class AppointmentCalendarWidget {
334
346
  phoneNumber: this.state.patientPhone,
335
347
  otpCode: this.state.otpCode,
336
348
  });
337
- await PatientService.verifyPhoneVerificationOtp({
349
+ const response = await PatientService.verifyPhoneVerificationOtp({
338
350
  countryCode: this.state.countryCode,
339
351
  phoneNumber: this.state.patientPhone,
340
352
  otpCode: this.state.otpCode,
341
353
  });
354
+ this.processOtpVerificationResponse(response);
342
355
  this.setState({ otpVerified: true, error: null });
356
+ this.goToNext();
343
357
  }
344
358
  catch (e) {
345
359
  console.error("OTP verification error:", e);
@@ -354,6 +368,77 @@ class AppointmentCalendarWidget {
354
368
  this.render();
355
369
  }
356
370
  }
371
+ processOtpVerificationResponse(response) {
372
+ try {
373
+ this.state.userSessionPacks = [];
374
+ this.state.availablePackages = [];
375
+ this.state.verifiedPatients = [];
376
+ const sessionPacksDetails = response?.sessionPacksDetails;
377
+ if (sessionPacksDetails?.associatedPatients &&
378
+ Array.isArray(sessionPacksDetails.associatedPatients) &&
379
+ sessionPacksDetails.associatedPatients.length > 0) {
380
+ this.state.verifiedPatients = sessionPacksDetails.associatedPatients;
381
+ }
382
+ else if (response.associatedPatients &&
383
+ Array.isArray(response.associatedPatients)) {
384
+ this.state.verifiedPatients = response.associatedPatients;
385
+ }
386
+ else if (response.patients && Array.isArray(response.patients)) {
387
+ this.state.verifiedPatients = response.patients;
388
+ }
389
+ if (sessionPacksDetails?.activeSessionPackResponses &&
390
+ Array.isArray(sessionPacksDetails.activeSessionPackResponses) &&
391
+ sessionPacksDetails.activeSessionPackResponses.length > 0) {
392
+ this.state.userSessionPacks =
393
+ sessionPacksDetails.activeSessionPackResponses.map((pack) => ({
394
+ id: pack.id || 0,
395
+ name: pack.packageName || pack.name || "Unknown Package",
396
+ description: pack.description,
397
+ totalSessions: pack.totalSessions || 0,
398
+ remainingSessions: pack.remainingSessions || 0,
399
+ expiryDate: pack.expiryDate || "",
400
+ purchaseDate: pack.purchaseDate,
401
+ doctorId: pack.doctorId,
402
+ doctorName: pack.doctorName,
403
+ allowedConsultationModes: pack.allowedConsultationModes || [],
404
+ }));
405
+ }
406
+ if (sessionPacksDetails?.allSessionPackResponses &&
407
+ Array.isArray(sessionPacksDetails.allSessionPackResponses) &&
408
+ sessionPacksDetails.allSessionPackResponses.length > 0) {
409
+ this.state.availablePackages =
410
+ sessionPacksDetails.allSessionPackResponses.map((pack) => ({
411
+ id: pack.id || 0,
412
+ name: pack.packageName || pack.name || "Unknown Package",
413
+ description: pack.description,
414
+ totalSessions: pack.totalSessions || 0,
415
+ price: pack.packagePrice || pack.price || 0,
416
+ discountedPrice: pack.discountedPrice,
417
+ discount: pack.discount,
418
+ discountType: pack.discountType,
419
+ validityDays: pack.validityDays || 0,
420
+ allowedConsultationModes: pack.allowedConsultationModes || [],
421
+ allowedDoctors: pack.allowedDoctors || [],
422
+ doctorIds: pack.doctorIds || [],
423
+ applicableOnline: pack.allowedConsultationModes?.includes("ONLINE") || false,
424
+ applicableOffline: pack.allowedConsultationModes?.includes("OFFLINE") || false,
425
+ }));
426
+ }
427
+ console.log("Processed OTP verification response:", {
428
+ verifiedPatients: this.state.verifiedPatients.length,
429
+ userSessionPacks: this.state.userSessionPacks.length,
430
+ availablePackages: this.state.availablePackages.length,
431
+ allSessionPackResponsesExists: sessionPacksDetails?.allSessionPackResponses?.length > 0,
432
+ sessionPacksDetailsExists: !!sessionPacksDetails,
433
+ });
434
+ }
435
+ catch (error) {
436
+ console.error("Error processing OTP verification response:", error);
437
+ this.state.verifiedPatients = [];
438
+ this.state.userSessionPacks = [];
439
+ this.state.availablePackages = [];
440
+ }
441
+ }
357
442
  async submitAppointment() {
358
443
  this.setState({ error: null });
359
444
  if (!this.state.selectedDoctor ||
@@ -377,23 +462,6 @@ class AppointmentCalendarWidget {
377
462
  this.setState({ error: "Please verify your phone number first." });
378
463
  return;
379
464
  }
380
- if (this.state.patientDob && this.state.patientDob.trim() !== "") {
381
- if (!this.isValidDateOfBirth(this.state.patientDob)) {
382
- const dobValue = this.state.patientDob;
383
- const today = new Date();
384
- const inputDate = new Date(dobValue);
385
- if (inputDate > today) {
386
- this.displayFieldValidationError("dateOfBirth", "future");
387
- }
388
- else if (!/^\d{4}-\d{2}-\d{2}$/.test(dobValue)) {
389
- this.displayFieldValidationError("dateOfBirth", "format");
390
- }
391
- else {
392
- this.displayFieldValidationError("dateOfBirth", "invalid");
393
- }
394
- return;
395
- }
396
- }
397
465
  if (this.state.bloodGroup && this.state.bloodGroup.trim() !== "") {
398
466
  if (!this.isValidBloodGroup(this.state.bloodGroup)) {
399
467
  this.displayFieldValidationError("bloodGroup", "invalid");
@@ -415,53 +483,64 @@ class AppointmentCalendarWidget {
415
483
  this.render();
416
484
  try {
417
485
  const { firstName, lastName } = parsePatientName(this.state.patientName || "Patient");
418
- const startDate = new Date(this.state.selectedSlot.start);
419
- const endDate = new Date(this.state.selectedSlot.end);
420
- const appointmentDate = formatDateToISO(startDate);
421
- const formatTime = (date) => {
422
- const hours = String(date.getHours()).padStart(2, "0");
423
- const minutes = String(date.getMinutes()).padStart(2, "0");
424
- return `${hours}:${minutes}`;
425
- };
426
- const fromDateTimeTs = formatTime(startDate);
427
- const toDateTimeTs = formatTime(endDate);
428
- const patientAddressPayload = {
429
- addressLine1: this.state.patientAddress,
430
- city: this.state.patientCity,
431
- state: this.state.patientState,
432
- country: this.state.patientCountry,
433
- zipcode: this.state.patientZipcode,
434
- landmark: this.state.patientLandmark || undefined,
486
+ const appointmentDate = formatDateToISO(this.state.selectedDate);
487
+ const fromDateTimeTs = this.state.selectedSlot.start;
488
+ const toDateTimeTs = this.state.selectedSlot.end;
489
+ const patientPayload = {
490
+ firstName,
491
+ lastName,
492
+ countryCode: this.state.countryCode,
493
+ phoneNumber: this.state.patientPhone,
494
+ age: parseInt(this.state.patientAge, 10),
495
+ gender: this.state.patientGender.toUpperCase(),
435
496
  };
436
- await AppointmentService.createAppointment({
497
+ if (this.state.patientEmail) {
498
+ patientPayload.email = this.state.patientEmail;
499
+ }
500
+ if (this.state.bloodGroup) {
501
+ patientPayload.bloodGroup = this.safeMapBloodGroupToApi(this.state.bloodGroup);
502
+ }
503
+ if (this.state.useExistingPatient && this.state.selectedPatient) {
504
+ patientPayload.id = this.state.selectedPatient.id;
505
+ }
506
+ const appointmentPayload = {
437
507
  workspaceId: this.state.workspaceId,
438
508
  workspaceAddressId: this.state.selectedAddress,
439
509
  doctorId: this.state.selectedDoctor,
440
510
  mode: this.state.consultationMode || "OFFLINE",
441
- appointmentDate,
442
- fromDateTimeTs,
443
- toDateTimeTs,
444
- consultationCharge: this.state.consultationMode === "ONLINE" ? "500" : "300",
511
+ appointmentDate: appointmentDate,
512
+ fromDateTimeTs: fromDateTimeTs,
513
+ toDateTimeTs: toDateTimeTs,
514
+ consultationCharge: this.state.consultationCharge || "0",
445
515
  type: "CONSULTATION",
446
516
  source: "SDK_POWERED_WEBSITE",
447
- patientPayload: {
448
- firstName,
449
- lastName,
450
- email: this.state.patientEmail || undefined,
517
+ bookingType: this.state.bookingType || "ONE_TIME_APPOINTMENT",
518
+ paymentMode: this.state.paymentMode || "CASH",
519
+ patientPayload,
520
+ patientAddress: {
521
+ addressLine1: this.state.patientAddress,
522
+ addressLine2: "",
523
+ city: this.state.patientCity,
524
+ state: this.state.patientState,
525
+ country: this.state.patientCountry,
526
+ zipcode: this.state.patientZipcode,
527
+ landmark: this.state.patientLandmark || undefined,
451
528
  countryCode: this.state.countryCode,
452
529
  phoneNumber: this.state.patientPhone,
453
- age: this.state.patientAge
454
- ? parseInt(this.state.patientAge, 10)
455
- : undefined,
456
- gender: this.state.patientGender
457
- ? this.state.patientGender.toUpperCase()
458
- : undefined,
459
- dob: this.safeFormatDateOfBirth(this.state.patientDob),
460
- bloodGroup: this.safeMapBloodGroupToApi(this.state.bloodGroup),
530
+ patientId: this.state.selectedPatient?.id || 0,
461
531
  },
462
- patientAddress: patientAddressPayload,
463
- });
464
- this.state.step = 7;
532
+ };
533
+ if (this.state.packageConfigId) {
534
+ appointmentPayload.packageConfigId = this.state.packageConfigId;
535
+ }
536
+ if (this.state.patientPackageId) {
537
+ appointmentPayload.patientPackageId = this.state.patientPackageId;
538
+ }
539
+ if (this.state.packageAmount) {
540
+ appointmentPayload.packageAmount = this.state.packageAmount;
541
+ }
542
+ await AppointmentService.createAppointment(appointmentPayload);
543
+ this.setState({ step: 7 });
465
544
  this.options.onSuccess?.();
466
545
  }
467
546
  catch (e) {
@@ -478,19 +557,34 @@ class AppointmentCalendarWidget {
478
557
  if (this.state.step === 0) {
479
558
  if (!this.state.otpVerified)
480
559
  return;
481
- this.state.step = 1;
482
- this.render();
483
- return;
560
+ console.log("Step 0 transition - Debug info:", {
561
+ allSessionPackResponses: this.state.availablePackages?.length || 0,
562
+ userSessionPacks: this.state.userSessionPacks?.length || 0,
563
+ availablePackages: this.state.availablePackages?.length || 0,
564
+ });
565
+ if (this.state.availablePackages &&
566
+ Array.isArray(this.state.availablePackages) &&
567
+ this.state.availablePackages.length > 0) {
568
+ this.state.step = 1;
569
+ console.log("Showing Booking Option Step - allSessionPackResponses is not empty");
570
+ this.render();
571
+ return;
572
+ }
573
+ else {
574
+ this.state.bookingOptionType = "new-appointment";
575
+ this.state.step = 2;
576
+ console.log("Auto-skipping Booking Option Step - allSessionPackResponses is empty");
577
+ this.render();
578
+ return;
579
+ }
484
580
  }
485
581
  if (this.state.step === 1) {
486
582
  if (!this.state.bookingOptionType)
487
583
  return;
488
- if (this.state.showPackageExplorer) {
489
- if (this.state.selectedNewPackage) {
490
- this.state.showPackageExplorer = false;
491
- this.state.step = 2;
492
- this.render();
493
- }
584
+ if (this.state.bookingOptionType === "explore-packages") {
585
+ this.state.showPackageExplorer = true;
586
+ this.state.step = 2;
587
+ this.render();
494
588
  return;
495
589
  }
496
590
  this.state.step = 2;
@@ -498,6 +592,13 @@ class AppointmentCalendarWidget {
498
592
  return;
499
593
  }
500
594
  if (this.state.step === 2) {
595
+ if (this.state.showPackageExplorer) {
596
+ if (this.state.selectedNewPackage) {
597
+ this.state.showPackageExplorer = false;
598
+ this.render();
599
+ }
600
+ return;
601
+ }
501
602
  if (this.state.addresses.length > 1 && !this.state.selectedAddress)
502
603
  return;
503
604
  if (this.doctors.length > 1 && !this.state.selectedDoctor)
@@ -521,10 +622,16 @@ class AppointmentCalendarWidget {
521
622
  return;
522
623
  }
523
624
  if (this.state.step === 4) {
524
- if (!this.state.selectedPatient)
625
+ if (this.state.selectedPatient && this.state.useExistingPatient) {
626
+ this.state.step = 6;
627
+ this.render();
525
628
  return;
526
- this.state.step = 5;
527
- this.render();
629
+ }
630
+ if (!this.state.useExistingPatient) {
631
+ this.state.step = 5;
632
+ this.render();
633
+ return;
634
+ }
528
635
  return;
529
636
  }
530
637
  if (this.state.step === 5) {
@@ -546,44 +653,40 @@ class AppointmentCalendarWidget {
546
653
  this.render();
547
654
  }
548
655
  goBack() {
549
- if (this.state.step === 1) {
550
- if (this.state.showPackageExplorer) {
551
- this.state.showPackageExplorer = false;
552
- this.state.selectedNewPackage = null;
553
- this.render();
554
- return;
555
- }
556
- this.state.step = 0;
557
- this.render();
558
- return;
559
- }
560
- if (this.state.step === 2) {
656
+ if (this.state.showPackageExplorer) {
657
+ this.state.showPackageExplorer = false;
658
+ this.state.selectedNewPackage = null;
659
+ this.state.bookingOptionType = null;
561
660
  this.state.step = 1;
562
661
  this.render();
563
662
  return;
564
663
  }
565
- if (this.state.step === 3) {
566
- this.state.step = 2;
567
- this.render();
568
- return;
569
- }
570
- if (this.state.step === 4) {
571
- this.state.step = 3;
572
- this.render();
573
- return;
574
- }
575
- if (this.state.step === 5) {
576
- this.state.step = 4;
577
- this.render();
578
- return;
579
- }
580
- if (this.state.step === 6) {
581
- this.state.step = 5;
664
+ const currentStep = this.state.step;
665
+ if (currentStep > 0) {
666
+ if (currentStep === 6 && this.state.useExistingPatient) {
667
+ this.state.selectedPatient = null;
668
+ this.state.useExistingPatient = false;
669
+ this.state.step = 4;
670
+ this.render();
671
+ return;
672
+ }
673
+ if (currentStep === 2) {
674
+ if (this.state.availablePackages &&
675
+ Array.isArray(this.state.availablePackages) &&
676
+ this.state.availablePackages.length > 0) {
677
+ this.state.step = 1;
678
+ console.log("Going back to Booking Option Step - allSessionPackResponses is not empty");
679
+ }
680
+ else {
681
+ this.state.step = 0;
682
+ console.log("Auto-skipping Booking Option Step on back - allSessionPackResponses is empty");
683
+ }
684
+ this.render();
685
+ return;
686
+ }
687
+ this.state.step = currentStep - 1;
582
688
  this.render();
583
- return;
584
689
  }
585
- this.state.step = Math.max(0, this.state.step - 1);
586
- this.render();
587
690
  }
588
691
  async reset() {
589
692
  this.state = { ...INITIAL_STATE };
@@ -597,12 +700,8 @@ class AppointmentCalendarWidget {
597
700
  if ("bloodGroup" in safeUpdates && safeUpdates.bloodGroup === undefined) {
598
701
  safeUpdates.bloodGroup = "";
599
702
  }
600
- if ("patientDob" in safeUpdates && safeUpdates.patientDob === undefined) {
601
- safeUpdates.patientDob = "";
602
- }
603
703
  this.state = { ...this.state, ...safeUpdates };
604
- if (safeUpdates.bloodGroup !== undefined ||
605
- safeUpdates.patientDob !== undefined) {
704
+ if (safeUpdates.bloodGroup !== undefined) {
606
705
  this.updatePatientDetailsButtonState();
607
706
  this.updateSubmitButtonState();
608
707
  }
@@ -827,30 +926,8 @@ class AppointmentCalendarWidget {
827
926
  </div>
828
927
  </div>
829
928
  <div class="medos-actions">
830
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
831
929
  <button class="medos-btn medos-btn-primary" id="medos-btn-send-otp" ${canSendOtp ? "" : "disabled"}>${this.state.otpSending ? "Sending..." : "Continue"}</button>
832
930
  </div>
833
- `;
834
- }
835
- if (this.state.otpVerified) {
836
- return `
837
- <div class="medos-section-card">
838
- <div class="medos-section-header">
839
- ${VanillaIcons.phone(14)}
840
- <span class="medos-section-title">Search Patient</span>
841
- </div>
842
- <div class="medos-section-body">
843
- <div class="medos-verified-badge">
844
- ${VanillaIcons.successBadge(20)}
845
- <span>Phone verified successfully</span>
846
- </div>
847
- <div class="medos-verified-number">${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
848
- </div>
849
- </div>
850
- <div class="medos-actions">
851
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
852
- <button class="medos-btn medos-btn-primary" id="medos-btn-next">Continue</button>
853
- </div>
854
931
  `;
855
932
  }
856
933
  return `
@@ -885,65 +962,98 @@ class AppointmentCalendarWidget {
885
962
  renderBookingOptionStep() {
886
963
  const hasActivePacks = this.state.userSessionPacks.length > 0;
887
964
  const activePack = this.state.userSessionPacks.find((p) => p.remainingSessions > 0);
965
+ const hasAvailablePackages = this.state.availablePackages.length > 0;
966
+ const multipleActivePacks = this.state.userSessionPacks.filter((p) => p.remainingSessions > 0)
967
+ .length > 1;
888
968
  return `
889
969
  <div class="medos-section-card">
890
970
  <div class="medos-section-header">
891
- ${VanillaIcons.calendar(14)}
892
- <span class="medos-section-title">Choose Booking Option</span>
971
+ <h3 class="medos-section-title" style="font-size: var(--medos-typography-font-size-xl, 20px);">Choose Booking Option</h3>
893
972
  </div>
894
973
  <div class="medos-section-body">
895
- <p class="medos-section-description">Select a package to explore on offer</p>
974
+ <p class="medos-section-description">
975
+ How would you like to book your appointment?
976
+ </p>
896
977
 
897
- ${hasActivePacks && activePack
978
+ ${multipleActivePacks
898
979
  ? `
899
- <div class="medos-active-session-packs">
900
- <label class="medos-form-label">YOUR ACTIVE SESSION PACKS</label>
901
- <div class="medos-session-pack-card ${this.state.bookingOptionType === "session-pack"
980
+ <div class="medos-session-packs-section">
981
+ <h4 class="medos-session-packs-title">Your Active Session Packs</h4>
982
+ <div class="medos-session-packs-list">
983
+ ${this.state.userSessionPacks
984
+ .filter((pack) => pack.remainingSessions > 0)
985
+ .map((pack) => `
986
+ <div class="medos-session-pack-card ${this.state.bookingOptionType === "session-pack" &&
987
+ this.state.selectedSessionPack?.id === pack.id
902
988
  ? "selected"
903
- : ""}" data-pack-id="${activePack.id}">
904
- <div class="medos-pack-header">
905
- <span class="medos-pack-name">${this.escapeHtml(activePack.name)}</span>
906
- <span class="medos-pack-badge">${activePack.remainingSessions} sessions</span>
907
- </div>
908
- <div class="medos-pack-details">
909
- ${activePack.remainingSessions} of ${activePack.totalSessions} sessions • <a href="#" class="medos-link">view details</a>
910
- </div>
989
+ : ""}" data-pack-id="${pack.id}">
990
+ <div class="medos-session-pack-info">
991
+ <span class="medos-session-pack-name">${this.escapeHtml(pack.name)}</span>
992
+ <span class="medos-session-pack-remaining">${pack.remainingSessions}/${pack.totalSessions} sessions</span>
993
+ ${pack.doctorName
994
+ ? `<span class="medos-session-pack-doctor">${this.escapeHtml(pack.doctorName)}</span>`
995
+ : ""}
996
+ </div>
997
+ <div class="medos-session-pack-expiry">
998
+ Expires: ${new Date(pack.expiryDate).toLocaleDateString()}
999
+ </div>
1000
+ </div>
1001
+ `)
1002
+ .join("")}
911
1003
  </div>
912
1004
  </div>
1005
+ ${hasActivePacks ? '<hr style="margin: 6px 0;" />' : ""}
913
1006
  `
914
1007
  : ""}
915
1008
 
916
- <div class="medos-other-options">
917
- <label class="medos-form-label">OTHER OPTIONS</label>
1009
+ <div class="medos-options-grid">
1010
+ ${activePack && !multipleActivePacks
1011
+ ? `
1012
+ <div class="medos-option-card ${this.state.bookingOptionType === "session-pack" &&
1013
+ this.state.selectedSessionPack?.id === activePack.id
1014
+ ? "selected"
1015
+ : ""}" data-option="session-pack" data-pack-id="${activePack.id}">
1016
+ <div class="medos-option-icon">📋</div>
1017
+ <h4 class="medos-option-title">Use Session Pack</h4>
1018
+ <p class="medos-option-description">
1019
+ You have ${activePack.remainingSessions} session${activePack.remainingSessions > 1 ? "s" : ""} remaining
1020
+ </p>
1021
+ <div class="medos-session-pack-badge">
1022
+ ${this.escapeHtml(activePack.name)}
1023
+ </div>
1024
+ </div>
1025
+ `
1026
+ : ""}
918
1027
 
919
- <div class="medos-option-cards">
920
- <div class="medos-option-card ${this.state.bookingOptionType === "new-appointment"
1028
+ <div class="medos-option-card ${this.state.bookingOptionType === "new-appointment"
921
1029
  ? "selected"
922
1030
  : ""}" data-option="new-appointment">
923
- <div class="medos-option-icon">${VanillaIcons.calendar(24)}</div>
924
- <div class="medos-option-content">
925
- <span class="medos-option-title">Standard Consultation</span>
926
- <span class="medos-option-subtitle">Pay per appointment</span>
927
- </div>
928
- </div>
929
-
1031
+ <div class="medos-option-icon">📅</div>
1032
+ <h4 class="medos-option-title">Book New Appointment</h4>
1033
+ <p class="medos-option-description">
1034
+ Schedule a single consultation visit
1035
+ </p>
1036
+ </div>
1037
+
1038
+ ${hasAvailablePackages
1039
+ ? `
930
1040
  <div class="medos-option-card ${this.state.bookingOptionType === "explore-packages"
931
- ? "selected"
932
- : ""}" data-option="explore-packages">
933
- <div class="medos-option-icon">${VanillaIcons.giftBox(24)}</div>
934
- <div class="medos-option-content">
935
- <span class="medos-option-title">Session Packs</span>
936
- <span class="medos-option-subtitle">Explore packs with discounts and offers</span>
937
- </div>
1041
+ ? "selected"
1042
+ : ""}" data-option="explore-packages">
1043
+ <div class="medos-option-icon">🎁</div>
1044
+ <h4 class="medos-option-title">Explore Packages</h4>
1045
+ <p class="medos-option-description">
1046
+ View and purchase consultation packages
1047
+ </p>
938
1048
  </div>
939
- </div>
1049
+ `
1050
+ : ""}
940
1051
  </div>
941
1052
  </div>
942
1053
  </div>
943
1054
  <div class="medos-actions">
944
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
945
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
946
- <button class="medos-btn medos-btn-primary" id="medos-btn-continue">Continue</button>
1055
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1056
+ <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${this.state.bookingOptionType ? "" : "disabled"}>Next</button>
947
1057
  </div>
948
1058
  `;
949
1059
  }
@@ -975,70 +1085,137 @@ class AppointmentCalendarWidget {
975
1085
  return `
976
1086
  <div class="medos-section-card">
977
1087
  <div class="medos-section-header">
978
- ${VanillaIcons.giftBox(14)}
979
- <span class="medos-section-title">Explore Session Packs</span>
1088
+ <h3 class="medos-section-title" style="font-size: var(--medos-typography-font-size-xl, 20px);">Explore Packages</h3>
980
1089
  </div>
981
1090
  <div class="medos-section-body">
982
- <p class="medos-section-description">Select a package to explore on offer</p>
1091
+ <p class="medos-section-description">
1092
+ Choose a package that suits your needs
1093
+ </p>
983
1094
 
984
- <div class="medos-available-packages">
985
- <label class="medos-form-label">AVAILABLE PACKAGES</label>
986
-
987
- ${packages
988
- .map((pkg, index) => `
989
- <div class="medos-package-card ${this.state.selectedNewPackage?.id === pkg.id ? "selected" : ""}" data-package-id="${pkg.id}">
990
- <div class="medos-package-radio">
991
- <input type="radio" name="package" value="${pkg.id}" ${this.state.selectedNewPackage?.id === pkg.id ? "checked" : ""} />
992
- </div>
993
- <div class="medos-package-content">
994
- <div class="medos-package-name">${this.escapeHtml(pkg.name)}</div>
995
- <div class="medos-package-description">${this.escapeHtml(pkg.description || "").replace(/\n/g, "<br>")}</div>
1095
+ ${packages.length > 0
1096
+ ? `
1097
+ <div class="medos-packages-grid">
1098
+ ${packages
1099
+ .map((pkg) => `
1100
+ <div class="medos-package-card ${this.state.selectedNewPackage?.id === pkg.id ? "selected" : ""}" data-package-id="${pkg.id}">
1101
+ <h4 class="medos-package-name">${this.escapeHtml(pkg.name)}</h4>
1102
+ <p class="medos-package-description">
1103
+ ${this.escapeHtml(pkg.description || "").replace(/\n/g, "<br>")}
1104
+ </p>
1105
+
1106
+ ${pkg.allowedDoctors && pkg.allowedDoctors.length > 0
1107
+ ? `
1108
+ <div class="medos-package-doctors">
1109
+ ${pkg.allowedDoctors
1110
+ .map((doc) => `
1111
+ <span class="medos-doctor-badge">
1112
+ ${this.escapeHtml(doc.fullName)}
1113
+ </span>
1114
+ `)
1115
+ .join("")}
1116
+ </div>
1117
+ `
1118
+ : ""}
1119
+
1120
+ ${pkg.allowedConsultationModes &&
1121
+ pkg.allowedConsultationModes.length > 0
1122
+ ? `
1123
+ <div class="medos-consultation-modes">
1124
+ ${pkg.allowedConsultationModes
1125
+ .map((mode) => `
1126
+ <span class="medos-mode-badge">
1127
+ ${mode === "ONLINE" ? "🌐 Online" : "🏥 In-Person"}
1128
+ </span>
1129
+ `)
1130
+ .join("")}
1131
+ </div>
1132
+ `
1133
+ : ""}
1134
+
1135
+ <div class="medos-package-details">
1136
+ <span class="medos-package-sessions">
1137
+ ${pkg.totalSessions || pkg.sessions} session${(pkg.totalSessions || pkg.sessions || 1) > 1 ? "s" : ""}
1138
+ </span>
1139
+ <div class="medos-package-price-container">
1140
+ ${pkg.discountedPrice && pkg.discountedPrice < pkg.price
1141
+ ? `
1142
+ <span class="medos-original-price">
1143
+ ₹${pkg.price.toLocaleString()}
1144
+ </span>
1145
+ <span class="medos-package-price">
1146
+ ₹${pkg.discountedPrice.toLocaleString()}
1147
+ </span>
1148
+ <span class="medos-per-session-price">
1149
+ ₹${Math.round(pkg.discountedPrice /
1150
+ (pkg.totalSessions || pkg.sessions || 1)).toLocaleString()}/session
1151
+ </span>
1152
+ ${pkg.discount
1153
+ ? `
1154
+ <span class="medos-discount-badge">
1155
+ ${pkg.discountType === "PERCENTAGE"
1156
+ ? `${pkg.discount}% OFF`
1157
+ : `₹${pkg.discount} OFF`}
1158
+ </span>
1159
+ `
1160
+ : ""}
1161
+ `
1162
+ : `
1163
+ <span class="medos-package-price">
1164
+ ₹${pkg.price.toLocaleString()}
1165
+ </span>
1166
+ <span class="medos-per-session-price">
1167
+ ₹${Math.round(pkg.price / (pkg.totalSessions || pkg.sessions || 1)).toLocaleString()}/session
1168
+ </span>
1169
+ `}
1170
+ </div>
1171
+ </div>
1172
+ <div class="medos-package-validity">
1173
+ Valid for ${pkg.validityDays} days
1174
+ </div>
996
1175
  </div>
997
- <div class="medos-package-price">₹ ${pkg.price.toLocaleString()}</div>
998
- </div>
999
- `)
1000
- .join("")}
1001
- </div>
1002
-
1003
- <div class="medos-package-note">
1004
- <a href="#" class="medos-link">View full terms and conditions</a>
1005
- </div>
1176
+ `)
1177
+ .join("")}
1178
+ </div>
1179
+ `
1180
+ : `
1181
+ <div style="text-align: center; padding: 40px 20px; color: var(--medos-color-text-secondary, #6b7280); font-size: var(--medos-typography-font-size-sm, 14px);">
1182
+ <p>No packages available at the moment.</p>
1183
+ </div>
1184
+ `}
1006
1185
  </div>
1007
1186
  </div>
1008
1187
  <div class="medos-actions">
1009
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1010
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
1011
- <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${this.state.selectedNewPackage ? "" : "disabled"}>Continue</button>
1188
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
1189
+ <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${this.state.selectedNewPackage ? "" : "disabled"}>Next</button>
1012
1190
  </div>
1013
1191
  `;
1014
1192
  }
1015
1193
  renderLocationDoctorStep() {
1016
1194
  const canProceed = this.canProceedFromMergedStep();
1017
1195
  return `
1018
- <div style="border:1px solid #e5e7eb;border-radius:12px;margin-bottom:24px;background:#fff;">
1019
- <div style="background:#F9FAFB;padding:16px 20px;display:flex;align-items:center;gap:12px;border-bottom:1px solid #E5E7EB;">
1196
+ <div class="medos-section-card">
1197
+ <div class="medos-section-header">
1020
1198
  ${VanillaIcons.mapPin(18)}
1021
- <h3 style="font-size:18px;font-weight:600;margin:0;">Location & Doctor</h3>
1199
+ <h3 class="medos-section-title">Location & Doctor</h3>
1022
1200
  </div>
1023
- <div style="padding:24px;">
1024
- <div style="margin-bottom:20px;">
1025
- <label style="display:block;font-size:13px;margin-bottom:6px;color:#374151;">
1026
- Preferred Location <span style="color:#EF4444">*</span>
1201
+ <div class="medos-section-body">
1202
+ <div class="medos-form-group">
1203
+ <label class="medos-label">
1204
+ Preferred Location <span class="medos-required">*</span>
1027
1205
  </label>
1028
1206
  <div id="medos-address-select-container"></div>
1029
1207
  </div>
1030
- <div style="margin-bottom:20px;">
1031
- <label style="display:block;font-size:13px;margin-bottom:6px;color:#374151;">
1032
- Preferred Doctor <span style="color:#EF4444">*</span>
1208
+ <div class="medos-form-group">
1209
+ <label class="medos-label">
1210
+ Preferred Doctor <span class="medos-required">*</span>
1033
1211
  </label>
1034
1212
  <div id="medos-doctor-select-container"></div>
1035
1213
  </div>
1036
- <div style="margin-bottom:20px;">
1037
- <label style="display:block;font-size:13px;margin-bottom:6px;color:#374151;">
1038
- Chief Complaint <span style="color:#6B7280">(optional)</span>
1214
+ <div class="medos-form-group">
1215
+ <label class="medos-label">
1216
+ Chief Complaint <span class="medos-optional">(optional)</span>
1039
1217
  </label>
1040
1218
  <textarea
1041
- style="width:100%;padding:10px 12px;border-radius:8px;border:1px solid #e6e9ef;font-size:14px;box-sizing:border-box;"
1042
1219
  class="medos-textarea"
1043
1220
  id="medos-chief-complaint"
1044
1221
  placeholder="Enter Chief Complaint or Appointment Notes"
@@ -1046,9 +1223,9 @@ class AppointmentCalendarWidget {
1046
1223
  </div>
1047
1224
  </div>
1048
1225
  </div>
1049
- <div style="display:flex;gap:8px;margin-top:16px;justify-content:flex-end;">
1050
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1051
- <button style="background:#218838;color:#fff;border:none;padding:10px 14px;border-radius:8px;cursor:pointer;font-weight:600;${canProceed ? "" : "opacity:0.6;"}" id="medos-btn-next" ${canProceed ? "" : "disabled"}>Continue</button>
1226
+ <div class="medos-actions">
1227
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1228
+ <button class="medos-btn medos-btn-primary" id="medos-btn-next" ${canProceed ? "" : "disabled"}>Continue</button>
1052
1229
  </div>
1053
1230
  `;
1054
1231
  }
@@ -1072,6 +1249,7 @@ class AppointmentCalendarWidget {
1072
1249
  </div>
1073
1250
  `
1074
1251
  : "";
1252
+ const slotsByPeriod = this.groupSlotsByPeriod(this.state.slots);
1075
1253
  return `
1076
1254
  <div class="medos-section-card">
1077
1255
  <div class="medos-section-header">
@@ -1089,7 +1267,7 @@ class AppointmentCalendarWidget {
1089
1267
  ${this.state.consultationMode === "OFFLINE" ? "checked" : ""}
1090
1268
  class="medos-radio-input"
1091
1269
  />
1092
- <span class="medos-radio-label">Online</span>
1270
+ <span class="medos-radio-label">Offline</span>
1093
1271
  </label>
1094
1272
  <label class="medos-radio-option ${this.state.consultationMode === "ONLINE" ? "selected" : ""}">
1095
1273
  <input
@@ -1099,7 +1277,7 @@ class AppointmentCalendarWidget {
1099
1277
  ${this.state.consultationMode === "ONLINE" ? "checked" : ""}
1100
1278
  class="medos-radio-input"
1101
1279
  />
1102
- <span class="medos-radio-label">Offline</span>
1280
+ <span class="medos-radio-label">Online</span>
1103
1281
  </label>
1104
1282
  </div>
1105
1283
  </div>
@@ -1120,129 +1298,93 @@ class AppointmentCalendarWidget {
1120
1298
  <label class="medos-form-label">Available Slots</label>
1121
1299
  ${this.state.slots.length === 0
1122
1300
  ? '<div class="medos-empty-slots">Select a date to see available slots</div>'
1123
- : `
1124
- <div class="medos-slots-grid-compact">
1125
- ${this.state.slots
1126
- .map((s) => {
1127
- const start = new Date(s.start).toLocaleTimeString([], {
1128
- hour: "2-digit",
1129
- minute: "2-digit",
1130
- });
1131
- const end = new Date(s.end).toLocaleTimeString([], {
1132
- hour: "2-digit",
1133
- minute: "2-digit",
1134
- });
1135
- const selected = this.state.selectedSlot?.start === s.start &&
1136
- this.state.selectedSlot?.end === s.end;
1137
- return `
1138
- <div class="medos-slot-btn ${selected ? "selected" : ""}"
1139
- data-slot-id="${this.escapeHtml(s.id || `${s.start}-${s.end}`)}"
1140
- data-slot-start="${this.escapeHtml(s.start)}"
1141
- data-slot-end="${this.escapeHtml(s.end)}">
1142
- ${start}
1143
- </div>
1144
- `;
1145
- })
1146
- .join("")}
1147
- </div>
1148
- `}
1301
+ : this.renderGroupedSlots(slotsByPeriod)}
1149
1302
  </div>
1150
1303
  </div>
1151
1304
  </div>
1152
1305
 
1153
1306
  <div class="medos-actions">
1154
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1155
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
1307
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1156
1308
  <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${this.state.selectedSlot ? "" : "disabled"}>Continue</button>
1157
1309
  </div>
1158
1310
  `;
1159
1311
  }
1312
+ groupSlotsByPeriod(slots) {
1313
+ const groups = {
1314
+ Morning: [],
1315
+ Afternoon: [],
1316
+ Evening: [],
1317
+ };
1318
+ slots.forEach((slot) => {
1319
+ const startTime = new Date(slot.start);
1320
+ const hours = startTime.getHours();
1321
+ let period = "Morning";
1322
+ if (hours >= 12 && hours < 17) {
1323
+ period = "Afternoon";
1324
+ }
1325
+ else if (hours >= 17) {
1326
+ period = "Evening";
1327
+ }
1328
+ groups[period].push(slot);
1329
+ });
1330
+ return groups;
1331
+ }
1332
+ renderGroupedSlots(slotsByPeriod) {
1333
+ const periods = ["Morning", "Afternoon", "Evening"];
1334
+ return `
1335
+ <div class="medos-slots-container-grouped">
1336
+ ${periods
1337
+ .filter((period) => slotsByPeriod[period].length > 0)
1338
+ .map((period) => `
1339
+ <div class="medos-slot-period">
1340
+ <div class="medos-slot-period-header">
1341
+ <span class="medos-slot-period-title">${period.toUpperCase()}</span>
1342
+ <span class="medos-slot-period-count">${slotsByPeriod[period].length} ${slotsByPeriod[period].length === 1 ? "slot" : "slots"}</span>
1343
+ </div>
1344
+ <div class="medos-slot-period-grid">
1345
+ ${slotsByPeriod[period]
1346
+ .map((slot) => {
1347
+ const startDate = new Date(slot.start);
1348
+ const endDate = new Date(slot.end);
1349
+ const startTime = startDate.toLocaleTimeString([], {
1350
+ hour: "2-digit",
1351
+ minute: "2-digit",
1352
+ });
1353
+ const endTime = endDate.toLocaleTimeString([], {
1354
+ hour: "2-digit",
1355
+ minute: "2-digit",
1356
+ });
1357
+ const durationMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
1358
+ const selected = this.state.selectedSlot?.start === slot.start &&
1359
+ this.state.selectedSlot?.end === slot.end;
1360
+ return `
1361
+ <button type="button" class="medos-slot-btn ${selected ? "selected" : ""}"
1362
+ data-slot-id="${this.escapeHtml(slot.id || `${slot.start}-${slot.end}`)}"
1363
+ data-slot-start="${this.escapeHtml(slot.start)}"
1364
+ data-slot-end="${this.escapeHtml(slot.end)}"
1365
+ ${selected
1366
+ ? 'aria-pressed="true"'
1367
+ : 'aria-pressed="false"'}
1368
+ aria-label="Select ${startTime} to ${endTime} time slot">
1369
+ <span class="medos-slot-time-range">
1370
+ <span class="medos-slot-start-time">${startTime}</span>
1371
+ <span class="medos-slot-separator">–</span>
1372
+ <span class="medos-slot-end-time">${endTime}</span>
1373
+ </span>
1374
+ <span class="medos-slot-duration">${durationMinutes}m</span>
1375
+ </button>
1376
+ `;
1377
+ })
1378
+ .join("")}
1379
+ </div>
1380
+ </div>
1381
+ `)
1382
+ .join("")}
1383
+ </div>
1384
+ `;
1385
+ }
1160
1386
  renderPatientSelectionStep() {
1161
- const patients = this.state.verifiedPatients.length > 0
1162
- ? this.state.verifiedPatients
1163
- : [
1164
- {
1165
- id: 1,
1166
- firstName: "Mumma",
1167
- lastName: "Bear",
1168
- email: "mumma@example.com",
1169
- countryCode: "+91",
1170
- phoneNumber: this.state.patientPhone,
1171
- dob: "1970-01-01",
1172
- age: 55,
1173
- gender: "FEMALE",
1174
- bloodGroup: "O+",
1175
- mrn: "MRN001",
1176
- address: {
1177
- id: 1,
1178
- completeAddress: "123 Main St",
1179
- addressLine1: "123 Main St",
1180
- addressLine2: "",
1181
- city: "Mumbai",
1182
- state: "Maharashtra",
1183
- country: "India",
1184
- zipcode: "400001",
1185
- landmark: "",
1186
- phoneNumber: this.state.patientPhone,
1187
- latitude: 0,
1188
- longitude: 0,
1189
- },
1190
- },
1191
- {
1192
- id: 2,
1193
- firstName: "Papa",
1194
- lastName: "Bear",
1195
- email: "papa@example.com",
1196
- countryCode: "+91",
1197
- phoneNumber: this.state.patientPhone,
1198
- dob: "1968-01-01",
1199
- age: 57,
1200
- gender: "MALE",
1201
- bloodGroup: "B+",
1202
- mrn: "MRN002",
1203
- address: {
1204
- id: 2,
1205
- completeAddress: "123 Main St",
1206
- addressLine1: "123 Main St",
1207
- addressLine2: "",
1208
- city: "Mumbai",
1209
- state: "Maharashtra",
1210
- country: "India",
1211
- zipcode: "400001",
1212
- landmark: "",
1213
- phoneNumber: this.state.patientPhone,
1214
- latitude: 0,
1215
- longitude: 0,
1216
- },
1217
- },
1218
- {
1219
- id: 3,
1220
- firstName: "Monty",
1221
- lastName: "Bear",
1222
- email: "monty@example.com",
1223
- countryCode: "+91",
1224
- phoneNumber: this.state.patientPhone,
1225
- dob: "1990-01-01",
1226
- age: 35,
1227
- gender: "MALE",
1228
- bloodGroup: "A+",
1229
- mrn: "MRN003",
1230
- address: {
1231
- id: 3,
1232
- completeAddress: "123 Main St",
1233
- addressLine1: "123 Main St",
1234
- addressLine2: "",
1235
- city: "Mumbai",
1236
- state: "Maharashtra",
1237
- country: "India",
1238
- zipcode: "400001",
1239
- landmark: "",
1240
- phoneNumber: this.state.patientPhone,
1241
- latitude: 0,
1242
- longitude: 0,
1243
- },
1244
- },
1245
- ];
1387
+ const hasVerifiedPatients = this.state.verifiedPatients.length > 0;
1246
1388
  return `
1247
1389
  <div class="medos-section-card">
1248
1390
  <div class="medos-section-header">
@@ -1250,25 +1392,40 @@ class AppointmentCalendarWidget {
1250
1392
  <span class="medos-section-title">Select Patient</span>
1251
1393
  </div>
1252
1394
  <div class="medos-section-body">
1253
- <div class="medos-patient-list">
1254
- ${patients
1255
- .map((patient) => `
1256
- <div class="medos-patient-card ${this.state.selectedPatient?.id === patient.id ? "selected" : ""}" data-patient-id="${patient.id}">
1257
- <div class="medos-patient-radio">
1258
- <input type="radio" name="patient" value="${patient.id}" ${this.state.selectedPatient?.id === patient.id ? "checked" : ""} />
1259
- </div>
1260
- <div class="medos-patient-avatar">
1261
- ${patient.firstName.charAt(0)}${patient.lastName.charAt(0)}
1262
- </div>
1263
- <div class="medos-patient-info">
1264
- <div class="medos-patient-name">${this.escapeHtml(patient.firstName)} ${this.escapeHtml(patient.lastName)}</div>
1265
- <div class="medos-patient-details">${this.escapeHtml(patient.countryCode)} ${this.escapeHtml(patient.phoneNumber)}</div>
1395
+ ${hasVerifiedPatients
1396
+ ? `
1397
+ <div class="medos-patient-list">
1398
+ ${this.state.verifiedPatients
1399
+ .map((patient) => `
1400
+ <div class="medos-patient-card ${this.state.selectedPatient?.id === patient.id
1401
+ ? "selected"
1402
+ : ""}" data-patient-id="${patient.id}">
1403
+ <div class="medos-patient-radio">
1404
+ <input type="radio" name="patient" value="${patient.id}" ${this.state.selectedPatient?.id === patient.id
1405
+ ? "checked"
1406
+ : ""} />
1407
+ </div>
1408
+ <div class="medos-patient-avatar">
1409
+ ${patient.firstName.charAt(0)}${patient.lastName.charAt(0)}
1410
+ </div>
1411
+ <div class="medos-patient-info">
1412
+ <div class="medos-patient-name">${this.escapeHtml(patient.firstName)} ${this.escapeHtml(patient.lastName)}</div>
1413
+ <div class="medos-patient-details">${this.escapeHtml(patient.countryCode)} ${this.escapeHtml(patient.phoneNumber)}</div>
1414
+ ${patient.age
1415
+ ? `<div class="medos-patient-age">Age: ${patient.age}</div>`
1416
+ : ""}
1417
+ </div>
1418
+ <div class="medos-patient-select">select</div>
1266
1419
  </div>
1267
- <div class="medos-patient-select">select</div>
1268
- </div>
1269
- `)
1270
- .join("")}
1271
- </div>
1420
+ `)
1421
+ .join("")}
1422
+ </div>
1423
+ `
1424
+ : `
1425
+ <div class="medos-no-patients">
1426
+ <p>No existing patients found for this phone number.</p>
1427
+ </div>
1428
+ `}
1272
1429
 
1273
1430
  <button class="medos-add-patient-btn" id="medos-btn-add-patient">
1274
1431
  ${VanillaIcons.plus(16)} New Patient
@@ -1276,8 +1433,7 @@ class AppointmentCalendarWidget {
1276
1433
  </div>
1277
1434
  </div>
1278
1435
  <div class="medos-actions">
1279
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1280
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
1436
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1281
1437
  <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${this.state.selectedPatient ? "" : "disabled"}>Continue</button>
1282
1438
  </div>
1283
1439
  `;
@@ -1302,7 +1458,7 @@ class AppointmentCalendarWidget {
1302
1458
  <div class="medos-form-row">
1303
1459
  <div class="medos-form-field">
1304
1460
  <label class="medos-form-label">
1305
- First Name <span style="color: #EF4444;">*</span>
1461
+ First Name <span class="medos-required">*</span>
1306
1462
  </label>
1307
1463
  <input
1308
1464
  type="text"
@@ -1314,7 +1470,7 @@ class AppointmentCalendarWidget {
1314
1470
  </div>
1315
1471
  <div class="medos-form-field">
1316
1472
  <label class="medos-form-label">
1317
- Last Name <span style="color: #EF4444;">*</span>
1473
+ Last Name <span class="medos-required">*</span>
1318
1474
  </label>
1319
1475
  <input
1320
1476
  type="text"
@@ -1329,7 +1485,7 @@ class AppointmentCalendarWidget {
1329
1485
  <div class="medos-form-row">
1330
1486
  <div class="medos-form-field">
1331
1487
  <label class="medos-form-label">
1332
- Age <span style="color: #EF4444;">*</span>
1488
+ Age <span class="medos-required">*</span>
1333
1489
  </label>
1334
1490
  <input
1335
1491
  type="number"
@@ -1356,7 +1512,7 @@ class AppointmentCalendarWidget {
1356
1512
  <div class="medos-form-row">
1357
1513
  <div class="medos-form-field">
1358
1514
  <label class="medos-form-label">
1359
- Gender <span style="color: #EF4444;">*</span>
1515
+ Gender <span class="medos-required">*</span>
1360
1516
  </label>
1361
1517
  <div id="medos-gender-select-container"></div>
1362
1518
  </div>
@@ -1366,22 +1522,6 @@ class AppointmentCalendarWidget {
1366
1522
  </div>
1367
1523
  </div>
1368
1524
 
1369
- <div class="medos-form-row">
1370
- <div class="medos-form-field">
1371
- <label class="medos-form-label">Date of Birth</label>
1372
- <input
1373
- type="date"
1374
- class="medos-appointment-input"
1375
- id="medos-patient-dob"
1376
- value="${this.escapeHtml(this.state.patientDob)}"
1377
- />
1378
- ${this.state.patientDob &&
1379
- this.state.patientDob.trim() !== "" &&
1380
- !this.isValidDateOfBirth(this.state.patientDob)
1381
- ? this.getDateOfBirthErrorMessage(this.state.patientDob)
1382
- : ""}
1383
- </div>
1384
- </div>
1385
1525
  </div>
1386
1526
  </div>
1387
1527
 
@@ -1394,7 +1534,7 @@ class AppointmentCalendarWidget {
1394
1534
  <div class="medos-section-body">
1395
1535
  <div class="medos-form-field">
1396
1536
  <label class="medos-form-label">
1397
- Address <span style="color: #EF4444;">*</span>
1537
+ Address <span class="medos-required">*</span>
1398
1538
  </label>
1399
1539
  <input
1400
1540
  type="text"
@@ -1408,7 +1548,7 @@ class AppointmentCalendarWidget {
1408
1548
  <div class="medos-form-row">
1409
1549
  <div class="medos-form-field">
1410
1550
  <label class="medos-form-label">
1411
- City <span style="color: #EF4444;">*</span>
1551
+ City <span class="medos-required">*</span>
1412
1552
  </label>
1413
1553
  <input
1414
1554
  type="text"
@@ -1420,7 +1560,7 @@ class AppointmentCalendarWidget {
1420
1560
  </div>
1421
1561
  <div class="medos-form-field">
1422
1562
  <label class="medos-form-label">
1423
- State <span style="color: #EF4444;">*</span>
1563
+ State <span class="medos-required">*</span>
1424
1564
  </label>
1425
1565
  <input
1426
1566
  type="text"
@@ -1435,7 +1575,7 @@ class AppointmentCalendarWidget {
1435
1575
  <div class="medos-form-row">
1436
1576
  <div class="medos-form-field">
1437
1577
  <label class="medos-form-label">
1438
- Country <span style="color: #EF4444;">*</span>
1578
+ Country <span class="medos-required">*</span>
1439
1579
  </label>
1440
1580
  <input
1441
1581
  type="text"
@@ -1447,7 +1587,7 @@ class AppointmentCalendarWidget {
1447
1587
  </div>
1448
1588
  <div class="medos-form-field">
1449
1589
  <label class="medos-form-label">
1450
- Zipcode <span style="color: #EF4444;">*</span>
1590
+ Zipcode <span class="medos-required">*</span>
1451
1591
  </label>
1452
1592
  <input
1453
1593
  type="text"
@@ -1473,8 +1613,7 @@ class AppointmentCalendarWidget {
1473
1613
  </div>
1474
1614
 
1475
1615
  <div class="medos-actions">
1476
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1477
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
1616
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1478
1617
  <button class="medos-btn medos-btn-primary" id="medos-btn-continue" ${isFormValid ? "" : "disabled"}>Continue</button>
1479
1618
  </div>
1480
1619
  `;
@@ -1482,139 +1621,223 @@ class AppointmentCalendarWidget {
1482
1621
  renderAppointmentSummaryStep() {
1483
1622
  const selectedDoctor = this.doctors.find((d) => d.id === this.state.selectedDoctor);
1484
1623
  const selectedAddress = this.state.addresses.find((addr) => addr.id === this.state.selectedAddress);
1485
- const appointmentDate = this.state.selectedDate
1486
- ? this.state.selectedDate.toLocaleDateString("en-US", {
1487
- weekday: "short",
1488
- year: "numeric",
1489
- month: "short",
1490
- day: "numeric",
1491
- })
1492
- : "";
1493
- const startTime = this.state.selectedSlot?.start
1494
- ? new Date(this.state.selectedSlot.start).toLocaleTimeString([], {
1495
- hour: "2-digit",
1496
- minute: "2-digit",
1497
- })
1498
- : "";
1499
- const usingSessionPack = this.state.bookingOptionType === "session-pack" &&
1624
+ const usingSessionPack = this.state.bookingType === "USE_ACTIVE_PACKAGE" &&
1500
1625
  this.state.selectedSessionPack;
1501
- const usingNewPackage = this.state.bookingOptionType === "explore-packages" &&
1626
+ const usingNewPackage = this.state.bookingType === "PACKAGE_PURCHASE" &&
1502
1627
  this.state.selectedNewPackage;
1503
- const consultationCharge = this.state.consultationMode === "ONLINE" ? 500 : 300;
1504
- let totalAmount = consultationCharge;
1505
- let sessionInfo = "";
1506
- let dateOfExpiry = "";
1507
- if (usingSessionPack) {
1508
- totalAmount = 0;
1509
- sessionInfo = `${this.state.selectedSessionPack?.remainingSessions} appointments now remaining`;
1510
- dateOfExpiry = this.state.selectedSessionPack?.expiryDate || "";
1511
- }
1512
- else if (usingNewPackage) {
1513
- totalAmount = this.state.selectedNewPackage?.price || 0;
1514
- sessionInfo = `${this.state.selectedNewPackage?.totalSessions} appointments after purchase`;
1515
- const validityDays = this.state.selectedNewPackage?.validityDays || 365;
1516
- const expiryDate = new Date();
1517
- expiryDate.setDate(expiryDate.getDate() + validityDays);
1518
- dateOfExpiry = expiryDate.toLocaleDateString("en-US", {
1519
- day: "numeric",
1520
- month: "long",
1521
- year: "numeric",
1522
- });
1523
- }
1524
- const patientName = this.state.selectedPatient
1525
- ? `${this.state.selectedPatient.firstName} ${this.state.selectedPatient.lastName}`
1526
- : this.state.patientName || "Patient";
1628
+ const isPackageBooking = this.state.bookingType === "PACKAGE_PURCHASE" ||
1629
+ this.state.bookingType === "USE_ACTIVE_PACKAGE";
1630
+ const getDuration = () => {
1631
+ if (this.state.selectedSlot) {
1632
+ return calculateDuration(this.state.selectedSlot.start, this.state.selectedSlot.end);
1633
+ }
1634
+ return 60;
1635
+ };
1636
+ const getPaymentInfo = () => {
1637
+ if (usingSessionPack) {
1638
+ return {
1639
+ type: "Session Pack",
1640
+ name: this.state.selectedSessionPack?.name || "Session Pack",
1641
+ amount: "Prepaid",
1642
+ remaining: `${(this.state.selectedSessionPack?.remainingSessions || 1) - 1} sessions remaining after this appointment`,
1643
+ };
1644
+ }
1645
+ else if (usingNewPackage) {
1646
+ return {
1647
+ type: "New Package",
1648
+ name: this.state.selectedNewPackage?.name || "Package",
1649
+ amount: `₹${this.state.selectedNewPackage?.price?.toLocaleString() || 0}`,
1650
+ remaining: `${this.state.selectedNewPackage?.totalSessions ||
1651
+ this.state.selectedNewPackage?.sessions ||
1652
+ 1} sessions included`,
1653
+ };
1654
+ }
1655
+ else {
1656
+ return {
1657
+ type: "Single Appointment",
1658
+ name: this.state.consultationMode === "ONLINE"
1659
+ ? "Online Consultation"
1660
+ : "In-Person Visit",
1661
+ amount: this.state.consultationCharge
1662
+ ? `₹${this.state.consultationCharge}`
1663
+ : "Pay at clinic",
1664
+ };
1665
+ }
1666
+ };
1667
+ const paymentInfo = getPaymentInfo();
1668
+ const patient = this.state.selectedPatient;
1669
+ const patientName = patient
1670
+ ? `${patient.firstName} ${patient.lastName}`
1671
+ : this.state.patientName || "New Patient";
1527
1672
  return `
1528
- <div class="medos-appointment-summary">
1529
- <div class="medos-summary-header">
1530
- <h3>Appointment Summary</h3>
1531
- <button class="medos-close-btn" id="medos-btn-close">&times;</button>
1532
- </div>
1533
-
1673
+ <div class="medos-summary-container">
1674
+ <h3 class="medos-summary-title">Appointment Summary</h3>
1675
+ <p class="medos-summary-subtitle">
1676
+ Please review your appointment details before confirming
1677
+ </p>
1678
+
1679
+ <!-- Patient Information -->
1534
1680
  <div class="medos-summary-section">
1535
- <label class="medos-form-label">REVIEW APPOINTMENT DETAILS</label>
1536
-
1537
- <div class="medos-summary-grid">
1681
+ <h4 class="medos-summary-section-title">👤 Patient Information</h4>
1682
+ <div class="medos-summary-card">
1538
1683
  <div class="medos-summary-row">
1539
- <span class="medos-summary-label">Patient:</span>
1684
+ <span class="medos-summary-label">Name</span>
1540
1685
  <span class="medos-summary-value">${this.escapeHtml(patientName)}</span>
1541
1686
  </div>
1542
- ${this.state.patientAge
1543
- ? `<div class="medos-summary-row">
1544
- <span class="medos-summary-label">Age:</span>
1545
- <span class="medos-summary-value">${this.escapeHtml(this.state.patientAge)} years</span>
1546
- </div>`
1547
- : ""}
1548
- ${this.state.bloodGroup
1549
- ? `<div class="medos-summary-row">
1550
- <span class="medos-summary-label">Blood Group:</span>
1551
- <span class="medos-summary-value">${this.escapeHtml(this.state.bloodGroup)}</span>
1552
- </div>`
1687
+ ${patient?.email || this.state.patientEmail
1688
+ ? `
1689
+ <div class="medos-summary-row">
1690
+ <span class="medos-summary-label">Email</span>
1691
+ <span class="medos-summary-value">${this.escapeHtml(patient?.email || this.state.patientEmail || "")}</span>
1692
+ </div>
1693
+ `
1553
1694
  : ""}
1554
1695
  <div class="medos-summary-row">
1555
- <span class="medos-summary-label">Doctor:</span>
1556
- <span class="medos-summary-value">${selectedDoctor
1557
- ? this.escapeHtml(selectedDoctor.name)
1558
- : "Not selected"}</span>
1696
+ <span class="medos-summary-label">Phone</span>
1697
+ <span class="medos-summary-value">${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</span>
1559
1698
  </div>
1560
- ${selectedAddress
1561
- ? `<div class="medos-summary-row">
1562
- <span class="medos-summary-label">Location:</span>
1563
- <span class="medos-summary-value">${this.escapeHtml(selectedAddress.label || "Unknown Location")}</span>
1564
- </div>`
1699
+ ${patient?.age || this.state.patientAge
1700
+ ? `
1701
+ <div class="medos-summary-row">
1702
+ <span class="medos-summary-label">Age</span>
1703
+ <span class="medos-summary-value">${patient?.age || this.state.patientAge} years</span>
1704
+ </div>
1705
+ `
1706
+ : ""}
1707
+ ${patient?.bloodGroup || this.state.bloodGroup
1708
+ ? `
1709
+ <div class="medos-summary-row">
1710
+ <span class="medos-summary-label">Blood Group</span>
1711
+ <span class="medos-summary-value">${this.escapeHtml(patient?.bloodGroup || this.state.bloodGroup || "")}</span>
1712
+ </div>
1713
+ `
1565
1714
  : ""}
1715
+ </div>
1716
+ </div>
1717
+
1718
+ <!-- Appointment Details -->
1719
+ <div class="medos-summary-section">
1720
+ <h4 class="medos-summary-section-title">📅 Appointment Details</h4>
1721
+ <div class="medos-summary-card">
1566
1722
  <div class="medos-summary-row">
1567
- <span class="medos-summary-label">Date & Time:</span>
1568
- <span class="medos-summary-value">${appointmentDate}${startTime ? `, ${startTime}` : ""}</span>
1723
+ <span class="medos-summary-label">Date</span>
1724
+ <span class="medos-summary-value">${formatDate(this.state.selectedDate)}</span>
1569
1725
  </div>
1570
1726
  <div class="medos-summary-row">
1571
- <span class="medos-summary-label">Appointment Type:</span>
1572
- <span class="medos-summary-value">${this.state.consultationMode === "ONLINE"
1573
- ? "In Clinic Consultation"
1574
- : "Online Consultation"}</span>
1727
+ <span class="medos-summary-label">Time</span>
1728
+ <span class="medos-summary-value">${this.state.selectedSlot
1729
+ ? `${formatTime(this.state.selectedSlot.start)} - ${formatTime(this.state.selectedSlot.end)}`
1730
+ : "Not selected"}</span>
1575
1731
  </div>
1576
1732
  <div class="medos-summary-row">
1577
- <span class="medos-summary-label">Service:</span>
1578
- <span class="medos-summary-value">${usingSessionPack
1579
- ? this.escapeHtml(this.state.selectedSessionPack?.name || "Session Pack")
1580
- : usingNewPackage
1581
- ? this.escapeHtml(this.state.selectedNewPackage?.name || "Package")
1582
- : "Standard Consultation"}</span>
1733
+ <span class="medos-summary-label">Duration</span>
1734
+ <span class="medos-summary-value">~${getDuration()} minutes</span>
1583
1735
  </div>
1584
1736
  <div class="medos-summary-row">
1585
- <span class="medos-summary-label">Sessions:</span>
1586
- <span class="medos-summary-value">${sessionInfo || "1 consultation"}</span>
1737
+ <span class="medos-summary-label">Type</span>
1738
+ <span class="medos-summary-value">${this.state.consultationMode === "ONLINE"
1739
+ ? "Online Consultation"
1740
+ : "In-Person Visit"}</span>
1587
1741
  </div>
1588
- ${usingSessionPack || usingNewPackage
1742
+ </div>
1743
+ </div>
1744
+
1745
+ <!-- Doctor & Location -->
1746
+ <div class="medos-summary-section">
1747
+ <h4 class="medos-summary-section-title">🏥 Doctor & Location</h4>
1748
+ <div class="medos-summary-card">
1749
+ ${selectedDoctor
1589
1750
  ? `
1590
1751
  <div class="medos-summary-row">
1591
- <span class="medos-summary-label">Date of Purchase:</span>
1592
- <span class="medos-summary-value">${usingSessionPack
1593
- ? this.state.selectedSessionPack?.purchaseDate ||
1594
- "Previously"
1595
- : "Today"}</span>
1752
+ <span class="medos-summary-label">Doctor</span>
1753
+ <span class="medos-summary-value">${this.escapeHtml(selectedDoctor.name)}${selectedDoctor.specialty
1754
+ ? ` • ${this.escapeHtml(selectedDoctor.specialty)}`
1755
+ : ""}</span>
1596
1756
  </div>
1757
+ `
1758
+ : ""}
1759
+ ${selectedAddress
1760
+ ? `
1597
1761
  <div class="medos-summary-row">
1598
- <span class="medos-summary-label">Date of Expiry:</span>
1599
- <span class="medos-summary-value medos-expiry">${dateOfExpiry}</span>
1762
+ <span class="medos-summary-label">Location</span>
1763
+ <span class="medos-summary-value">${this.escapeHtml(selectedAddress.label || "Unknown Location")}</span>
1600
1764
  </div>
1601
1765
  `
1602
1766
  : ""}
1603
1767
  </div>
1604
1768
  </div>
1605
-
1606
- <div class="medos-summary-total">
1607
- <div class="medos-total-row">
1608
- <span class="medos-total-label">Total Amount</span>
1609
- <span class="medos-total-note">${usingSessionPack ? "Using credits from bundle sessions." : ""}</span>
1769
+
1770
+ <!-- Payment Information -->
1771
+ <div class="medos-summary-section">
1772
+ <h4 class="medos-summary-section-title">💳 Payment</h4>
1773
+ <div class="medos-summary-card">
1774
+ <div class="medos-summary-row">
1775
+ <span class="medos-summary-label">Booking Type</span>
1776
+ <span class="medos-summary-value">${paymentInfo.type}</span>
1777
+ </div>
1778
+ <div class="medos-summary-row">
1779
+ <span class="medos-summary-label">${usingSessionPack ? "Pack" : "Service"}</span>
1780
+ <span class="medos-summary-value">${paymentInfo.name}</span>
1781
+ </div>
1782
+ ${isPackageBooking && this.state.selectedNewPackage
1783
+ ? `
1784
+ <div class="medos-summary-row medos-summary-total-row">
1785
+ <span class="medos-summary-total-label">Amount</span>
1786
+ <div class="medos-summary-price-container">
1787
+ ${this.state.selectedNewPackage.discountedPrice &&
1788
+ this.state.selectedNewPackage.discountedPrice <
1789
+ this.state.selectedNewPackage.price
1790
+ ? `
1791
+ <span class="medos-summary-strikethrough-price">
1792
+ ₹${this.state.selectedNewPackage.price.toLocaleString()}
1793
+ </span>
1794
+ <span class="medos-summary-total-value">
1795
+ ₹${this.state.selectedNewPackage.discountedPrice.toLocaleString()}
1796
+ </span>
1797
+ ${this.state.selectedNewPackage.discount
1798
+ ? `
1799
+ <span class="medos-summary-discount-badge">
1800
+ ${this.state.selectedNewPackage.discountType ===
1801
+ "PERCENTAGE"
1802
+ ? `${this.state.selectedNewPackage.discount}% OFF`
1803
+ : `₹${this.state.selectedNewPackage.discount} OFF`}
1804
+ </span>
1805
+ `
1806
+ : ""}
1807
+ `
1808
+ : `
1809
+ <span class="medos-summary-total-value">
1810
+ ₹${this.state.selectedNewPackage.price.toLocaleString()}
1811
+ </span>
1812
+ `}
1813
+ </div>
1814
+ </div>
1815
+ `
1816
+ : !usingSessionPack
1817
+ ? `
1818
+ <div class="medos-summary-row medos-summary-total-row">
1819
+ <span class="medos-summary-total-label">Amount</span>
1820
+ <span class="medos-summary-total-value">${paymentInfo.amount}</span>
1821
+ </div>
1822
+ `
1823
+ : `
1824
+ <div class="medos-summary-row medos-summary-total-row">
1825
+ <span class="medos-summary-total-label">Amount</span>
1826
+ <span class="medos-summary-total-value">${paymentInfo.amount}</span>
1827
+ </div>
1828
+ `}
1829
+ ${paymentInfo.remaining
1830
+ ? `
1831
+ <div class="medos-summary-remaining-info">${paymentInfo.remaining}</div>
1832
+ `
1833
+ : ""}
1610
1834
  </div>
1611
- <div class="medos-total-amount">₹${totalAmount.toLocaleString()}</div>
1612
1835
  </div>
1613
-
1836
+
1837
+ <!-- Actions -->
1614
1838
  <div class="medos-actions">
1615
- <button class="medos-btn medos-btn-back" id="medos-btn-back">${VanillaIcons.arrowLeft(14)} Back</button>
1616
- <button class="medos-btn medos-btn-secondary" id="medos-btn-cancel">Cancel</button>
1617
- <button class="medos-btn medos-btn-primary" id="medos-btn-confirm">Confirm Book</button>
1839
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
1840
+ <button class="medos-btn medos-btn-primary" id="medos-btn-confirm" ${this.state.loading ? "disabled" : ""}>${this.state.loading ? "Confirming..." : "Confirm Appointment"}</button>
1618
1841
  </div>
1619
1842
  </div>
1620
1843
  `;
@@ -1726,7 +1949,8 @@ class AppointmentCalendarWidget {
1726
1949
  });
1727
1950
  const slotCards = this.container.querySelectorAll(".medos-slot-card, .medos-slot-btn");
1728
1951
  slotCards.forEach((card) => {
1729
- card.addEventListener("click", () => {
1952
+ card.addEventListener("click", (e) => {
1953
+ e.preventDefault();
1730
1954
  const slotId = card.dataset.slotId;
1731
1955
  const slotStart = card.dataset.slotStart;
1732
1956
  const slotEnd = card.dataset.slotEnd;
@@ -1877,82 +2101,23 @@ class AppointmentCalendarWidget {
1877
2101
  this.updatePatientDetailsButtonState();
1878
2102
  });
1879
2103
  }
1880
- const patientDobInput = this.container.querySelector("#medos-patient-dob");
1881
- if (patientDobInput) {
1882
- patientDobInput.addEventListener("input", (e) => {
1883
- const target = e.target;
1884
- const dobValue = target.value;
1885
- this.setState({ patientDob: dobValue });
1886
- this.setState({ error: null });
1887
- if (dobValue && dobValue.trim() !== "") {
1888
- try {
1889
- if (!this.isValidDateOfBirth(dobValue)) {
1890
- target.classList.add("medos-input-error");
1891
- const today = new Date();
1892
- const inputDate = new Date(dobValue);
1893
- if (inputDate > today) {
1894
- target.title = "Date of birth cannot be in the future";
1895
- }
1896
- else if (!/^\d{4}-\d{2}-\d{2}$/.test(dobValue)) {
1897
- target.title = "Please use YYYY-MM-DD format";
1898
- }
1899
- else {
1900
- target.title = "Please enter a valid date";
1901
- }
1902
- }
1903
- else {
1904
- target.classList.remove("medos-input-error");
1905
- target.title = "";
1906
- }
1907
- }
1908
- catch (error) {
1909
- console.warn("Date validation error:", error);
1910
- target.classList.add("medos-input-error");
1911
- target.title = "Date validation failed";
1912
- }
1913
- }
1914
- else {
1915
- target.classList.remove("medos-input-error");
1916
- target.title = "";
1917
- }
1918
- this.updatePatientDetailsButtonState();
1919
- this.updateSubmitButtonState();
1920
- });
1921
- patientDobInput.addEventListener("blur", (e) => {
1922
- const target = e.target;
1923
- const dobValue = target.value;
1924
- if (dobValue && dobValue.trim() !== "") {
1925
- try {
1926
- if (!this.isValidDateOfBirth(dobValue)) {
1927
- target.classList.add("medos-input-error");
1928
- const today = new Date();
1929
- const inputDate = new Date(dobValue);
1930
- if (inputDate > today) {
1931
- this.displayFieldValidationError("dateOfBirth", "future");
1932
- }
1933
- else if (!/^\d{4}-\d{2}-\d{2}$/.test(dobValue)) {
1934
- this.displayFieldValidationError("dateOfBirth", "format");
1935
- }
1936
- else {
1937
- this.displayFieldValidationError("dateOfBirth", "invalid");
1938
- }
1939
- }
1940
- }
1941
- catch (error) {
1942
- console.warn("Date validation error on blur:", error);
1943
- this.displayFieldValidationError("dateOfBirth", "invalid");
1944
- }
1945
- }
1946
- });
1947
- }
1948
2104
  const optionCards = this.container.querySelectorAll(".medos-option-card");
1949
2105
  optionCards.forEach((card) => {
1950
2106
  card.addEventListener("click", () => {
1951
2107
  const option = card.dataset.option;
2108
+ const packId = card.dataset.packId
2109
+ ? Number.parseInt(card.dataset.packId, 10)
2110
+ : null;
1952
2111
  if (option === "new-appointment" ||
1953
2112
  option === "session-pack" ||
1954
2113
  option === "explore-packages") {
1955
2114
  this.state.bookingOptionType = option;
2115
+ if (option === "session-pack" && packId) {
2116
+ const pack = this.state.userSessionPacks.find((p) => p.id === packId);
2117
+ if (pack) {
2118
+ this.state.selectedSessionPack = pack;
2119
+ }
2120
+ }
1956
2121
  this.render();
1957
2122
  }
1958
2123
  });
@@ -1999,8 +2164,7 @@ class AppointmentCalendarWidget {
1999
2164
  : this.getPlaceholderPatients();
2000
2165
  const patient = patients.find((p) => p.id === patientId);
2001
2166
  if (patient) {
2002
- this.state.selectedPatient = patient;
2003
- this.render();
2167
+ this.handleSelectExistingPatient(patient);
2004
2168
  }
2005
2169
  });
2006
2170
  });
@@ -2024,10 +2188,6 @@ class AppointmentCalendarWidget {
2024
2188
  if (backBtn) {
2025
2189
  backBtn.addEventListener("click", () => this.goBack());
2026
2190
  }
2027
- const cancelBtn = this.container.querySelector("#medos-btn-cancel");
2028
- if (cancelBtn) {
2029
- cancelBtn.addEventListener("click", () => this.reset());
2030
- }
2031
2191
  const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
2032
2192
  if (sendOtpBtn) {
2033
2193
  sendOtpBtn.addEventListener("click", () => {
@@ -2080,12 +2240,46 @@ class AppointmentCalendarWidget {
2080
2240
  const addPatientBtn = this.container.querySelector("#medos-btn-add-patient");
2081
2241
  if (addPatientBtn) {
2082
2242
  addPatientBtn.addEventListener("click", () => {
2083
- this.state.selectedPatient = null;
2084
- this.state.useExistingPatient = false;
2085
- this.goToNext();
2243
+ this.handleProceedAsNewPatient();
2086
2244
  });
2087
2245
  }
2088
2246
  }
2247
+ handleSelectExistingPatient(patient) {
2248
+ this.state.selectedPatient = patient;
2249
+ this.state.useExistingPatient = true;
2250
+ this.state.patientName = `${patient.firstName} ${patient.lastName}`;
2251
+ this.state.patientEmail = patient.email;
2252
+ this.state.patientAge = patient.age.toString();
2253
+ this.state.patientGender = patient.gender;
2254
+ this.state.bloodGroup = patient.bloodGroup;
2255
+ if (patient.address) {
2256
+ this.state.patientAddress = patient.address.addressLine1;
2257
+ this.state.patientCity = patient.address.city;
2258
+ this.state.patientState = patient.address.state;
2259
+ this.state.patientCountry = patient.address.country;
2260
+ this.state.patientZipcode = patient.address.zipcode;
2261
+ this.state.patientLandmark = patient.address.landmark || "";
2262
+ }
2263
+ this.state.step = 6;
2264
+ this.render();
2265
+ }
2266
+ handleProceedAsNewPatient() {
2267
+ this.state.selectedPatient = null;
2268
+ this.state.useExistingPatient = false;
2269
+ this.state.patientName = "";
2270
+ this.state.patientEmail = "";
2271
+ this.state.patientAge = "";
2272
+ this.state.patientGender = "";
2273
+ this.state.bloodGroup = "";
2274
+ this.state.patientAddress = "";
2275
+ this.state.patientCity = "";
2276
+ this.state.patientState = "";
2277
+ this.state.patientCountry = "";
2278
+ this.state.patientZipcode = "";
2279
+ this.state.patientLandmark = "";
2280
+ this.state.step = 5;
2281
+ this.render();
2282
+ }
2089
2283
  getPlaceholderPatients() {
2090
2284
  return [
2091
2285
  {
@@ -2265,6 +2459,68 @@ class AppointmentCalendarWidget {
2265
2459
  }
2266
2460
  this.setState({ error: errorMessage });
2267
2461
  }
2462
+ async handleSubmitAppointment() {
2463
+ await this.submitAppointment();
2464
+ }
2465
+ async handleOtpVerification(otpCode) {
2466
+ this.state.otpCode = otpCode;
2467
+ await this.verifyOtp();
2468
+ if (this.state.otpVerified) {
2469
+ this.goToNext();
2470
+ }
2471
+ }
2472
+ handleAddressSelect(addressId) {
2473
+ return this.handleAddressChange(addressId);
2474
+ }
2475
+ handleDoctorSelect(doctorId) {
2476
+ this.state.selectedDoctor = doctorId;
2477
+ const selectedDoc = this.doctors.find((d) => d.id === doctorId);
2478
+ if (selectedDoc?.consultationCharge) {
2479
+ this.state.consultationCharge = selectedDoc.consultationCharge;
2480
+ }
2481
+ this.render();
2482
+ }
2483
+ handleDateSelect(date) {
2484
+ this.state.selectedDate = date;
2485
+ this.loadSlots();
2486
+ }
2487
+ handleSlotSelect(slot) {
2488
+ this.state.selectedSlot = slot;
2489
+ this.render();
2490
+ }
2491
+ handleSessionPackSelect(sessionPack) {
2492
+ this.state.selectedSessionPack = sessionPack;
2493
+ this.state.bookingOptionType = "session-pack";
2494
+ this.render();
2495
+ }
2496
+ handleExplorePackages() {
2497
+ this.state.bookingOptionType = "explore-packages";
2498
+ this.state.showPackageExplorer = true;
2499
+ this.render();
2500
+ }
2501
+ handlePackageSelect(packageItem) {
2502
+ this.state.selectedNewPackage = packageItem;
2503
+ this.render();
2504
+ }
2505
+ handleNewAppointmentSelect() {
2506
+ this.state.bookingOptionType = "new-appointment";
2507
+ this.render();
2508
+ }
2509
+ handlePatientSelect(patient) {
2510
+ this.handleSelectExistingPatient(patient);
2511
+ }
2512
+ handleNewPatient() {
2513
+ this.handleProceedAsNewPatient();
2514
+ }
2515
+ goToNextStep() {
2516
+ this.goToNext();
2517
+ }
2518
+ goBackStep() {
2519
+ this.goBack();
2520
+ }
2521
+ getState() {
2522
+ return { ...this.state };
2523
+ }
2268
2524
  destroy() {
2269
2525
  this.mounted = false;
2270
2526
  this.container.innerHTML = "";