DigitalConsumer.ISS/src/layouts/schedule-page/schedule-page.vue
2026-03-25 14:27:12 -04:00

977 lines
44 KiB
Vue

<template>
<Form
ref="theForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="fade-on-route-transition">
<div class="justify-content-center">
<siteHeader
cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
<div class="schedule-page-container iss-heritage-content-container-width">
<siteSubHeader
cmsWidgetName="ScheduleSubHeaderWidget"
secondaryTextClasses="text-center small sub-text"
class="mt-4" />
<div class="service-location-content-container">
<serviceLocation
ref="serviceLocation"
:schedulingInshopAvailabilityRating="inShopAvailabilityRating"
@appointmentTypeChanged="appointmentTypeChangedFromServiceLocation"
@cityUpdated="cityUpdatedFromServiceLocation"
@clearMobileData="clearMobileDataFromServiceLocation"
@inShopZipUpdated="inShopZipUpdatedFromServiceLocation"
@mobileZipUpdated="mobileZipUpdatedFromServiceLocation"
@providerChanged="providerChangedFromServiceLocation" />
</div>
</div>
<div
v-show="hasNeededServiceLocationData"
class="date-picker-container iss-heritage-content-container-width">
<div class="date-picker-content-container">
<locationAlerts
ref="locationAlerts"
cmsWidgetPrefix="LocationAlert-"
:city="selectedServiceLocationCity" />
<datePicker
ref="datePicker"
v-model="selectedTimeSlotInfo"
customComponentId="dateQuestion"
selectableDatesSetting="custom"
class="text-link-small"
:activeAppointmentType="selectedAppointmentType"
:isMobileView="isMobileView"
:showTimeSlotError="showDatePickerError"
:customSelectableDatesCallback="getAvailableDatesMethod"
@dateSelected="dateSelectedFromPicker"
@timeSlotSelected="timeSlotSelectedFromPicker" />
</div>
</div>
<div class="site-footer-container iss-heritage-content-container-width">
<siteFooter
ref="navbar"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardButtonHidden="!hasNeededServiceLocationData"
:isForwardButtonNavigationDisabled="!isFormValid"
@backClicked="navigateBack(this, navigateBackScenario)"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
<suggestTimeslotModal
:ref="SUGGEST_TIMESLOT_MODAL_REF_NAME"
cmsWidgetName="SuggestTimeslotModal"
@confirmAppointmentClicked="autoSelectTimeslotConfirmed" />
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import locationAlerts from '@/layouts/schedule-page/location-alerts/location-alerts.vue';
import datePicker from '@/digital-components/date-picker/date-picker.vue';
import serviceLocation from '@/layouts/schedule-page/service-location/service-location.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import suggestTimeslotModal from '@/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue';
// Supporting files
import { experimentSettings, experimentUniverses } from '@/constants/experiments';
import {
AppointmentTypeStrings,
GET_MOBILE_TIME_SLOTS,
GET_SHOP_TIME_SLOTS,
PREMIUM_FEE_PART_TYPE
} from '@/constants/schedule-constants.js';
import {
fetchCmsContentForPage,
splitCopyOnCMSPlaceHolder
} from '@/helpers/cms-content-helper';
import {
calcDaysBetweenDates,
convertDateToDateString,
sumDateString
} from '@/helpers/date-helper';
import settleAllPromises from '@/helpers/layout-helper';
import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
import {
getPricedMobileFeePart,
getMobileZipCodeData,
getZipCodeData,
mapTimeSlot
} from '@/helpers/service-location-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
// Define constants
const SUGGEST_TIMESLOT_MODAL_REF_NAME = 'SuggestTimeslotModal';
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
const getAvailableDates = async (
startDateString,
endDateString,
appointmentType,
providerNumber,
mobileZipCodeOverride = null
) => {
const apiEndDateLimit = sumDateString(
startDateString,
TIME_SLOTS_CALL_DAYS_LIMIT - 1
);
const difference = calcDaysBetweenDates(startDateString, endDateString);
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
const storeActionConfigs = [];
let apiStartDate = startDateString;
let apiEndDate = endDateString;
for (let i = 1; i <= apiCallsCount; i++) {
let storeActionConfig;
if (i > 1) {
apiStartDate = sumDateString(apiEndDate, 1);
apiEndDate = sumDateString(
apiStartDate,
TIME_SLOTS_CALL_DAYS_LIMIT - 1
);
if (i === apiCallsCount) {
apiEndDate = endDateString;
}
} else if (apiEndDate > apiEndDateLimit) {
apiEndDate = apiEndDateLimit;
}
if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
storeActionConfig = {
storeAction: GET_MOBILE_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
zipCodeOverride: mobileZipCodeOverride
}
};
} else {
storeActionConfig = {
storeAction: GET_SHOP_TIME_SLOTS,
payload: {
startDate: apiStartDate,
endDate: apiEndDate,
shopAppointmentType: appointmentType,
providerNumber
}
};
}
if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig);
}
const timeSlotsResponsesData = {
days: []
};
function compareDayStrings(a, b) {
if (a.date < b.date) return -1;
if (a.date > b.date) return 1;
return 0;
}
const makeParallelCalls = async () => {
await Promise.all(storeActionConfigs.map(async (storeAction) => {
let timeSlotsResponse = null;
if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) {
if (storeAction.payload.providerNumber) {
timeSlotsResponse = await useMainStore().getShopTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
);
}
} else if (storeAction.payload?.zipCodeOverride) {
timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate,
storeAction.payload.endDate,
storeAction.payload.zipCodeOverride
);
}
if (!timeSlotsResponse || !timeSlotsResponse.data) {
return;
}
timeSlotsResponsesData.estimatedServiceMinutesMinimum = timeSlotsResponse.data.estimatedServiceMinutesMinimum;
timeSlotsResponsesData.estimatedServiceMinutesMaximum = timeSlotsResponse.data.estimatedServiceMinutesMaximum;
timeSlotsResponsesData.days = [
...timeSlotsResponsesData.days,
...timeSlotsResponse.data.days
];
}));
};
return makeParallelCalls().then(() => {
// sort days chronologically
timeSlotsResponsesData.days.sort(compareDayStrings);
return timeSlotsResponsesData;
})
.catch(() => {
return timeSlotsResponsesData;
});
};
export default {
name: 'schedule-page',
components: {
siteHeader,
siteSubHeader,
locationAlerts,
datePicker,
serviceLocation,
siteFooter,
suggestTimeslotModal,
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
let storeServiceLocation = useMainStore().order.serviceLocation;
let storeSelectedProvider = storeServiceLocation?.provider;
const storeSelectedSchedule = useMainStore().order.schedule;
if (storeSelectedProvider?.providerNumber && !storeSelectedSchedule?.routeCode) {
useMainStore().resetServiceLocationProvider();
storeServiceLocation = useMainStore().order.serviceLocation;
storeSelectedProvider = storeServiceLocation?.provider;
}
const storeSelectedAppointmentType = storeServiceLocation?.appointmentType;
const isMobileAppointment = storeSelectedAppointmentType === AppointmentTypeStrings.MOBILE
|| storeSelectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceZipCode = storeServiceLocation?.zipCode || useMainStore().order.customer.address.zipCode;
const zipCodeData = getZipCodeData(serviceZipCode);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = useMainStore().getServiceabilityDetails(serviceZipCode);
const getGlassFeesPromise = useMainStore().getGlassFees();
const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode, 150);
const premiumFeePromise = useMainStore().getMobilePremiumFee();
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
return useMainStore().getCombinedQuote(result.data);
}
return result.data;
});
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
},
{
resultKey: 'mobileFeePart',
promise: mobileFeePartPromise
},
{
resultKey: 'glassFees',
promise: getGlassFeesPromise
},
{
resultKey: 'serviceabilityDetails',
promise: serviceabilityDetailsPromise
},
{
resultKey: 'zipCodeData',
promise: zipCodeData
},
{
resultKey: 'providers',
promise: providersPromise
},
{
resultKey: 'premiumFeeWithPrice',
promise: premiumFeeWithPricePromise
}
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
const providersUnderOneHundredMiles = resultMap.providers?.shopProviders?.filter((provider) => provider.distanceInMiles <= 100);
const serviceLocationData = {
defaultMobileZipCode: isMobileAppointment
? storeServiceLocation?.zipCode
: '',
glassFees: resultMap.glassFees,
mobileFeePart: resultMap.mobileFeePart,
providers: {
...resultMap.providers,
shopProviders: providersUnderOneHundredMiles || []
},
serviceabilityDetails: resultMap.serviceabilityDetails,
zipCodeData: resultMap.zipCodeData
};
useMainStore().updateIsSafeliteProvider(true);
next(async (vm) => {
const foundProviderInFullList = resultMap.providers?.shopProviders
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || null;
const foundProviderInHundredMiles = providersUnderOneHundredMiles
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || null;
if (!foundProviderInHundredMiles && !foundProviderInFullList) {
if (storeSelectedProvider?.providerNumber && resultMap.providers?.shopProviders) {
serviceLocationData.providers?.shopProviders.push(storeSelectedProvider);
}
} else if (!foundProviderInHundredMiles && foundProviderInFullList) {
serviceLocationData.providers?.shopProviders.push(foundProviderInFullList);
}
const initialServiceLocationObj = {
mobileProviderNumber: isMobileAppointment
? storeSelectedProvider?.providerNumber
: null,
provider: isMobileAppointment ? null : foundProviderInFullList || resultMap.providers?.shopProviders[0] || null,
zipCode: useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode,
zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu
};
const mobileFirstObject = {
mobileProviderNumber: resultMap.providers?.mobileProviderNumber,
serviceabilityDetails: resultMap.serviceabilityDetails,
zipCode: initialServiceLocationObj.zipCode
}
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.serviceLocation.initializeComponent(serviceLocationData);
await vm.setData(resultMap.premiumFeeWithPrice, initialServiceLocationObj);
await vm.checkMobileFirstExperience(mobileFirstObject);
showIssLoadingModal(false);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
inShopAvailabilityRating: 'high',
inShopDatesData: [],
isDatePickerRefreshing: false,
isMobileView: false,
mobileDatesData: [],
mobileFeePart: null,
mobilePremiumAppointmentFee: null,
mobileProviderNumber: null,
selectableDatesData: [],
selectedAppointmentType: this.getAppointmentType(),
selectedDate: this.getSelectedDate(),
selectedMobileZipCode: null,
selectedProvider: this.getSelectedProvider(),
selectedServiceLocation: this.getServiceLocation(),
selectedServiceLocationCity: this.getServiceLocationCity(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
showDatePickerError: false,
SUGGEST_TIMESLOT_MODAL_REF_NAME
};
},
computed: {
hasNeededServiceLocationData() {
const serviceLocationToUse = this.selectedServiceLocation;
if (!serviceLocationToUse || !this.selectedAppointmentType) {
return false;
}
const validMobileAppointmentType = ((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
&& serviceLocationToUse.mobileProviderNumber);
const validInShopAppointmentType = ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP
|| this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF)
&& (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber));
const serviceLocationPreReqs = serviceLocationToUse.zipCode !== null
&& serviceLocationToUse.zipCodeCtu !== null
&& serviceLocationToUse.appointmentType !== null
&& (validMobileAppointmentType || validInShopAppointmentType)
&& !this.isDatePickerRefreshing;
const damageInfo = useMainStore().order.damage.isRepair
|| (useMainStore().order.lineItems?.glassParts != null && useMainStore().order.lineItems.glassParts.length > 0);
const supportingItems = this.supportingItems !== null;
return (serviceLocationPreReqs && supportingItems && damageInfo);
},
isFormValid() {
const hasTimeSlotSelected = this.selectedTimeSlotInfo?.timeSlot?.routeCode != null;
return hasTimeSlotSelected;
},
navigateBackScenario() {
const { isNoComp, isITAC } = useMainStore();
return isNoComp || isITAC
? this.navigationScenarios.CLICKED_BACK_CANNOT_REACH_TPA_FLOW
: this.navigationScenarios.CLICKED_BACK_CAN_REACH_TPA_FLOW;
},
supportingItems() {
return useMainStore().lineItems.supportingItems;
}
},
watch: {
inShopDatesData(newData) {
if (newData?.initialShopTimeSlotsResponse?.days?.length > 0) {
const numberOfDaysNeededToBeHighAvailability = 2;
const availabilityEndDate = new Date();
availabilityEndDate.setDate(availabilityEndDate.getDate() + 6);
const datesInAvailabilityRange = newData.initialShopTimeSlotsResponse.days.filter((day) => {
const dayDate = new Date(`${day.date}T00:00:00`);
return dayDate <= availabilityEndDate;
});
const isGoodAvailability =
datesInAvailabilityRange.filter((x) => x.timeSlots.length > 0).length
>= numberOfDaysNeededToBeHighAvailability;
this.inShopAvailabilityRating = isGoodAvailability ? 'high' : 'low';
} else {
this.inShopAvailabilityRating = 'low';
}
}
},
mounted() {
showIssLoadingModal(true);
this.mql = window.matchMedia('(min-width: 1200px)');
this.isMobileView = !this.mql.matches;
this.mql.addEventListener('change', this.handleMqlChange);
},
unmounted() {
if (this.mql) {
this.mql.removeEventListener('change', this.handleMqlChange);
}
},
methods: {
splitCopyOnCMSPlaceHolder,
arePagePrerequisitesValid() {
return useMainStore().order.serviceLocation.zipCode !== null;
},
async autoSelectTimeslotConfirmed(emittedValue) {
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
console.log('User is No Comp or ITAC, skipping mobile fee update on auto select timeslot confirmed');
// this.mainStore.updateMobileFee(this.mobileFeePart);
}
const providerToUse = {
providerNumber: emittedValue.mobileProviderNumber,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null
}
};
this.mainStore.updateServiceLocation({
state: emittedValue.zipCodeData.state,
zipCode: emittedValue.zipCodeData.zipCode,
zipCodeCtu: emittedValue.zipCodeData.zipCodeCtu,
appointmentType: AppointmentTypeStrings.MOBILE,
provider: providerToUse
});
const timeSlotToUse = {
date: emittedValue.date,
routeCode: emittedValue.timeSlot.id,
startTime: emittedValue.timeSlot.startTime,
endTime: emittedValue.timeSlot.endTime,
jobMaxMinutes: emittedValue.estimatedServiceMinutesMax.toString(),
jobMinMinutes: emittedValue.estimatedServiceMinutesMin.toString(),
};
this.mainStore.saveSchedule(timeSlotToUse);
showIssLoadingModal(true);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
async checkMobileFirstExperience(mobileFirstObject = {}) {
const showMobileFirst = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT);
if (showMobileFirst) {
const pmMobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS);
const mobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_MOBILE_DAYS);
const hasSelectedDate = !!this.getSelectedDate();
const isServicableMobile = mobileFirstObject.serviceabilityDetails?.isGlassServiceableMobile;
if (!hasSelectedDate && isServicableMobile && mobileFirstObject.mobileProviderNumber && mobileFirstObject.zipCode) {
const todayDateObject = new Date();
const todayDateString = convertDateToDateString(todayDateObject);
const calendarViewDirection = 'future';
const initialDays = 15;
const initialViewStartDate = todayDateString;
const initialEndDate = new Date();
initialEndDate.setDate(initialEndDate.getDate() + (initialDays - 1));
const initialViewEndDate = convertDateToDateString(initialEndDate);
const preSelectedDate = this.selectedDate;
const initialData = await getAvailableDates(
initialViewStartDate,
initialViewEndDate,
AppointmentTypeStrings.MOBILE,
mobileFirstObject.mobileProviderNumber,
mobileFirstObject.zipCode
).then((response) => ({
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: response,
preSelectedDate,
initialDaysLoaded: initialDays,
daysFromStart: null
}))
.catch(() => {
return {
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: { days: [] },
preSelectedDate,
initialDaysLoaded: initialDays,
daysFromStart: null
};
});
this.mobileDatesData = initialData;
const estimatedServiceMinutesMax = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMaximum || 120;
const estimatedServiceMinutesMin = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMinimum || 90;
const retTimeSlotsResponse = [];
initialData.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
const dateObjectToPush = {
date: selectableDate.date,
timeSlots: selectableDate.timeSlots,
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12;
}).map((timeSlot) => mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12;
}).map((timeSlot) => mapTimeSlot(timeSlot, 'afternoon')),
isSelected: false
};
if (dateObjectToPush.morningTimeSlots.length > 0) {
const findIndex = dateObjectToPush.morningTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
if (findIndex !== -1) {
const dropOffSlot = dateObjectToPush.morningTimeSlots.splice(findIndex, 1)[0];
dateObjectToPush.morningTimeSlots.unshift(dropOffSlot);
}
}
if (dateObjectToPush.afternoonTimeSlots.length > 0) {
const findIndex = dateObjectToPush.afternoonTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
if (findIndex !== -1) {
const dropOffSlot = dateObjectToPush.afternoonTimeSlots.splice(findIndex, 1)[0];
dateObjectToPush.afternoonTimeSlots.push(dropOffSlot);
}
}
retTimeSlotsResponse.push(dateObjectToPush);
});
let dateDifferenceForPmMobile = null;
const firstPmMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
if (hasPmMobileTimeSlots && pmMobileDaysLimit) {
dateDifferenceForPmMobile = calcDaysBetweenDates(todayDateString, date.date);
return dateDifferenceForPmMobile <= pmMobileDaysLimit;
}
return hasPmMobileTimeSlots;
});
let dateDifferenceForMobile = null;
const firstMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
const hasMobileTimeSlots = date.morningTimeSlots?.length > 0;
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
if (hasMobileTimeSlots || hasPmMobileTimeSlots) {
if (mobileDaysLimit) {
dateDifferenceForMobile = calcDaysBetweenDates(todayDateString, date.date);
return mobileDaysLimit && dateDifferenceForMobile <= mobileDaysLimit;
}
return true;
}
return hasPmMobileTimeSlots;
});
const zipCodeData = await getMobileZipCodeData(mobileFirstObject.zipCode);
if ((firstPmMobileDateWithTimeSlots || firstMobileDateWithTimeSlots) && zipCodeData?.isValid) {
zipCodeData.zipCode = mobileFirstObject.zipCode;
const timeSlotForAutoSelectionMobile = {
date: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.date : firstMobileDateWithTimeSlots.date,
estimatedServiceMinutesMax,
estimatedServiceMinutesMin,
isRepair: this.mainStore.order.damage.isRepair,
mobileProviderNumber: mobileFirstObject.mobileProviderNumber,
timeSlot: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.afternoonTimeSlots[0]
: (firstMobileDateWithTimeSlots.morningTimeSlots[0] || firstMobileDateWithTimeSlots.afternoonTimeSlots[0]),
zipCodeData
};
const dateObjectToUse = new Date(`${timeSlotForAutoSelectionMobile.date}T${timeSlotForAutoSelectionMobile.timeSlot.startTime}:00`);
timeSlotForAutoSelectionMobile.dateObject = dateObjectToUse;
timeSlotForAutoSelectionMobile.dayOfWeek = dateObjectToUse.toLocaleDateString('en-US', { weekday: 'long' });
const experiment = this.getExperimentByName(experimentUniverses.ISS_MOBILE_FIRST_APPOINTMENTS);
if (experiment && !experiment.isExposed) {
this.mainStore.logMobileFirstExperimentExposure();
}
this.$refs[SUGGEST_TIMESLOT_MODAL_REF_NAME].openModal(timeSlotForAutoSelectionMobile);
}
}
}
},
async setData(premiumFeeWithPriceResponse, initialServiceLocationObj) {
this.selectedServiceLocation = initialServiceLocationObj;
this.mobilePremiumAppointmentFee = premiumFeeWithPriceResponse
? premiumFeeWithPriceResponse[0]
: null;
if (initialServiceLocationObj?.zipCodeCtu) {
this.$refs.locationAlerts.loadInitialData(
initialServiceLocationObj.zipCodeCtu,
initialServiceLocationObj.provider?.ctu
).then((alertReasons) => {
this.$refs.locationAlerts.initializeComponent(alertReasons.data);
});
}
if (this.selectedAppointmentType !== AppointmentTypeStrings.MOBILE
&& this.selectedAppointmentType !== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
await this.getDatePickerInitialData(initialServiceLocationObj.provider?.providerNumber).then((initialData) => {
this.selectableDatesData = initialData.initialShopTimeSlotsResponse;
this.$refs.datePicker.setCalendarData(initialData);
});
} else {
this.mobileProviderNumber = initialServiceLocationObj.mobileProviderNumber;
this.selectedMobileZipCode = initialServiceLocationObj.zipCode;
await this.getDatePickerInitialData().then((initialData) => {
this.selectableDatesData = initialData.initialShopTimeSlotsResponse;
this.$refs.datePicker.setCalendarData(initialData);
});
}
},
appointmentTypeChangedFromServiceLocation(newAppointmentType) {
this.selectedAppointmentType = newAppointmentType.appointmentType;
if (newAppointmentType.appointmentType === AppointmentTypeStrings.IN_SHOP) {
if (this.inShopDatesData?.initialShopTimeSlotsResponse?.days?.length > 0) {
this.selectableDatesData = this.inShopDatesData.initialShopTimeSlotsResponse;
this.$refs.datePicker.initializeComponent(this.inShopDatesData);
}
} else if (this.mobileDatesData?.initialShopTimeSlotsResponse?.days?.length > 0) {
this.selectableDatesData = this.mobileDatesData.initialShopTimeSlotsResponse;
this.$refs.datePicker.initializeComponent(this.mobileDatesData);
}
},
cityUpdatedFromServiceLocation(newCity) {
this.selectedServiceLocationCity = newCity;
},
clearMobileDataFromServiceLocation() {
if (this.selectedServiceLocation) {
this.selectedServiceLocation.mobileProviderNumber = null;
}
this.mobileDatesData = [];
this.mobileProviderNumber = null;
this.selectedMobileZipCode = null;
},
dateSelectedFromPicker(date) {
this.selectedDate = date;
this.showDatePickerError = false;
},
getAppointmentType() {
return this.mainStore.order?.serviceLocation?.appointmentType;
},
async getAvailableDatesMethod(startDate, endDate) {
let mobileProviderNumberToUse = this.mobileProviderNumber;
if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
mobileProviderNumberToUse = this.selectedServiceLocation.mobileProviderNumber;
}
const newShopTimeSlots = await getAvailableDates(
startDate,
endDate,
this.selectedAppointmentType,
this.selectedProvider.providerNumber || mobileProviderNumberToUse,
this.selectedMobileZipCode
);
// ADD API CALL RESULTS TO EXISTING DATE DATA
this.selectableDatesData.days =
this.selectableDatesData.days.concat(newShopTimeSlots.days);
return newShopTimeSlots;
},
getAvailableDates,
async getDatePickerInitialData(defaultProviderNumber = null) {
let todayDateString;
const todayDateObject = new Date();
const calendarViewDirection = 'future';
const initialDays = 15;
if (this.todayString) {
todayDateString = this.todayString;
} else {
todayDateString = convertDateToDateString(todayDateObject);
}
const initialViewStartDate = todayDateString;
const initialEndDate = new Date();
initialEndDate.setDate(initialEndDate.getDate() + (initialDays - 1));
let initialViewEndDate = convertDateToDateString(initialEndDate);
const preSelectedDate = this.selectedDate;
const endDateObject = this.getEndDateForExistingDate(
todayDateObject,
preSelectedDate,
initialDays
);
if (endDateObject.endDateString !== initialViewEndDate) {
initialViewEndDate = endDateObject.endDateString;
}
let mobileProviderNumberToUse = defaultProviderNumber;
if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
mobileProviderNumberToUse = this.selectedServiceLocation.mobileProviderNumber;
}
const initialData = await getAvailableDates(
initialViewStartDate,
initialViewEndDate,
this.selectedAppointmentType || AppointmentTypeStrings.IN_SHOP,
this.selectedProvider?.providerNumber || mobileProviderNumberToUse,
this.selectedServiceLocation.zipCode
).then((response) => ({
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: response,
preSelectedDate,
initialDaysLoaded: endDateObject.newDaysLoaded || initialDays,
daysFromStart: endDateObject.daysFromStart || null
}))
.catch(() => {
return {
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: { days: [] },
preSelectedDate,
initialDaysLoaded: endDateObject.newDaysLoaded || initialDays,
daysFromStart: endDateObject.daysFromStart || null
};
});
if (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
this.mobileDatesData = initialData;
if (this.selectedProvider?.providerNumber) {
const inShopData = await getAvailableDates(
initialViewStartDate,
initialViewEndDate,
AppointmentTypeStrings.IN_SHOP,
this.selectedProvider?.providerNumber || defaultProviderNumber,
this.selectedServiceLocation.zipCode
).then((response) => ({
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: response,
preSelectedDate,
initialDaysLoaded: endDateObject.newDaysLoaded || initialDays,
daysFromStart: endDateObject.daysFromStart || null
}));
this.inShopDatesData = inShopData;
}
} else {
this.inShopDatesData = initialData;
this.mobileDatesData = [];
}
return initialData;
},
getEndDateForExistingDate(startDate, selectedDateString, daysInView) {
const endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + (daysInView - 1));
if (selectedDateString) {
const selectedDate = new Date(`${selectedDateString}T00:00:00`);
const daysDifferenceComparedToStartDate = Math.ceil((selectedDate - startDate) / (1000 * 60 * 60 * 24));
if (selectedDate > endDate) {
const daysDifferenceComparedToStartDateAsPages = Math.ceil(daysDifferenceComparedToStartDate / daysInView);
const newEndDate = new Date(startDate);
newEndDate.setDate(newEndDate.getDate() + (daysDifferenceComparedToStartDateAsPages * daysInView) - 1);
return {
endDateString: convertDateToDateString(newEndDate),
newDaysLoaded: (daysDifferenceComparedToStartDateAsPages * daysInView),
daysFromStart: daysDifferenceComparedToStartDate
};
}
return {
endDateString: convertDateToDateString(endDate),
newDaysLoaded: null,
daysFromStart: daysDifferenceComparedToStartDate
};
}
return {
endDateString: convertDateToDateString(endDate),
newDaysLoaded: null,
daysFromStart: null
};
},
getServiceZipCtuCodeFromStore() {
return this.mainStore.order.serviceLocation.zipCodeCtu;
},
getSelectedDate() {
return this.mainStore.order.schedule.date;
},
getSelectedProvider() {
return this.mainStore.order.serviceLocation.provider;
},
getSelectedTimeSlotInfo() {
const isPremiumAppointment =
!!(
this.supportingItems?.filter((lineItem) =>
lineItem.partType === PREMIUM_FEE_PART_TYPE) ?? []
).length > 0;
const selectedTimeSlotInfo = {
timeSlot: this.mainStore.order.schedule,
isPremiumAppointment
};
return selectedTimeSlotInfo;
},
getServiceLocation() {
return this.mainStore.order.serviceLocation;
},
getServiceLocationCity() {
return this.mainStore.order.serviceLocation?.city;
},
handleMqlChange(e) {
this.isMobileView = !e.matches;
},
inShopZipUpdatedFromServiceLocation(inShopZipServiceLocation) {
if (inShopZipServiceLocation) {
this.mobileProviderNumber = null;
this.selectedMobileZipCode = null;
this.selectedServiceLocation = inShopZipServiceLocation;
}
},
mobileZipUpdatedFromServiceLocation(mobileZipServiceLocation) {
if (mobileZipServiceLocation) {
this.mobileProviderNumber = mobileZipServiceLocation.mobileProviderNumber;
this.selectedMobileZipCode = mobileZipServiceLocation.zipCode;
this.selectedServiceLocation = mobileZipServiceLocation;
if (mobileZipServiceLocation.refreshDatePicker) {
this.refreshDatePicker();
}
}
},
async providerChangedFromServiceLocation(newProvider) {
this.selectedProvider = newProvider?.provider;
if (newProvider?.refreshDatePicker && newProvider.provider) {
this.mainStore.updateServiceLocationProvider(newProvider.provider);
showIssLoadingModal(true);
await this.mainStore.getBillToInfo(newProvider.provider?.providerNumber)
.then(() => {
this.refreshDatePicker();
})
.catch(() => {
console.warn('Error fetching bill to info...');
showIssLoadingModal(false);
});
}
},
async refreshDatePicker() {
this.resetLocalFlags();
this.isDatePickerRefreshing = true;
await this.getDatePickerInitialData().then((data) => {
this.selectableDatesData = data.initialShopTimeSlotsResponse;
this.$refs.datePicker.initializeComponent(data);
this.isDatePickerRefreshing = false;
showIssLoadingModal(false);
});
},
resetLocalFlags() {
this.selectedDate = null;
this.selectedTimeSlotInfo = null;
this.showDatePickerError = false;
},
timeSlotSelectedFromPicker(timeSlot) {
this.selectedTimeSlotInfo = timeSlot;
this.showDatePickerError = false;
},
async forwardButtonAction() {
if (!this.isFormValid) {
this.showDatePickerError = true;
return;
}
let appointmentTypeToUse = this.selectedAppointmentType;
if (appointmentTypeToUse === AppointmentTypeStrings.IN_SHOP && this.selectedTimeSlotInfo.timeSlot?.isDropoff === true) {
appointmentTypeToUse = AppointmentTypeStrings.DROP_OFF;
}
await this.$refs.serviceLocation.forwardButtonAction(appointmentTypeToUse);
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
}
}
};
</script>
<style lang="scss" scoped>
$page-side-padding: 1.5rem;
.iss-heritage-container-width {
padding-right: 0.9375rem;
padding-left: 0.9375rem;
.schedule-page-container {
position: relative;
min-height: 1px;
}
}
:deep(.text-link-small) {
a,
.btn-link {
font-size: 0.875rem;
line-height: 1.5;
}
}
:deep(.change-shop-link) {
a {
font-weight: 500;
}
}
:deep(.subheader-primary) {
h5.dark-header {
margin-bottom: 0.25rem !important;
}
}
</style>