Merge pull request #3044 from Safelite/feature/CASH-1790-refactor-fixes
Feature/cash 1790 refactor fixes
This commit is contained in:
commit
7eef34f6a9
6 changed files with 154 additions and 2842 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
|||
<template>
|
||||
<div
|
||||
class="date-picker text-center"
|
||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
|
||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass} ${loadingStatus === 'reinitial' ? 'loading-reinitial' : ''}`">
|
||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||
<legend class="sr-only">Select a day and time</legend>
|
||||
<div
|
||||
v-for="month in months"
|
||||
v-for="(month, i) in months"
|
||||
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||
class="calendar-grid-container"
|
||||
class="calendar-grid-container month"
|
||||
:class="[
|
||||
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
||||
month.monthClass,
|
||||
i === 0 ? 'first-month-shown' : '',
|
||||
]">
|
||||
<div class="month-year body-small d-flex align-items-center">
|
||||
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
|
||||
|
|
@ -114,7 +115,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<loader
|
||||
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
||||
class="date-picker-loader"
|
||||
:class="[loadingStatus !== 'none' ? 'show-loader' : '']"
|
||||
loaderColor="blue"
|
||||
loaderPosition="center" />
|
||||
<div class="row form-test-error" id="date-of-month-error">
|
||||
|
|
@ -125,7 +127,7 @@
|
|||
type="button"
|
||||
class="btn btn-link view-more-dates"
|
||||
id="viewMoreDates"
|
||||
:disabled="isLoading"
|
||||
:disabled="loadingStatus !== 'none'"
|
||||
@click="showAnotherMonth">
|
||||
View more dates
|
||||
</button>
|
||||
|
|
@ -160,7 +162,6 @@ export default {
|
|||
name: "datePicker",
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
months: null,
|
||||
disableViewMoreDatesButton: false,
|
||||
hideSomeDaysForInitialView: null,
|
||||
|
|
@ -169,7 +170,10 @@ export default {
|
|||
};
|
||||
},
|
||||
props: {
|
||||
customComponentId: String,
|
||||
customComponentId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
selectableDatesSetting: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
|
|
@ -200,6 +204,10 @@ export default {
|
|||
pricingByDayUpcharge: Number,
|
||||
isPricingByDayExperiment: Boolean,
|
||||
isMobileSelected: Boolean,
|
||||
loadingStatus: {
|
||||
type: String,
|
||||
default: "none",
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
|
|
@ -494,7 +502,6 @@ export default {
|
|||
});
|
||||
},
|
||||
async setCalendarData(config = {}) {
|
||||
this.isLoading = true;
|
||||
this.selectableDatesInshop = [];
|
||||
this.selectableDatesMobile = [];
|
||||
this.months = [];
|
||||
|
|
@ -546,7 +553,6 @@ export default {
|
|||
// }
|
||||
}
|
||||
this.months = months;
|
||||
this.isLoading = false;
|
||||
|
||||
if (config.preSelectedDate) {
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -734,7 +740,6 @@ export default {
|
|||
},
|
||||
async showAnotherMonth() {
|
||||
if (!this.months) return;
|
||||
this.isLoading = true;
|
||||
let monthToShow;
|
||||
let monthStartDate;
|
||||
|
||||
|
|
@ -779,7 +784,6 @@ export default {
|
|||
monthToShowDates[monthToShowDates.length - 1].inputValue
|
||||
);
|
||||
|
||||
this.isLoading = false;
|
||||
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
|
||||
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
||||
this.scrollToElement(monthToShow.monthString);
|
||||
|
|
@ -792,7 +796,6 @@ export default {
|
|||
},
|
||||
async updateSelectableDates(monthStart, monthEnd) {
|
||||
const moreSelectableDates = await this.getMoreDatesCallback(monthStart, monthEnd);
|
||||
|
||||
moreSelectableDates.inshopTimeSlotsData.days.forEach((selectableDate) => {
|
||||
const index = this.selectableDatesInshop.findIndex(
|
||||
(dateObj) => dateObj.date === selectableDate.date
|
||||
|
|
@ -851,12 +854,6 @@ export default {
|
|||
},
|
||||
},
|
||||
watch: {
|
||||
isLoading(newValue) {
|
||||
// if done loading dates, then set to first available date
|
||||
if (newValue === false && this.firstAvailableSelectableDate) {
|
||||
this.selectedDate = this.firstAvailableSelectableDate;
|
||||
}
|
||||
},
|
||||
modelValue(newValue) {
|
||||
this.resetField({
|
||||
value: newValue,
|
||||
|
|
@ -877,9 +874,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.date-picker-hidden {
|
||||
.date-picker-loader {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
|
||||
&.show-loader {
|
||||
opacity: 1;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
.date-picker {
|
||||
position: relative;
|
||||
|
|
@ -908,12 +910,12 @@ export default {
|
|||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
.calendar-grid-container {
|
||||
.month {
|
||||
margin: 0 auto 1rem auto;
|
||||
position: relative;
|
||||
transition:
|
||||
height ease 2s,
|
||||
opacity ease 2s;
|
||||
height ease 1s,
|
||||
max-height ease 1s;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
justify-content: center;
|
||||
|
|
@ -1162,7 +1164,7 @@ export default {
|
|||
}
|
||||
|
||||
.past {
|
||||
.calendar-grid-container {
|
||||
.month {
|
||||
.month-year {
|
||||
grid-area: 1 / 1 / 2 / 6;
|
||||
}
|
||||
|
|
@ -1219,7 +1221,7 @@ export default {
|
|||
}
|
||||
// new calendar styling overrides
|
||||
&.calendar-2025 {
|
||||
.calendar-grid-container .grid-item {
|
||||
.month .grid-item {
|
||||
margin: 0 0 0.15rem 0;
|
||||
}
|
||||
.month-year {
|
||||
|
|
@ -1321,6 +1323,33 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-reinitial {
|
||||
.month {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
.month.first-month-shown {
|
||||
opacity: 1;
|
||||
max-height: none;
|
||||
|
||||
.grid-item.radio-wrapper.selectable-day {
|
||||
input[type="radio"]:checked + label,
|
||||
label {
|
||||
background-color: $white;
|
||||
border: none;
|
||||
color: initial;
|
||||
cursor: default;
|
||||
&:hover {
|
||||
border: none;
|
||||
}
|
||||
span {
|
||||
font-family: UrbanistRegular;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.timeslots .button-inner-wrapper) {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ export function isDropOffRouteCode(routeCode) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getTodayDate(routeCode) {
|
||||
export function getTodayDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
export function getTodayDateString(routeCode) {
|
||||
export function getTodayDateString() {
|
||||
return convertDateToDateString(getTodayDate());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,8 @@
|
|||
:pricingByDayBasePrice="pricingByDayBasePrice"
|
||||
:pricingByDayUpcharge="pricingByDayUpcharge"
|
||||
:showPricingByDay="showPricingByDay"
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment" />
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment"
|
||||
:loadingStatus="calendarLoadingStatus" />
|
||||
<timeSlotQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
v-if="showTimeSlotQuestion"
|
||||
|
|
@ -168,7 +169,8 @@
|
|||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:isSameDay="isSameDay"
|
||||
:selectedRouteCodeData="selectedRouteCodeData"
|
||||
@waitListRequested="handleWaitListRequested" />
|
||||
@waitListRequested="handleWaitListRequested"
|
||||
:lazyLoadAmountToLoad="10" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
|
|
@ -439,15 +441,14 @@ export default {
|
|||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
mobilePremiumAppointmentFee: null,
|
||||
waitListRequested: null,
|
||||
displayWaitList: null,
|
||||
pricingByDayUpchargeLineItem: null,
|
||||
includePricingByDayUpcharge: null,
|
||||
isPricingByDayExperiment: null,
|
||||
pricingByDayBasePrice: null,
|
||||
pricingByDayUpcharge: null,
|
||||
showPricingByDay: null,
|
||||
selectableDatesInshop: [],
|
||||
selectableDatesMobile: [],
|
||||
selectableDatesInshop: {},
|
||||
selectableDatesMobile: {},
|
||||
preSelectedDate: null,
|
||||
streetAddress: this.getServiceAddressFromStore(),
|
||||
apartmentNumberOrBusinessName: this.getServiceAddress2FromStore(),
|
||||
|
|
@ -480,6 +481,7 @@ export default {
|
|||
experimentSettings.SHOW_MOBILE_FIRST_APPT,
|
||||
"true"
|
||||
),
|
||||
calendarLoadingStatus: "none",
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -590,6 +592,8 @@ export default {
|
|||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.showLoadingModal();
|
||||
vm.calendarLoadingStatus = "initial";
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
||||
vm.mobilePremiumAppointmentFee = pricedPremiumFee;
|
||||
|
|
@ -610,6 +614,9 @@ export default {
|
|||
);
|
||||
vm.initializeDatePicker().then(() => {
|
||||
vm.showMobileFirstModal();
|
||||
vm.hideLoadingModal();
|
||||
vm.calendarLoadingStatus = "none";
|
||||
if (!vm.preSelectedDate) vm.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
@ -896,6 +903,53 @@ export default {
|
|||
? this.$refs.mobileFirstModal?.getIsModalOpen()
|
||||
: false;
|
||||
},
|
||||
displayWaitList() {
|
||||
if (!this.appointmentType) return false;
|
||||
const firstMobileDateString =
|
||||
Array.isArray(this.selectableDatesMobile.days) &&
|
||||
this.selectableDatesMobile.days.length > 0
|
||||
? this.selectableDatesMobile.days[0].date
|
||||
: null;
|
||||
const firstInshopDateString =
|
||||
Array.isArray(this.selectableDatesInshop.days) &&
|
||||
this.selectableDatesInshop.days.length > 0
|
||||
? this.selectableDatesInshop.days[0].date
|
||||
: null;
|
||||
const dateString =
|
||||
this.isMobileSelected && firstMobileDateString
|
||||
? firstMobileDateString
|
||||
: firstInshopDateString;
|
||||
|
||||
if (
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_WAITLIST,
|
||||
"true"
|
||||
) &&
|
||||
dateString
|
||||
) {
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1, day);
|
||||
const currentDate = getTodayDate();
|
||||
const futureDate = new Date(currentDate);
|
||||
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
? parseInt(
|
||||
experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
)
|
||||
: 0;
|
||||
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
||||
|
||||
if (targetDate >= futureDate) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
hasSelectableDatesLoaded() {
|
||||
if (this.isMobileSelected) {
|
||||
return this.selectableDatesMobile?.days?.length > 0;
|
||||
|
|
@ -1164,9 +1218,10 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
async onShopSelected(shopQuestionPopUpData) {
|
||||
onShopSelected(shopQuestionPopUpData) {
|
||||
this.resetWaitlist();
|
||||
this.preSelectedDate = null;
|
||||
this.calendarLoadingStatus = "reinitial";
|
||||
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
||||
const oldProvider = this.selectedProvider;
|
||||
const selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
||||
|
|
@ -1186,11 +1241,16 @@ export default {
|
|||
this.updateSelectedProvider(selectedProvider);
|
||||
}
|
||||
}
|
||||
await this.initializeDatePicker();
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
this.initializeDatePicker().then(() => {
|
||||
this.showMobileFirstModal();
|
||||
this.hideLoadingModal();
|
||||
this.calendarLoadingStatus = "none";
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
},
|
||||
async getMoreScheduleData(startDate, endDate) {
|
||||
// called only when "View more dates" is clicked; not on initial page load
|
||||
this.calendarLoadingStatus = "more";
|
||||
const moreShopTimeSlots = await getScheduleApiResponse({
|
||||
startDateString: startDate,
|
||||
endDateString: endDate,
|
||||
|
|
@ -1202,6 +1262,7 @@ export default {
|
|||
includeMobileTimeSlots: this.isServiceableMobile,
|
||||
includeInshopTimeSlots: this.isServiceableInshop || this.isServiceableDropoff,
|
||||
});
|
||||
this.calendarLoadingStatus = "none";
|
||||
|
||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||
this.selectableDatesInshop.days = this.selectableDatesInshop.days.concat(
|
||||
|
|
@ -1240,7 +1301,6 @@ export default {
|
|||
await datePickerInitialData.initialShopTimeSlotsResponse.inshopTimeSlotsData;
|
||||
this.selectableDatesMobile =
|
||||
await datePickerInitialData.initialShopTimeSlotsResponse.mobileTimeSlotsData;
|
||||
this.setDisplayWaitList();
|
||||
|
||||
if (this.preSelectedDate) {
|
||||
this.selectedDate = this.preSelectedDate;
|
||||
|
|
@ -1252,11 +1312,8 @@ export default {
|
|||
) {
|
||||
await this.$nextTick();
|
||||
await this.$refs.datePicker.showAnotherMonth();
|
||||
this.setDisplayWaitList();
|
||||
}
|
||||
}
|
||||
|
||||
this.isShowMobileFirstAppt && this.hideLoadingModal();
|
||||
},
|
||||
getIsRecalAcknowledgedForSchedulingFromStore() {
|
||||
return store.getters.order.isRecalAcknowledgedForScheduling;
|
||||
|
|
@ -1526,39 +1583,6 @@ export default {
|
|||
}
|
||||
this.showLoadingModal();
|
||||
},
|
||||
setDisplayWaitList() {
|
||||
const dateString = this.isMobileSelected
|
||||
? this.selectableDatesMobile?.days[0]?.date
|
||||
: this.selectableDatesInshop?.days[0]?.date;
|
||||
if (
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_WAITLIST,
|
||||
"true"
|
||||
) &&
|
||||
dateString
|
||||
) {
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1, day);
|
||||
const currentDate = getTodayDate();
|
||||
const futureDate = new Date(currentDate);
|
||||
const experimentThresholdDays = experimentMixin.methods.hasSetting(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
? parseInt(
|
||||
experimentMixin.methods.getSettingValue(
|
||||
experimentSettings.WAITLIST_THRESHOLD_DAYS
|
||||
)
|
||||
)
|
||||
: 0;
|
||||
futureDate.setDate(currentDate.getDate() + experimentThresholdDays);
|
||||
|
||||
if (targetDate >= futureDate) {
|
||||
this.displayWaitList = true;
|
||||
} else {
|
||||
this.displayWaitList = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateSupportingItems() {
|
||||
const supportingItems = this.getSupportingItems();
|
||||
|
||||
|
|
@ -1754,6 +1778,7 @@ export default {
|
|||
}
|
||||
|
||||
this.resetMobileLocation();
|
||||
this.calendarLoadingStatus = "initial";
|
||||
|
||||
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
|
||||
this.shopProviderData = result.data;
|
||||
|
|
@ -1764,6 +1789,9 @@ export default {
|
|||
this.selectedDate = null;
|
||||
this.initializeDatePicker().then(() => {
|
||||
this.showMobileFirstModal();
|
||||
this.hideLoadingModal();
|
||||
this.calendarLoadingStatus = "none";
|
||||
this.setSelectedDateToFirstAvailable();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@
|
|||
isInshopOrDropOffAppointment ? 'is-inshop-or-drop-off-appointment' : '',
|
||||
]"
|
||||
:answers="availableTimeSlots"
|
||||
:lazyLoad="{ isLazyLoad: !isMobileAppointment, amountToLoad: 10 }"
|
||||
:lazyLoad="{
|
||||
isLazyLoad: isLazyLoad,
|
||||
amountToLoad: lazyLoadAmountToLoad || 10,
|
||||
}"
|
||||
groupName="chooseTimeSlot"
|
||||
textPosition="text-center"
|
||||
questionText="Available times:"
|
||||
|
|
@ -134,6 +137,7 @@ export default {
|
|||
isSameDay: Boolean,
|
||||
displayWaitList: Boolean,
|
||||
selectedRouteCodeData: Object,
|
||||
lazyLoadAmountToLoad: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -396,6 +400,26 @@ export default {
|
|||
!this.isDropOffAppointmentAvailable
|
||||
);
|
||||
},
|
||||
isLazyLoad() {
|
||||
if (this.availableTimeSlots?.length < this.lazyLoadAmountToLoad + 1) return false;
|
||||
if (this.isMobileAppointment) return false;
|
||||
if (this.selectedAnswerForTimeSlots && !this.isSelectedInshopTimeSlotShowing)
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
availableTimeSlotsForInshop() {
|
||||
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate?.timeSlots);
|
||||
},
|
||||
isSelectedInshopTimeSlotShowing() {
|
||||
if (!this.selectedAnswerForTimeSlots) return false;
|
||||
const idx = this.availableTimeSlotsForInshop?.findIndex((timeSlot) => {
|
||||
return timeSlot.value === this.selectedAnswerForTimeSlots;
|
||||
});
|
||||
if (idx > -1 && idx < this.lazyLoadAmountToLoad) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
|
||||
|
|
@ -453,6 +477,7 @@ export default {
|
|||
// }
|
||||
// },
|
||||
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
||||
if (!timeSlotsForSelectedDate) return;
|
||||
return timeSlotsForSelectedDate
|
||||
.filter((timeSlot) => {
|
||||
return !isDropOffRouteCode(timeSlot.id);
|
||||
|
|
|
|||
Loading…
Reference in a new issue