CASH-2378: implement new multi location modal

This commit is contained in:
Adam Caouette 2026-03-04 08:06:27 -05:00
parent e5aa47673b
commit 364b5d29bf

View file

@ -6,6 +6,11 @@
modalWidgetName="MobileFirstModalWidget"
ref="mobileFirstModal"
@confirm-appointment="updateMobileFirstTimeSlotandNavigateForward" />
<multiLocationModal
modalWidgetName="MultiLocationModalWidget"
buttonWidgetName="MultiLocationRadioButtonWidget"
ref="multiLocationModal"
@confirm-appointment="updateMultiLocationTimeSlotandNavigateForward" />
<recalAckModal
modalWidgetName="RecalAckModalWidget"
agreementWidgetName="RecalAgreementQuestionWidget"
@ -14,7 +19,9 @@
ref="recalAckModal" />
<div
class="container page-container-grouped-styles"
:class="[isMobileFirstModalOpen ? 'hidden-background' : '']">
:class="[
isMobileFirstModalOpen || isMultiLocationModalOpen ? 'hidden-background' : '',
]">
<div class="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<mobileFeeWaiverAlert
@ -201,6 +208,7 @@ import shopLocation from "@/layouts/schedule/shop-location/shop-location";
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
import mobileFirstModal from "@/layouts/schedule/mobile-first-modal/mobile-first-modal.vue";
import multiLocationModal from "@/layouts/schedule/multi-location-modal/multi-location-modal.vue";
import recalAckModal from "@/layouts/schedule/recal-ack-modal/recal-ack-modal.vue";
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
@ -488,6 +496,7 @@ export default {
shopListButton: shopListButton,
lastSelectedInshopOrDropoffProvider: null,
selectedMobileFirstAppointment: false,
selectedMultiLocationAppointment: false,
calendarLoadingStatus: "none",
};
},
@ -620,7 +629,8 @@ export default {
shopProviderData.data
);
vm.initializeDatePicker().then(() => {
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,