From 364b5d29bf961af42e4371632941064a2591ba97 Mon Sep 17 00:00:00 2001 From: Adam Caouette Date: Wed, 4 Mar 2026 08:06:27 -0500 Subject: [PATCH] CASH-2378: implement new multi location modal --- src/layouts/schedule/schedule.vue | 198 +++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 5 deletions(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index e1dd86e06..dbdaaa90f 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -6,6 +6,11 @@ modalWidgetName="MobileFirstModalWidget" ref="mobileFirstModal" @confirm-appointment="updateMobileFirstTimeSlotandNavigateForward" /> +
+ :class="[ + isMobileFirstModalOpen || isMultiLocationModalOpen ? 'hidden-background' : '', + ]">
{ - vm.showMobileFirstModal(); + // vm.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + vm.showMultiLocationModal(); vm.hideLoadingModal(); vm.calendarLoadingStatus = "none"; if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable(); @@ -930,6 +940,11 @@ export default { ? this.$refs.mobileFirstModal?.getIsModalOpen() : false; }, + isMultiLocationModalOpen() { + return this.selectableDatesMobile.days && this.selectableDatesInshop.days + ? this.$refs.multiLocationModal?.getIsModalOpen() + : false; + }, displayWaitList() { if (!this.appointmentType) return false; const firstMobileDateString = @@ -990,6 +1005,12 @@ export default { "true" ); }, + isShowMultiLocationPopup() { + return experimentMixin.methods.hasSettingEqualTo( + experimentSettings.SHOW_MULTI_LOCATION_APPT, + "true" + ); + }, }, methods: { splitCopyOnCMSPlaceHolder, @@ -1275,7 +1296,8 @@ export default { } } this.initializeDatePicker().then(() => { - this.showMobileFirstModal(); + // this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + this.showMultiLocationModal(); this.hideLoadingModal(); this.calendarLoadingStatus = "none"; this.setSelectedDateToFirstAvailable(); @@ -1309,7 +1331,7 @@ export default { }, async initializeDatePicker() { - this.isShowMobileFirstAppt && this.showLoadingModal(); + this.isShowMultiLocationPopup && this.showLoadingModal(); const includeMobileTimeSlots = this.isServiceableMobile; const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff; @@ -1821,7 +1843,8 @@ export default { this.zipCodeCtu = newZipCode.zipCodeCtu; this.selectedDate = null; this.initializeDatePicker().then(() => { - this.showMobileFirstModal(); + // this.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST + this.showMultiLocationModal(); this.hideLoadingModal(); this.calendarLoadingStatus = "none"; this.setSelectedDateToFirstAvailable(); @@ -2064,6 +2087,157 @@ export default { } } }, + async showMultiLocationModal() { + if (!this.selectedRouteCodeData?.routeCode) { + let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION + ); + let maxDayRangeToShowNoPmTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_NO_PM_DAYS_MULTI_LOCATION + ); + let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION + ); + let showMultiLocationAppointment = experimentMixin.methods.getSettingValue( + experimentSettings.SHOW_MULTI_LOCATION_APPT + ); + + // temp vars to use for testing; TO BE REMOVED + maxDayRangeToShowPmTimeslot = 2; + maxDayRangeToShowNoPmTimeslot = 2; + maxDayRangeToShowAnyTimeslot = 5; + + let promotedMobileAppointment = null; + let promotedInshopAppointment = null; + const todaysDate = getTodayDate(); + + // Helper function to calculate days until appointment (handles null/undefined) + const calculateDaysUntilDate = (appointmentObj) => { + const dateString = appointmentObj?.date; + return dateString + ? (new Date(dateString) - todaysDate) / (1000 * 60 * 60 * 24) + : null; + }; + + // Fetch all appointments + const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAMAppt, firstInshopPMAppt] = + await Promise.all([ + this.getFirstAvailableApptByTOD("AM", "mobile"), + this.getFirstAvailableApptByTOD("PM", "mobile"), + this.getFirstAvailableApptByTOD("AM", "inshop"), + this.getFirstAvailableApptByTOD("PM", "inshop"), + ]); + + // Extract dates + const firstMobileAMDate = firstMobileAMAppt?.date; + const firstMobilePMDate = firstMobilePMAppt?.date; + const firstInshopAMDate = firstInshopAMAppt?.date; + const firstInshopPMDate = firstInshopPMAppt?.date; + + // Calculate days until appointments + const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt); + const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt); + const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAMAppt); + const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopPMAppt); + + const shouldExposeMultiLocationModal = () => { + const isMobileAppointmentInDateRange = + (firstMobileAMDate && + numberOfDaysToFirstMobileAMDate <= maxDayRangeToShowAnyTimeslot) || + (firstMobilePMDate && + numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowAnyTimeslot); + const isInshopAppointmentInDateRange = + (firstInshopAMDate && + numberOfDaysToFirstInshopAMDate <= maxDayRangeToShowAnyTimeslot) || + (firstInshopPMDate && + numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot); + + if (showMultiLocationAppointment) { + return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange; + } + return false; + }; + + if (shouldExposeMultiLocationModal()) { + const multiLocationPopupExperiment = + store.getters.applicationUser.experiments.find( + (e) => e.universeName === experimentUniverses.MULTI_LOCATION_POPUP + ); + const hasExposedMultiLocationPopup = multiLocationPopupExperiment?.isExposed; + + if (!hasExposedMultiLocationPopup && multiLocationPopupExperiment) { + baseMixin.methods.dispatchStoreActionWithLogging( + storeActions.LOG_EXPERIMENT_EXPOSURE_AND_UPDATE_STORE, + { + userId: getUserIdValue(), + deviceId: getDeviceIdValue(), + sessionKey: getSessionKeyValue(), + pageName: "schedule", + experiment: multiLocationPopupExperiment, + }, + "schedule", + false + ); + } + if (this.isShowMultiLocationPopup) { + if (maxDayRangeToShowPmTimeslot == 0) { + // Selects the first available mobile time slot + promotedMobileAppointment = + numberOfDaysToFirstMobileAMDate <= numberOfDaysToFirstMobilePMDate + ? firstMobileAMAppt + : firstMobilePMAppt; + // Selects the first available inshop time slot + promotedInshopAppointment = + numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate + ? firstInshopAMAppt + : firstInshopPMAppt; + } else { + if ( + firstMobilePMDate && + numberOfDaysToFirstMobilePMDate <= maxDayRangeToShowPmTimeslot + ) { + // Selects the first available PM time slot + promotedMobileAppointment = firstMobilePMAppt; + } else if ( + firstMobilePMDate && + numberOfDaysToFirstMobilePMDate >= maxDayRangeToShowNoPmTimeslot + ) { + // Selects the first available AM time slot. If none, selects the first available PM time slot + promotedMobileAppointment = firstMobileAMAppt + ? firstMobileAMAppt + : firstMobilePMAppt; + } + + if ( + firstInshopPMDate && + numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot + ) { + // Selects the first available PM time slot + promotedInshopAppointment = firstInshopPMAppt; + } else if ( + firstInshopPMDate && + numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot + ) { + // Selects the first available AM time slot. If none, selects the first available PM time slot + promotedInshopAppointment = firstInshopAMAppt + ? firstInshopAMAppt + : firstInshopPMAppt; + } + } + + if (promotedMobileAppointment && promotedInshopAppointment) { + this.$refs.multiLocationModal.setPromotedMobileAppointment( + promotedMobileAppointment + ); + this.$refs.multiLocationModal.setPromotedInshopAppointment( + promotedInshopAppointment + ); + this.$refs.multiLocationModal.openModal(); + } + } + } + } + }, async getFirstAvailableApptByTOD(timeOfDay, appointmentType) { let selectableDays; if (appointmentType?.toLowerCase() == "mobile") { @@ -2084,6 +2258,7 @@ export default { }, timeSlot: null, date: null, + addressCopy: null, }; const isMatchingApptDay = dateObj.timeSlots.some( (slot) => @@ -2116,6 +2291,18 @@ export default { this.updateTimeSlot(timeSlotObj); this.forwardButtonAction(); }, + updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) { + if (appointmentType?.toLowerCase() == "mobile") { + this.appointmentType = AppointmentTypeStrings.MOBILE; + } else if (appointmentType?.toLowerCase() == "inshop") { + this.appointmentType = AppointmentTypeStrings.IN_SHOP; + } + this.selectedMultiLocationAppointment = true; + this.selectedDate = selectedTimeSlot.date; + this.updateSelectedProvider(); + this.updateTimeSlot(selectedTimeSlot); + this.forwardButtonAction(); + }, updateRecalAcknowledgedAndNavigateForward(isAcknowledged) { this.isRecalAcknowledgedForScheduling = isAcknowledged; this.forwardButtonAction(); @@ -2139,6 +2326,7 @@ export default { textBlock, timeSlotQuestion, mobileFirstModal, + multiLocationModal, recalAckModal, alert, serviceZipModalQuestion,