Merge pull request #3090 from Safelite/feature/CASH-2378
Feature/CASH-2451 Fix for missing in radius inshop appointments
This commit is contained in:
commit
3a5e20a530
4 changed files with 363 additions and 208 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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", {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
@ -809,13 +811,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 +819,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 [];
|
||||||
},
|
},
|
||||||
|
|
@ -977,6 +965,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 +1926,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 +2053,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 +2070,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 +2092,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 +2122,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 +2156,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 +2180,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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2210,15 +2208,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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 +2222,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 +2229,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 +2236,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 +2375,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: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue