drop off updates for schedule page

Morning Drop off.
This commit is contained in:
Bill Richardson 2026-02-12 15:53:06 -05:00
parent 63e2f36c19
commit ff6dca7650
4 changed files with 182 additions and 75 deletions

View file

@ -99,9 +99,9 @@
</div>
<template
v-for="timeSlot in selectableTimeSlotsData"
:key="timeSlot.startTime">
:key="`${timeSlot.startTime}-${timeSlot.isDropoff}`">
<input
:id="`timeslot${timeSlot.startTime}`"
:id="`timeslot${timeSlot.startTime}-${timeSlot.isDropoff}`"
v-model="selectedTime"
type="radio"
name="time-slot"
@ -114,9 +114,14 @@
class="time-slot-button"
:class="[checkIsSelectedTimeSlot(timeSlot) ? 'selected-time-slot' : ''
, {'has-date-error': showTimeSlotError}]"
@click="selectTimeSlotForDay(timeSlot)">
{{ displayTimeSlotTime(timeSlot) }}
@click="selectTimeSlotForDay(timeSlot)"
v-html="displayTimeSlotTime(timeSlot)">
</label>
<div
v-if="timeSlot.isDropoff"
class="time-slot-drop-off-information"
v-html="displayDropOffInformation(timeSlot)">
</div>
</template>
</div>
<div class="row form-test-error">
@ -256,6 +261,10 @@ export default {
appointmentEstimate() {
return getDisplayTextForDurationLength(this.appointmentDurationMinutesMinimum, this.appointmentDurationMinutesMaximum);
},
calendarViewDirection() {
if (this.selectableDatesSetting === 'custom') return 'future';
return 'past';
},
daysToAdd() {
return this.isMobileView ? this.daysInViewMobile : this.daysInViewStandard;
},
@ -284,10 +293,6 @@ export default {
this.todayOverrideDateString
|| convertDateToDateString(new Date())
);
},
calendarViewDirection() {
if (this.selectableDatesSetting === 'custom') return 'future';
return 'past';
}
},
watch: {
@ -301,16 +306,16 @@ export default {
this.findFirstAvailableDateInView();
}
},
selectedDate(newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('dateSelected', newValue);
}
},
selectedTimeSlot(newValue, oldValue) {
if (newValue !== oldValue) {
const testObj = this.getSelectedTimeSlotInfoObject(newValue);
this.$emit('timeSlotSelected', testObj);
}
},
selectedDate(newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('dateSelected', newValue);
}
}
},
methods: {
@ -340,50 +345,44 @@ export default {
addPremiumFlagToInput(routeCode) {
return (`${routeCode}${PREMIUM_TIME_SLOT_ID_FLAG}`);
},
removePremiumFlagFromInput(routeCode) {
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, '');
checkIsSelectedDay(index, timeOfDayGrouping) {
const dateToShowString = this.getDateToShowString(index);
return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping;
},
getSelectedTimeSlotInfoObject(timeSlot) {
const routeCode = timeSlot?.id;
let routeCodeToUse = routeCode;
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
if (routeCodeIncludesPremium) {
routeCodeToUse = this.removePremiumFlagFromInput(routeCode);
checkIsSelectedTimeSlot(timeSlot) {
if (!this.selectedTimeSlot) {
return false;
}
const fullTimeSlot = this.selectableTimeSlotsData?.find((ts) => ts.id === routeCodeToUse);
if (fullTimeSlot) {
return {
timeSlot: {
date: this.selectedDate,
routeCode: fullTimeSlot.id,
startTime: fullTimeSlot.startTime,
endTime: fullTimeSlot.endTime,
jobMaxMinutes: this.appointmentDurationMinutesMaximum.toString(),
jobMinMinutes: this.appointmentDurationMinutesMinimum.toString()
},
isPremiumAppointment: !!routeCodeIncludesPremium
};
return (this.selectedTimeSlot.isDropoff === timeSlot.isDropoff
&& this.selectedTimeSlot.startTime === timeSlot.startTime
&& this.selectedTimeSlot.timeOfDay === timeSlot.timeOfDay);
},
displayDropOffInformation(timeSlot) {
if (timeSlot.isDropoff) {
if (timeSlot.timeOfDay === 'morning') {
return this.getCmsContent(
'DropOffTimeSlotModal',
'BodyText'
);
}
return this.getCmsContent(
'OvernightDropOffTimeSlotModal',
'BodyText'
);
}
return {
timeSlot: {
date: null,
startTime: null,
endTime: null,
routeCode: null,
jobMaxMinutes: null,
jobMinMinutes: null
},
isPremiumAppointment: null
};
return '';
},
displayTimeSlotTime(timeSlot) {
const appointmentType = this.activeAppointmentType || AppointmentTypeStrings.IN_SHOP;
if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
return `${militaryToTwelveHourTime(timeSlot.startTime)} - ${militaryToTwelveHourTime(timeSlot.endTime)}`;
}
if (timeSlot.isDropoff) {
if (timeSlot.timeOfDay === 'morning') {
return '<span class="dropoff-label">Drop & Go<sup>&trade;</sup></span>';
}
return '<span class="dropoff-label">Overnight drop off</span>';
}
return militaryToTwelveHourTime(timeSlot.startTime);
},
findFirstAvailableDateInView() {
@ -436,8 +435,7 @@ export default {
if (!initialTimeSlot || !initialTimeSlot.startTime) {
return;
}
const initialStartTime = initialTimeSlot.startTime;
const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialStartTime);
const foundTimeSlot = timeSlots.find((ts) => ts.startTime === initialTimeSlot.startTime && ts.endTime === initialTimeSlot.endTime);
if (foundTimeSlot) {
this.selectTimeSlotForDay(foundTimeSlot);
}
@ -452,6 +450,44 @@ export default {
const dateToShow = this.getDateToShow(index);
return convertDateToDateString(dateToShow);
},
getSelectedTimeSlotInfoObject(timeSlot) {
const routeCode = timeSlot?.id;
let routeCodeToUse = routeCode;
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
if (routeCodeIncludesPremium) {
routeCodeToUse = this.removePremiumFlagFromInput(routeCode);
}
const fullTimeSlot = this.selectableTimeSlotsData?.find((ts) => ts.id === routeCodeToUse);
if (fullTimeSlot) {
return {
timeSlot: {
date: this.selectedDate,
routeCode: fullTimeSlot.id,
startTime: fullTimeSlot.startTime,
endTime: fullTimeSlot.endTime,
isDropoff: fullTimeSlot.isDropoff,
jobMaxMinutes: this.appointmentDurationMinutesMaximum.toString(),
jobMinMinutes: this.appointmentDurationMinutesMinimum.toString()
},
isPremiumAppointment: !!routeCodeIncludesPremium
};
}
return {
timeSlot: {
date: null,
routeCode: null,
startTime: null,
endTime: null,
isDropoff: null,
jobMaxMinutes: null,
jobMinMinutes: null
},
isPremiumAppointment: null
};
},
async gotoNextPage() {
const maxPageIndex = Math.floor((this.daysLoaded - 1) / this.daysToAdd);
if (this.activePageIndex >= maxPageIndex) {
@ -479,15 +515,15 @@ export default {
this.selectedTimeSlot = null;
this.findFirstAvailableDateInView();
},
checkIsSelectedDay(index, timeOfDayGrouping) {
const dateToShowString = this.getDateToShowString(index);
return this.selectedDate === dateToShowString && this.selectedTimeOfDayGrouping === timeOfDayGrouping;
mapTimeSlot(timeSlot, timeOfDay) {
return {
...timeSlot,
isDropoff: timeSlot.id.endsWith('DROP OFF'),
timeOfDay
};
},
checkIsSelectedTimeSlot(timeSlot) {
if (!this.selectedTimeSlot) {
return false;
}
return this.selectedTimeSlot.startTime === timeSlot.startTime;
removePremiumFlagFromInput(routeCode) {
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, '');
},
selectTimeSlotForDay(timeSlot) {
if (timeSlot) {
@ -567,13 +603,28 @@ export default {
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12;
}),
}).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12;
}),
}).map((timeSlot) => this.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);
}
}
this.selectableDatesData.push(dateObjectToPush);
});
@ -635,14 +686,28 @@ export default {
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12;
}),
}).map((timeSlot) => this.mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12;
}),
}).map((timeSlot) => this.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);
}
}
this.selectableDatesData.push(dateObjectToPush);
}
});
@ -832,16 +897,55 @@ export default {
text-align: center;
cursor: pointer;
:deep(.dropoff-label) {
sup {
top: -0.375rem;
font-size: 0.75rem;
}
}
& + .time-slot-drop-off-information {
max-height: 0;
overflow: hidden;
transition: max-height 0.75s ease-in-out;
font-weight: 400;
text-align: left;
color: $darker-gray;
:deep(ul) {
margin: 0;
margin-bottom: 0.375rem;
> li {
padding-left: 0.625rem;
margin-top: 0.625rem;
}
}
}
&.selected-time-slot {
background-color: $background-color-selected;
border: solid 1px #0070d1;
color:#000;
font-weight: 500;
:deep(.dropoff-label) {
sup {
top: .25rem;
font-size: 1.625rem;
}
}
& + .time-slot-drop-off-information {
max-height: 12rem;
transition: max-height 0.75s ease-in-out;
}
}
&.has-date-error {
border: 1px solid #d93025;
}
}
}

View file

@ -6,7 +6,6 @@
<div class="fade-on-route-transition">
<div class="justify-content-center">
<siteHeader
class="header"
cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
@ -176,9 +175,7 @@ const getAvailableDates = async (
storeAction.payload.endDate,
storeAction.payload.shopAppointmentType,
storeAction.payload.providerNumber
).catch(() => {
console.warn('Error fetching shop time slots...');
});
);
} else if (storeAction.payload?.zipCodeOverride) {
timeSlotsResponse = await useMainStore().getMobileTimeSlots(
storeAction.payload.startDate,
@ -343,7 +340,7 @@ export default {
&& (((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
&& serviceLocationToUse.mobileProviderNumber)
|| (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP
|| ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP || this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF)
&& (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber)));
const damageInfo = useMainStore().order.damage.isRepair
@ -652,8 +649,13 @@ export default {
return;
}
// Save service location then schedule and navigate forward
await this.$refs.serviceLocation.forwardButtonAction();
let appointmentTypeToUse = this.selectedAppointmentType;
if (appointmentTypeToUse === AppointmentTypeStrings.IN_SHOP && this.selectedTimeSlotInfo.timeSlot?.isDropoff === true) {
appointmentTypeToUse = AppointmentTypeStrings.DROP_OFF;
}
// Save service location then save schedule then navigate forward
await this.$refs.serviceLocation.forwardButtonAction(appointmentTypeToUse);
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,

View file

@ -308,7 +308,7 @@ export default {
}
},
selectedAppointmentType(newValue, oldValue) {
if (this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP && this.selectedProvider && this.availabilityRating === null) {
if (this.isInshop && this.selectedProvider && this.availabilityRating === null) {
this.refreshAvailabilityRating();
const appointmenTypeServiceLocationObj = {
@ -336,7 +336,7 @@ export default {
},
selectedProvider(newProvider, oldProvider) {
if (newProvider?.providerNumber !== oldProvider?.providerNumber || this.availabilityRating === null) {
const appointmentIsInshop = this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP;
const appointmentIsInshop = this.isInshop;
const returnedProvider = {
provider: newProvider,
refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null
@ -358,7 +358,7 @@ export default {
await this.setData(initialData);
this.initializingData = false;
},
async forwardButtonAction() {
async forwardButtonAction(appointmentType) {
let provider = this.selectedProvider;
this.mainStore.updateMobileFee(null);
if (this.isMobile) {
@ -382,7 +382,7 @@ export default {
state: this.state,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
appointmentType: appointmentType || this.selectedAppointmentType,
provider
});
},

View file

@ -965,7 +965,8 @@ export const useMainStore = defineStore({
providerNumber,
startDate,
endDate,
shopAppointmentType,
shopAppointmentType: (shopAppointmentType === AppointmentTypeStrings.IN_SHOP
|| shopAppointmentType === AppointmentTypeStrings.DROP_OFF) ? 'InShopOrDropoff' : shopAppointmentType,
applicationName: applicationConfig.APPLICATION_NAME,
billToAccountNumber: this.billToAccountNumber,
parentAccountNumber: this.order.parentAccountNumber, // this.payment.parentAccountNumber,
@ -1605,7 +1606,7 @@ export const useMainStore = defineStore({
order.customer.firstName = data?.customer?.firstName;
order.customer.lastName = data?.customer?.lastName;
order.customer.emailAddress = data?.customer?.emailAddress;
order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone;