Merge branch 'release/2026.03.12' into rlsmerge/2026.03.12ToDevelop

This commit is contained in:
CarlNation 2026-03-10 07:15:53 -04:00
commit b91fc6a76e
6 changed files with 407 additions and 219 deletions

View file

@ -153,6 +153,8 @@ import {
convertDateToDateString, convertDateToDateString,
convertDateStringToDate, convertDateStringToDate,
getTodayDateString, getTodayDateString,
getInitialViewWeeks,
getMonthEnd,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { useField, ErrorMessage } from "vee-validate"; import { useField, ErrorMessage } from "vee-validate";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
@ -291,140 +293,6 @@ export default {
} }
this.$emit("date-selected", date); this.$emit("date-selected", date);
}, },
getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
// Subtract the day of the week from date to get the date of Sunday
const sunday = new Date(date);
sunday.setDate(sunday.getDate() - dayOfWeek);
return convertDateToDateString(sunday);
},
getWeekEndDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
const daysUntilSaturday = 6 - dayOfWeek; // Calculate the number of days until Saturday
// Clone the given date and add the remaining days until Saturday
const saturday = new Date(date);
saturday.setDate(date.getDate() + daysUntilSaturday);
return convertDateToDateString(saturday);
},
getNextWeekSunday(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
const daysUntilNextSunday = 7 - dayOfWeek; // Calculate the number of days until the next Sunday
// Clone the given date and add the remaining days until Sunday
const nextSunday = new Date(date);
nextSunday.setDate(date.getDate() + daysUntilNextSunday);
return convertDateToDateString(nextSunday);
},
getMonthEnd(dateStr) {
// convert string to date, do date calc, then return a string back
const date = new Date(dateStr.split("-")[0], parseInt(dateStr.split("-")[1]), 0);
return convertDateToDateString(date);
},
getInitialViewWeeks(todayString, initialViewRowsToShow, preSelectedDate) {
// TODO: this only is for future direction; need to create logic for past direction
const weeks = [];
let weekStartDate = this.getWeekStartDate(todayString);
let weekEndDate = this.getWeekEndDate(todayString);
if (preSelectedDate) {
let preSelectedDateMonthEnd = this.getMonthEnd(
preSelectedDate.replace("-mobile", "")
);
let monthEndsByThisWeek = false;
let i = 0;
while (!monthEndsByThisWeek && i < 52) {
if (i > 0) {
weekStartDate = this.getNextWeekSunday(weekEndDate);
weekEndDate = this.getWeekEndDate(weekStartDate);
if (
(preSelectedDateMonthEnd > weekStartDate &&
preSelectedDateMonthEnd < weekEndDate) ||
preSelectedDateMonthEnd === weekStartDate ||
preSelectedDateMonthEnd === weekEndDate
) {
weekEndDate = preSelectedDateMonthEnd;
monthEndsByThisWeek = true;
} else if (preSelectedDateMonthEnd < weekEndDate) {
// Additional case: if month ends before this week (but not necessarily during it),
// still cut the loop here, but don't truncate the week.
monthEndsByThisWeek = true;
}
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
i++;
}
} else {
for (let i = 0; i < initialViewRowsToShow; i++) {
if (i > 0) {
weekStartDate = this.getNextWeekSunday(weekEndDate);
weekEndDate = this.getWeekEndDate(weekStartDate);
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
}
// If any of these weeks is split between two months, then make them 2 separate "weeks"
// (a week split between two months is considered 2 weeks per business requirements)
const hasSplitWeek = (week) => {
return week.weekStartDate.split("-")[1] !== week.weekEndDate.split("-")[1]
? true
: false;
};
const splitWeekIndex = weeks.findIndex(hasSplitWeek);
if (splitWeekIndex > -1) {
const week1 = [];
const week2 = [];
let switchToWeek2 = false;
for (let j = 0; j < 7; j++) {
const newDate = convertDateStringToDate(
weeks[splitWeekIndex].weekStartDate
);
newDate.setDate(newDate.getDate() + j);
if (newDate.getDate() === 1) switchToWeek2 = true;
if (switchToWeek2) {
week2.push(convertDateToDateString(newDate));
} else {
week1.push(convertDateToDateString(newDate));
}
}
const week1EndDate = week1[week1.length - 1];
const week2StartDate = week2[0];
if (week1EndDate < todayString) {
// replace week 1 with week 2
weeks[splitWeekIndex].weekStartDate = week2StartDate;
} else {
const newWeek = {
weekNum: weeks[splitWeekIndex].weekNum,
weekStartDate: week2StartDate,
weekEndDate: weeks[splitWeekIndex].weekEndDate,
};
weeks[splitWeekIndex].weekEndDate = week1EndDate;
weeks.splice(splitWeekIndex + 1, 0, newWeek);
weeks.pop();
weeks.forEach((item, index) => {
if (index > splitWeekIndex) {
item.weekNum = item.weekNum + 1;
}
});
}
}
}
return weeks;
},
async loadInitialData(config) { async loadInitialData(config) {
/* /*
** NOTE: this _could_ be called by a parent before fully loaded, so FYI component data or computeds might not be available ** NOTE: this _could_ be called by a parent before fully loaded, so FYI component data or computeds might not be available
@ -444,10 +312,10 @@ export default {
if (config.selectableDatesSetting === "past") calendarViewDirection = "past"; if (config.selectableDatesSetting === "past") calendarViewDirection = "past";
if (config.selectableDatesSetting === "custom") calendarViewDirection = "future"; if (config.selectableDatesSetting === "custom") calendarViewDirection = "future";
const currentMonthEnd = this.getMonthEnd(todayDateString); const currentMonthEnd = getMonthEnd(todayDateString);
// TODO - set up currentMonthStart if direction is PAST: // TODO - set up currentMonthStart if direction is PAST:
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1); // let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
const initialViewWeeks = this.getInitialViewWeeks( const initialViewWeeks = getInitialViewWeeks(
todayDateString, todayDateString,
config.initialViewRowsToShow, config.initialViewRowsToShow,
config.preSelectedDate config.preSelectedDate

View file

@ -79,3 +79,137 @@ export function getTodayDate() {
export function getTodayDateString() { export function getTodayDateString() {
return convertDateToDateString(getTodayDate()); return convertDateToDateString(getTodayDate());
} }
export function getMonthEnd(dateStr) {
// convert string to date, do date calc, then return a string back
const date = new Date(dateStr.split("-")[0], parseInt(dateStr.split("-")[1]), 0);
return convertDateToDateString(date);
}
export function getWeekStartDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
// Subtract the day of the week from date to get the date of Sunday
const sunday = new Date(date);
sunday.setDate(sunday.getDate() - dayOfWeek);
return convertDateToDateString(sunday);
}
export function getWeekEndDate(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
const daysUntilSaturday = 6 - dayOfWeek; // Calculate the number of days until Saturday
// Clone the given date and add the remaining days until Saturday
const saturday = new Date(date);
saturday.setDate(date.getDate() + daysUntilSaturday);
return convertDateToDateString(saturday);
}
export function getNextWeekSunday(dateString) {
const date = convertDateStringToDate(dateString);
const dayOfWeek = date.getDay();
const daysUntilNextSunday = 7 - dayOfWeek; // Calculate the number of days until the next Sunday
// Clone the given date and add the remaining days until Sunday
const nextSunday = new Date(date);
nextSunday.setDate(date.getDate() + daysUntilNextSunday);
return convertDateToDateString(nextSunday);
}
export function getInitialViewWeeks(todayString, initialViewRowsToShow, preSelectedDate) {
const weeks = [];
let weekStartDate = getWeekStartDate(todayString);
let weekEndDate = getWeekEndDate(todayString);
if (preSelectedDate) {
let preSelectedDateMonthEnd = getMonthEnd(preSelectedDate.replace("-mobile", ""));
let monthEndsByThisWeek = false;
let i = 0;
while (!monthEndsByThisWeek && i < 52) {
if (i > 0) {
weekStartDate = getNextWeekSunday(weekEndDate);
weekEndDate = getWeekEndDate(weekStartDate);
if (
(preSelectedDateMonthEnd > weekStartDate &&
preSelectedDateMonthEnd < weekEndDate) ||
preSelectedDateMonthEnd === weekStartDate ||
preSelectedDateMonthEnd === weekEndDate
) {
weekEndDate = preSelectedDateMonthEnd;
monthEndsByThisWeek = true;
} else if (preSelectedDateMonthEnd < weekEndDate) {
// Additional case: if month ends before this week (but not necessarily during it),
// still cut the loop here, but don't truncate the week.
monthEndsByThisWeek = true;
}
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
i++;
}
} else {
for (let i = 0; i < initialViewRowsToShow; i++) {
if (i > 0) {
weekStartDate = getNextWeekSunday(weekEndDate);
weekEndDate = getWeekEndDate(weekStartDate);
}
weeks.push({
weekNum: i + 1,
weekStartDate: weekStartDate,
weekEndDate: weekEndDate,
});
}
// If any of these weeks is split between two months, then make them 2 separate "weeks"
// (a week split between two months is considered 2 weeks per business requirements)
const hasSplitWeek = (week) => {
return week.weekStartDate.split("-")[1] !== week.weekEndDate.split("-")[1]
? true
: false;
};
const splitWeekIndex = weeks.findIndex(hasSplitWeek);
if (splitWeekIndex > -1) {
const week1 = [];
const week2 = [];
let switchToWeek2 = false;
for (let j = 0; j < 7; j++) {
const newDate = convertDateStringToDate(weeks[splitWeekIndex].weekStartDate);
newDate.setDate(newDate.getDate() + j);
if (newDate.getDate() === 1) switchToWeek2 = true;
if (switchToWeek2) {
week2.push(convertDateToDateString(newDate));
} else {
week1.push(convertDateToDateString(newDate));
}
}
const week1EndDate = week1[week1.length - 1];
const week2StartDate = week2[0];
if (week1EndDate < todayString) {
// replace week 1 with week 2
weeks[splitWeekIndex].weekStartDate = week2StartDate;
} else {
const newWeek = {
weekNum: weeks[splitWeekIndex].weekNum,
weekStartDate: week2StartDate,
weekEndDate: weeks[splitWeekIndex].weekEndDate,
};
weeks[splitWeekIndex].weekEndDate = week1EndDate;
weeks.splice(splitWeekIndex + 1, 0, newWeek);
weeks.pop();
weeks.forEach((item, index) => {
if (index > splitWeekIndex) {
item.weekNum = item.weekNum + 1;
}
});
}
}
}
return weeks;
}

View file

@ -103,6 +103,7 @@ export default {
timeSlot: this.selectedAppointment.timeSlot, timeSlot: this.selectedAppointment.timeSlot,
routeCode: this.selectedAppointment.timeSlot.id, routeCode: this.selectedAppointment.timeSlot.id,
date: this.selectedAppointment.date, date: this.selectedAppointment.date,
provider: this.selectedAppointment.provider,
}; };
this.confirmedAppointment = true; this.confirmedAppointment = true;
this.$emit("confirm-appointment", { this.$emit("confirm-appointment", {
@ -152,7 +153,7 @@ export default {
this.GaCategories.INSHOP_CONFIRMATION_CLICKED, this.GaCategories.INSHOP_CONFIRMATION_CLICKED,
gaAction, gaAction,
this.inshopAppointmentOption.timeSlot.startTime + this.inshopAppointmentOption.timeSlot.startTime +
"-" + " " +
this.inshopAppointmentOption.date, this.inshopAppointmentOption.date,
true, true,
null, null,
@ -300,6 +301,7 @@ export default {
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
padding: 0 1rem; padding: 0 1rem;
margin-top: 4rem;
& > span { & > span {
color: $red; color: $red;

View file

@ -265,6 +265,7 @@ export default {
.location-info-label { .location-info-label {
display: flex; display: flex;
align-items: center; align-items: center;
line-height: 1.625rem;
&:before { &:before {
content: ""; content: "";

View file

@ -252,6 +252,8 @@ import {
sumDateString, sumDateString,
isDropOffRouteCode, isDropOffRouteCode,
getTodayDate, getTodayDate,
getTodayDateString,
getInitialViewWeeks,
} from "@/layouts/schedule/helpers/schedule-helper"; } from "@/layouts/schedule/helpers/schedule-helper";
import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper"; import { containsRecalParts, anyPartWithRequiresRecalFlag } from "@/helpers/recal-helper";
@ -485,6 +487,7 @@ export default {
selectedMobileFirstAppointment: false, selectedMobileFirstAppointment: false,
selectedMultiLocationAppointment: false, selectedMultiLocationAppointment: false,
calendarLoadingStatus: "none", calendarLoadingStatus: "none",
isLoadingMultiLocationPopup: true,
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -615,9 +618,9 @@ export default {
pricedMobileFeePart, pricedMobileFeePart,
shopProviderData.data shopProviderData.data
); );
vm.initializeDatePicker().then(() => { vm.initializeDatePicker().then(async () => {
// vm.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST // vm.showMobileFirstModal(); // KEEP IN CASE WE WANT TO REINSTATE MOBILE FIRST
vm.showMultiLocationModal(); await vm.showMultiLocationModal();
vm.hideLoadingModal(); vm.hideLoadingModal();
vm.calendarLoadingStatus = "none"; vm.calendarLoadingStatus = "none";
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable(); if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
@ -809,13 +812,6 @@ export default {
}; };
}, },
selectedShopAnswer() { selectedShopAnswer() {
const toTitleCase = (str) => {
if (!str) return "";
return str.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
if ( if (
this.selectedProvider && this.selectedProvider &&
this.selectedProvider.address && this.selectedProvider.address &&
@ -824,18 +820,11 @@ export default {
const provider = this.shopProviderData?.shopProviders?.find( const provider = this.shopProviderData?.shopProviders?.find(
(p) => p.providerNumber === this.selectedProvider?.providerNumber (p) => p.providerNumber === this.selectedProvider?.providerNumber
); );
const streetAddress = toTitleCase(this.selectedProvider.address.streetAddress); const distanceInMiles = provider?.distanceInMiles
const city = toTitleCase(this.selectedProvider.address.city); ? Math.round(provider.distanceInMiles * 2) / 2
const state = this.selectedProvider.address.state; : 0;
const zipCode = this.selectedProvider.address.zipCode;
const distanceInMiles = provider ? Math.round(provider.distanceInMiles * 2) / 2 : 0;
return { return this.getFormattedShopAddress(provider?.address, distanceInMiles);
city: `${city}`,
distance: `${distanceInMiles} mi`,
address1: `${streetAddress}`,
address2: `${city}, ${state} ${zipCode}`,
};
} }
return []; return [];
}, },
@ -907,6 +896,10 @@ export default {
: false; : false;
}, },
isMultiLocationModalOpen() { isMultiLocationModalOpen() {
// Only return false if we're done loading the popup
if (this.isLoadingMultiLocationPopup) {
return true; // Still loading, keep background hidden
}
return this.selectableDatesMobile.days && this.selectableDatesInshop.days return this.selectableDatesMobile.days && this.selectableDatesInshop.days
? this.$refs.multiLocationModal?.getIsModalOpen() ? this.$refs.multiLocationModal?.getIsModalOpen()
: false; : false;
@ -977,6 +970,17 @@ export default {
"true" "true"
); );
}, },
initialViewStartDate() {
return getTodayDateString();
},
initialViewEndDate() {
const initialViewWeeks = getInitialViewWeeks(
this.initialViewStartDate,
NUMBER_OF_CALENDAR_ROWS_TO_SHOW_FOR_INITIAL_VIEW,
this.preSelectedDate
);
return initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
},
}, },
methods: { methods: {
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
@ -1927,8 +1931,8 @@ export default {
let promotedMobileAppointment = null; let promotedMobileAppointment = null;
const todaysDate = getTodayDate(); const todaysDate = getTodayDate();
let firstMobileAMAppt = await this.getFirstAvailableApptByTOD("AM", "mobile"); let firstMobileAMAppt = await this.getFirstAvailableMobileApptByTOD("AM");
let firstMobilePMAppt = await this.getFirstAvailableApptByTOD("PM", "mobile"); let firstMobilePMAppt = await this.getFirstAvailableMobileApptByTOD("PM");
if (!firstMobileAMAppt && !firstMobilePMAppt) { if (!firstMobileAMAppt && !firstMobilePMAppt) {
return; return;
} }
@ -2054,7 +2058,14 @@ export default {
} }
}, },
async showMultiLocationModal() { async showMultiLocationModal() {
if (!this.selectedRouteCodeData?.routeCode) { const showMultiLocationAppointment = experimentMixin.methods.getSettingValue(
experimentSettings.SHOW_MULTI_LOCATION_APPT
);
if (
!this.selectedRouteCodeData?.routeCode &&
showMultiLocationAppointment?.toLowerCase() === "true"
) {
let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue( let maxDayRangeToShowPmTimeslot = experimentMixin.methods.getSettingValue(
experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION experimentSettings.SHOW_PM_DAYS_MULTI_LOCATION
); );
@ -2064,9 +2075,6 @@ export default {
let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue( let maxDayRangeToShowAnyTimeslot = experimentMixin.methods.getSettingValue(
experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION experimentSettings.SHOW_NO_AVAILABILE_DAYS_MULTI_LOCATION
); );
let showMultiLocationAppointment = experimentMixin.methods.getSettingValue(
experimentSettings.SHOW_MULTI_LOCATION_APPT
);
let experimentSettingsString = let experimentSettingsString =
"ShowPm:" + "ShowPm:" +
maxDayRangeToShowPmTimeslot + maxDayRangeToShowPmTimeslot +
@ -2089,25 +2097,23 @@ export default {
}; };
// Fetch all appointments // Fetch all appointments
const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAMAppt, firstInshopPMAppt] = const [firstMobileAMAppt, firstMobilePMAppt, firstInshopAppts] = await Promise.all([
await Promise.all([ this.getFirstAvailableMobileApptByTOD("AM"),
this.getFirstAvailableApptByTOD("AM", "mobile"), this.getFirstAvailableMobileApptByTOD("PM"),
this.getFirstAvailableApptByTOD("PM", "mobile"), this.getFirstAvailableInshopApptsByTOD(["AM", "PM"]),
this.getFirstAvailableApptByTOD("AM", "inshop"), ]);
this.getFirstAvailableApptByTOD("PM", "inshop"),
]);
// Extract dates // Extract dates
const firstMobileAMDate = firstMobileAMAppt?.date; const firstMobileAMDate = firstMobileAMAppt?.date;
const firstMobilePMDate = firstMobilePMAppt?.date; const firstMobilePMDate = firstMobilePMAppt?.date;
const firstInshopAMDate = firstInshopAMAppt?.date; const firstInshopAMDate = firstInshopAppts[0]?.date;
const firstInshopPMDate = firstInshopPMAppt?.date; const firstInshopPMDate = firstInshopAppts[1]?.date;
// Calculate days until appointments // Calculate days until appointments
const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt); const numberOfDaysToFirstMobileAMDate = calculateDaysUntilDate(firstMobileAMAppt);
const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt); const numberOfDaysToFirstMobilePMDate = calculateDaysUntilDate(firstMobilePMAppt);
const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAMAppt); const numberOfDaysToFirstInshopAMDate = calculateDaysUntilDate(firstInshopAppts[0]);
const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopPMAppt); const numberOfDaysToFirstInshopPMDate = calculateDaysUntilDate(firstInshopAppts[1]);
const shouldExposeMultiLocationModal = () => { const shouldExposeMultiLocationModal = () => {
const isMobileAppointmentInDateRange = const isMobileAppointmentInDateRange =
@ -2121,10 +2127,7 @@ export default {
(firstInshopPMDate && (firstInshopPMDate &&
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot); numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowAnyTimeslot);
if (showMultiLocationAppointment?.toLowerCase() === "true") { return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange;
return isMobileAppointmentInDateRange && isInshopAppointmentInDateRange;
}
return false;
}; };
if (shouldExposeMultiLocationModal()) { if (shouldExposeMultiLocationModal()) {
@ -2158,8 +2161,8 @@ export default {
// Selects the first available inshop time slot // Selects the first available inshop time slot
promotedInshopAppointment = promotedInshopAppointment =
numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate numberOfDaysToFirstInshopAMDate <= numberOfDaysToFirstInshopPMDate
? firstInshopAMAppt ? firstInshopAppts[0]
: firstInshopPMAppt; : firstInshopAppts[1];
} else { } else {
if ( if (
firstMobilePMDate && firstMobilePMDate &&
@ -2182,15 +2185,15 @@ export default {
numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot numberOfDaysToFirstInshopPMDate <= maxDayRangeToShowPmTimeslot
) { ) {
// Selects the first available PM time slot // Selects the first available PM time slot
promotedInshopAppointment = firstInshopPMAppt; promotedInshopAppointment = firstInshopAppts[1];
} else if ( } else if (
firstInshopPMDate && firstInshopPMDate &&
numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot numberOfDaysToFirstInshopPMDate >= maxDayRangeToShowNoPmTimeslot
) { ) {
// Selects the first available AM time slot. If none, selects the first available PM time slot // Selects the first available AM time slot. If none, selects the first available PM time slot
promotedInshopAppointment = firstInshopAMAppt promotedInshopAppointment = firstInshopAppts[0]
? firstInshopAMAppt ? firstInshopAppts[0]
: firstInshopPMAppt; : firstInshopAppts[1];
} }
} }
@ -2209,16 +2212,11 @@ export default {
} }
} }
} }
this.isLoadingMultiLocationPopup = false;
}, },
async getFirstAvailableApptByTOD(timeOfDay, appointmentType) {
let selectableDays; async getFirstAvailableMobileApptByTOD(timeOfDay) {
if (appointmentType?.toLowerCase() == "mobile") { const selectableDays = this.selectableDatesMobile?.days;
selectableDays = this.selectableDatesMobile?.days;
} else if (appointmentType?.toLowerCase() == "inshop") {
selectableDays = this.selectableDatesInshop?.days;
} else {
return;
}
if (!selectableDays?.length) { if (!selectableDays?.length) {
return null; return null;
} else { } else {
@ -2230,7 +2228,6 @@ export default {
}, },
timeSlot: null, timeSlot: null,
date: null, date: null,
addressCopy: null,
}; };
const isMatchingApptDay = dateObj.timeSlots.some( const isMatchingApptDay = dateObj.timeSlots.some(
(slot) => (slot) =>
@ -2238,16 +2235,6 @@ export default {
(matchingTimeSlot.timeSlot = slot) && (matchingTimeSlot.timeSlot = slot) &&
(matchingTimeSlot.date = dateObj.date) (matchingTimeSlot.date = dateObj.date)
); );
if (
appointmentType?.toLowerCase() == "inshop" &&
this.selectedShopAnswer?.address1 &&
this.selectedShopAnswer?.address2
) {
matchingTimeSlot.addressCopy =
this.selectedShopAnswer?.address1 +
", " +
this.selectedShopAnswer?.address2;
}
if (isMatchingApptDay && matchingTimeSlot) { if (isMatchingApptDay && matchingTimeSlot) {
return matchingTimeSlot; return matchingTimeSlot;
} }
@ -2255,6 +2242,136 @@ export default {
return null; return null;
} }
}, },
findMatchingInshopAppt(selectableDays, timeOfDay, provider) {
if (!selectableDays?.length) return null;
for (const dateObj of selectableDays) {
const distanceInMiles = provider?.distanceInMiles
? Math.round(provider.distanceInMiles * 2) / 2
: 0;
const providerAddress = this.getFormattedShopAddress(
provider?.address,
distanceInMiles
);
let matchingTimeSlot = {
estimatedServiceMinutes: {
minimum: this.estimatedServiceMinutesMinimum,
maximum: this.estimatedServiceMinutesMaximum,
},
timeSlot: null,
date: null,
addressCopy: providerAddress.address1 + ", " + providerAddress.address2,
provider: provider,
};
const isMatchingApptDay = dateObj.timeSlots.some(
(slot) =>
slot.id.includes(timeOfDay) &&
(matchingTimeSlot.timeSlot = slot) &&
(matchingTimeSlot.date = dateObj.date)
);
if (isMatchingApptDay && matchingTimeSlot) {
return matchingTimeSlot;
}
}
return null;
},
async getFirstAvailableInshopApptsByTOD(timeOfDayArray) {
let matchingApptNearestAM1 = this.findMatchingInshopAppt(
this.selectableDatesInshop?.days,
timeOfDayArray[0],
this.selectedProvider
);
let matchingApptNearestPM1 = this.findMatchingInshopAppt(
this.selectableDatesInshop?.days,
timeOfDayArray[1],
this.selectedProvider
);
let matchingApptNearestAM2;
let matchingApptNearestPM2;
let matchingApptNearestAM3;
let matchingApptNearestPM3;
const providerNearest2 = this.shopProviderData.shopProviders[1];
const providerNearest3 = this.shopProviderData.shopProviders[2];
if (providerNearest2?.distanceInMiles && providerNearest2.distanceInMiles < 25) {
this.calendarLoadingStatus = "more";
const selectableDaysFromProviderNearest2 = await getScheduleApiResponse({
startDateString: this.initialViewStartDate,
endDateString: this.initialViewEndDate,
inshopProviderNumber: providerNearest2.providerNumber,
zipCode: this.zipCode,
includeMobileTimeSlots: false,
includeInshopTimeSlots: true,
});
matchingApptNearestAM2 = this.findMatchingInshopAppt(
selectableDaysFromProviderNearest2?.inshopTimeSlotsData?.days,
"AM",
providerNearest2
);
matchingApptNearestPM2 = this.findMatchingInshopAppt(
selectableDaysFromProviderNearest2?.inshopTimeSlotsData?.days,
"PM",
providerNearest2
);
}
if (providerNearest3?.distanceInMiles && providerNearest3?.distanceInMiles < 25) {
this.calendarLoadingStatus = "more";
const selectableDaysFromProviderNearest3 = await getScheduleApiResponse({
startDateString: this.initialViewStartDate,
endDateString: this.initialViewEndDate,
inshopProviderNumber: providerNearest3.providerNumber,
zipCode: this.zipCode,
includeMobileTimeSlots: false,
includeInshopTimeSlots: true,
});
matchingApptNearestAM3 = this.findMatchingInshopAppt(
selectableDaysFromProviderNearest3?.inshopTimeSlotsData?.days,
"AM",
providerNearest3
);
matchingApptNearestPM3 = this.findMatchingInshopAppt(
selectableDaysFromProviderNearest3?.inshopTimeSlotsData?.days,
"PM",
providerNearest3
);
}
this.calendarLoadingStatus = "none";
// WHICH OF THE 3 INSHOP APPTS IS THE EARLIEST?
const compareAppointments = (appt1, appt2) => {
// Handle null/undefined appointments
if (!appt1?.date) return 1; // appt1 is later (or invalid)
if (!appt2?.date) return -1; // appt2 is later (or invalid)
// Compare dates first (YYYY-MM-DD format allows string comparison)
if (appt1.date < appt2.date) return -1; // appt1 is earlier
if (appt1.date > appt2.date) return 1; // appt2 is earlier
// Dates are equal, compare times (HH:MM format allows string comparison)
const time1 = appt1.timeSlot?.startTime || "23:59";
const time2 = appt2.timeSlot?.startTime || "23:59";
if (time1 < time2) return -1; // appt1 is earlier
if (time1 > time2) return 1; // appt2 is earlier
return 0; // Completely equal
};
// Find earliest AM appointment
const preferredApptAM =
[matchingApptNearestAM1, matchingApptNearestAM2, matchingApptNearestAM3]
.filter((appt) => appt?.date) // Remove null/undefined
.sort(compareAppointments)[0] || null;
// Find earliest PM appointment
const preferredApptPM =
[matchingApptNearestPM1, matchingApptNearestPM2, matchingApptNearestPM3]
.filter((appt) => appt?.date)
.sort(compareAppointments)[0] || null;
return [preferredApptAM, preferredApptPM];
},
updateMobileFirstTimeSlotandNavigateForward(timeSlotObj) { updateMobileFirstTimeSlotandNavigateForward(timeSlotObj) {
this.selectedMobileFirstAppointment = true; this.selectedMobileFirstAppointment = true;
this.appointmentType = AppointmentTypeStrings.MOBILE; this.appointmentType = AppointmentTypeStrings.MOBILE;
@ -2264,21 +2381,62 @@ export default {
this.forwardButtonAction(); this.forwardButtonAction();
}, },
updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) { updateMultiLocationTimeSlotandNavigateForward({ selectedTimeSlot, appointmentType }) {
if (appointmentType?.toLowerCase() == "mobile") { if (!selectedTimeSlot) return;
this.appointmentType = AppointmentTypeStrings.MOBILE;
} else if (appointmentType?.toLowerCase() == "inshop") {
this.appointmentType = AppointmentTypeStrings.IN_SHOP;
}
this.selectedMultiLocationAppointment = true; this.selectedMultiLocationAppointment = true;
this.selectedDate = selectedTimeSlot.date; this.selectedDate = selectedTimeSlot.date;
this.updateSelectedProvider(); if (appointmentType?.toLowerCase() == "mobile") {
this.updateTimeSlot(selectedTimeSlot); this.appointmentType = AppointmentTypeStrings.MOBILE;
this.updateSelectedProvider();
this.updateTimeSlot(selectedTimeSlot);
} else if (appointmentType?.toLowerCase() == "inshop") {
this.appointmentType = AppointmentTypeStrings.IN_SHOP;
this.updateSelectedProvider(selectedTimeSlot.provider);
// UPDATE SELECTED TIME SLOT INFO
this.selectedTimeSlotInfo = {
timeSlot: {
date: selectedTimeSlot.date,
routeCode: selectedTimeSlot.routeCode,
startTime: selectedTimeSlot.timeSlot.startTime,
endTime: selectedTimeSlot.timeSlot.endTime,
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
},
isPremiumAppointment: selectedTimeSlot.isPremiumAppointment ? true : false,
};
// UPDATE APPT TYPE
this.appointmentType = this.getInShopOrDropOffApptType(selectedTimeSlot.routeCode);
}
this.forwardButtonAction(); this.forwardButtonAction();
}, },
updateRecalAcknowledgedAndNavigateForward(isAcknowledged) { updateRecalAcknowledgedAndNavigateForward(isAcknowledged) {
this.isRecalAcknowledgedForScheduling = isAcknowledged; this.isRecalAcknowledgedForScheduling = isAcknowledged;
this.forwardButtonAction(); this.forwardButtonAction();
}, },
getFormattedShopAddress(providerAddress, distanceInMiles) {
const toTitleCase = (str) => {
if (!str) return "";
return str.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
if (!providerAddress) return {};
const streetAddress = toTitleCase(providerAddress.streetAddress);
const city = toTitleCase(providerAddress.city);
const state = providerAddress.state;
const zipCode = providerAddress.zipCode;
return {
city: city,
distance: `${distanceInMiles} mi`,
address1: streetAddress,
address2: `${city}, ${state} ${zipCode}`,
};
},
}, },
watch: { watch: {
appointmentTypeFromAppointmentTypeQuestion: { appointmentTypeFromAppointmentTypeQuestion: {

View file

@ -2014,27 +2014,52 @@ export const actions = {
}, },
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) { getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
const flattenedLineItemsWithChildParts = getFlattenedArrayOfLineItemsWithChildParts( const escapeRecalibrationType = (rt) => rt.split("&").join("%26");
context.getters.order.lineItems.glassParts
const partialLineItemsObjects = context.getters.order.lineItems.glassParts?.map(
(lineItem) => ({
partNumber: lineItem.partNumber,
recalibrationType:
lineItem.recalibrationType && isRecalPartOrHasChildRecalPart(lineItem)
? escapeRecalibrationType(lineItem.recalibrationType)
: undefined,
})
); );
const lineItemsList = flattenedLineItemsWithChildParts var lineItems = null;
.map((item) => item.partNumber) if (partialLineItemsObjects) {
.join(","); lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
partialLineItemsObjects,
"lineItems"
);
}
const vehicle = context.getters.vehicle; const vehicle = context.getters.vehicle;
const carId = vehicle.carId; const carId = vehicle.carId;
const damage = context.getters.damage; const damage = context.getters.damage;
const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace);
const glassPieces = buildQueryStringParameterFromArrayOfComplexObjects(
glassArray,
"glassPieces"
);
const parentAccountNumber = const parentAccountNumber =
context.getters.order.payment.parentAccountNumber ?? context.getters.order.payment.parentAccountNumber ??
applicationConfig.CASH_PARENT_ACCOUNT_NUMBER; applicationConfig.CASH_PARENT_ACCOUNT_NUMBER;
const referralSequenceNumber = context.getters.order.referralSequenceNumber;
var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&isRepair=${damage.isRepair}`; var endPoint = `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&parentAccountNumber=${parentAccountNumber}&referralSequenceNumber=${referralSequenceNumber}&applicationName=${applicationConfig.ANALYTICS_APPLICATION_NAME}`;
if (lineItemsList) { if (lineItems) {
endPoint += `&lineItems=${lineItemsList}`; endPoint += `&${lineItems}`;
} }
if (glassPieces) {
endPoint += `&${glassPieces}`;
}
endPoint += `&isHeavyTruckVehicle=${context.getters.order.vehicle.isBigTruck}`;
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method, method: endpoints.GetServiceabilityDetails.method,
endpoint: endPoint, endpoint: endPoint,