Final review of Scheduling Page

Refactor scheduling.vue state to colocate providers with their time slots

Replace parallel inShopProviders/inshopTimeSlots arrays with a single
inShopProvidersAndTimeslots array of { provider, timeSlots } objects, and
combine mobileTimeSlots/mobileProviderNumber into a single
mobileProviderAndTimeSlot object. Also extract SCHEDULE_FETCH_DAYS constant
to replace the magic number 14
add JSDoc to toDateString, and remove the
unused inShopProviderNumber state.
This commit is contained in:
scottkiener-at-safelite 2026-05-06 12:45:48 -04:00
parent 9dbcf1c93d
commit bf38d11bca

View file

@ -50,12 +50,19 @@ import {
} from "@/helpers/page-prerequisites-helper.js"; } from "@/helpers/page-prerequisites-helper.js";
import { debugLog } from "@/helpers/debug-log-helper"; import { debugLog } from "@/helpers/debug-log-helper";
/**
* Returns a YYYY-MM-DD date string offset by the given number of days from a base date.
* @param {number} offsetDays - Number of days to offset (positive or negative).
* @param {Date} base - The starting date (defaults to today).
*/
function toDateString(offsetDays, base = new Date()) { function toDateString(offsetDays, base = new Date()) {
const d = new Date(base); const d = new Date(base);
d.setDate(d.getDate() + offsetDays); d.setDate(d.getDate() + offsetDays);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
} }
const SCHEDULE_FETCH_DAYS = 15;
export default { export default {
name: "scheduling", name: "scheduling",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -65,29 +72,31 @@ export default {
payload: { serviceZipCode }, payload: { serviceZipCode },
pageNameToLog: to.name, pageNameToLog: to.name,
}); });
// Get the first 3 providers from the shopProviderData
const providers = shopProviderData?.data?.shopProviders?.slice(0, 3) ?? []; const providers = shopProviderData?.data?.shopProviders?.slice(0, 3) ?? [];
const mobileProviderNumber = shopProviderData?.data?.mobileProviderNumber ?? null; const mobileProviderNumber = shopProviderData?.data?.mobileProviderNumber ?? null;
const startDate = toDateString(0); const startDate = toDateString(0);
const endDate = toDateString(14); const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
const promiseResultMap = [ const promiseResultMap = [
{ {
resultKey: "cmsContent", resultKey: "cmsContent",
promise: fetchCmsContentForPage(to.name), promise: fetchCmsContentForPage(to.name),
}, },
...providers.map((p, i) => ({ ...providers.map((provider, i) => ({
resultKey: `inshopTimeSlots_${i}`, resultKey: `inshopTimeSlots_${i}`,
promise: store.dispatch("getShopTimeSlots", { promise: store.dispatch("getShopTimeSlots", {
payload: { payload: {
startDate, startDate,
endDate, endDate,
shopAppointmentType: "InshopOrDropoff", shopAppointmentType: "InshopOrDropoff",
providerNumber: p.providerNumber, providerNumber: provider.providerNumber,
}, },
pageNameToLog: to.name, pageNameToLog: to.name,
}), }),
})), })),
// Get the mobile time slots if a mobile provider number is available
...(mobileProviderNumber ...(mobileProviderNumber
? [ ? [
{ {
@ -103,11 +112,13 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.inShopProviders = providers; vm.inShopProvidersAndTimeslots = providers.map((provider, i) => ({
vm.inshopTimeSlots = providers.map((_, i) => resultMap[`inshopTimeSlots_${i}`] ?? null); provider,
vm.mobileTimeSlots = resultMap.mobileTimeSlots ?? null; timeSlots: resultMap[`inshopTimeSlots_${i}`] ?? null,
vm.inShopProviderNumber = providers[0]?.providerNumber ?? null; }));
vm.mobileProviderNumber = mobileProviderNumber; vm.mobileProviderAndTimeSlot = mobileProviderNumber
? { providerNumber: mobileProviderNumber, timeSlots: resultMap.mobileTimeSlots ?? null }
: null;
}); });
}, },
computed: { computed: {
@ -115,10 +126,10 @@ export default {
return this.getCmsContent("ServiceLocationText", "Text"); return this.getCmsContent("ServiceLocationText", "Text");
}, },
availableDates() { availableDates() {
const inshopDates = this.inshopTimeSlots.flatMap((ts) => const inshopDates = this.inShopProvidersAndTimeslots.flatMap(({ timeSlots }) =>
(ts?.days ?? []).map((d) => d.date) (timeSlots?.days ?? []).map((d) => d.date)
); );
const mobileDates = (this.mobileTimeSlots?.days ?? []).map((d) => d.date); const mobileDates = (this.mobileProviderAndTimeSlot?.timeSlots?.days ?? []).map((d) => d.date);
return [...new Set([...inshopDates, ...mobileDates])].sort(); return [...new Set([...inshopDates, ...mobileDates])].sort();
}, },
}, },
@ -127,12 +138,9 @@ export default {
selectedDate: null, selectedDate: null,
isLoadingDates: false, isLoadingDates: false,
datePickerStartDate: toDateString(0), datePickerStartDate: toDateString(0),
datePickerEndDate: toDateString(14), datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
inShopProviders: [], inShopProvidersAndTimeslots: [],
inshopTimeSlots: [], mobileProviderAndTimeSlot: null,
mobileTimeSlots: null,
inShopProviderNumber: null,
mobileProviderNumber: null,
}; };
}, },
methods: { methods: {
@ -194,7 +202,7 @@ export default {
this.$refs.loadingModal.hideModal(); this.$refs.loadingModal.hideModal();
}, },
forwardButtonAction() { forwardButtonAction() {
const appointmentType = store.getters.order.serviceLocation.appointmentType; const appointmentType = store.getters.order.serviceLocation.appointmentType; // TODO: remove this once we have a proper appointment type
if (appointmentType === AppointmentTypeStrings.MOBILE) { if (appointmentType === AppointmentTypeStrings.MOBILE) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE, this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,