Merge pull request #2839 from Safelite/schedule-refactor/post-parity
Date-Picker Refactor
This commit is contained in:
commit
591cc417ab
4 changed files with 314 additions and 346 deletions
|
|
@ -2,16 +2,6 @@
|
|||
<div
|
||||
class="date-picker text-center"
|
||||
:class="`${calendarViewDirection} calendar-2025 ${showPricingByDayClass}`">
|
||||
<div class="date-picker-header">
|
||||
<funnelSubHeader cmsWidgetName="DatePickerSubHeaderWidget" class="mt-5" />
|
||||
<textBlock
|
||||
v-show="durationTextBlockCopy"
|
||||
:customText="durationTextBlockCopy"
|
||||
typeStyle="small"
|
||||
marginTopSizeOverride="0"
|
||||
class="duration-text-block" />
|
||||
</div>
|
||||
|
||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||
<legend class="sr-only">Select a day and time</legend>
|
||||
<div
|
||||
|
|
@ -139,9 +129,9 @@
|
|||
@click="showAnotherMonth">
|
||||
View more dates
|
||||
</button>
|
||||
<timeSlotQuestion
|
||||
<!--<timeSlotQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
v-model="selectedTimeSlotInfo" // Removed, some watcher logic for duration was in here, need ot recreate
|
||||
cmsWidgetName="TimeSlotModalQuestion"
|
||||
waitListLabelWidget="WaitListLabelWidget"
|
||||
waitListQuestionWidget="WaitListQuestionWidget"
|
||||
|
|
@ -151,24 +141,19 @@
|
|||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
dropOffOrPickATimeQuestionCmsWidgetName="DropOffOrPickATimeQuestionWidget"
|
||||
:selectedDate="selectedDate"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="premiumAppointmentFee"
|
||||
:displayWaitList="displayWaitList"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:estimatedServiceMinutesMinimum="estimatedServiceMinutesMinimum"
|
||||
:estimatedServiceMinutesMaximum="estimatedServiceMinutesMaximum"
|
||||
@waitListRequested="handleWaitListRequested"
|
||||
@time-slot-modal-closed="timeSlotModalClosed" />
|
||||
:selectedDate="selectedDate" // used in date-picker
|
||||
:appointmentType="appointmentType" // used in date-picker for duration
|
||||
:premiumAppointmentFee="premiumAppointmentFee" // Removed from date-picker
|
||||
:displayWaitList="displayWaitList" // Removed from date-picker
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate" // Removed from date-picker (but it had a null check for is-same-day)
|
||||
:estimatedServiceMinutesMinimum="estimatedServiceMinutesMinimum" // used in date-picker for duration
|
||||
:estimatedServiceMinutesMaximum="estimatedServiceMinutesMaximum" // used in date-picker for duration
|
||||
@waitListRequested="handleWaitListRequested" // removed, it's a pass-through /> -->
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
|
||||
// Supporting files
|
||||
import loader from "@/ux-components/loader/loader";
|
||||
import store from "@/store";
|
||||
|
|
@ -178,12 +163,6 @@ import {
|
|||
MONTHS_OF_YEAR,
|
||||
DAYS_OF_WEEK,
|
||||
} from "@/digital-components/date-picker/mixins/constants";
|
||||
import {
|
||||
AppointmentTypeStrings,
|
||||
RouteCodeFlags,
|
||||
PREMIUM_FEE_PART_TYPE,
|
||||
cmsWidgetFieldMappings,
|
||||
} from "@/constants/schedule-constants";
|
||||
import {
|
||||
selectableDaysOptions,
|
||||
requiredParameter,
|
||||
|
|
@ -195,7 +174,6 @@ import {
|
|||
import { useField, ErrorMessage } from "vee-validate";
|
||||
import { deepClone } from "@/helpers/object-helper";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||
|
||||
export default {
|
||||
name: "datePicker",
|
||||
|
|
@ -205,10 +183,8 @@ export default {
|
|||
months: null,
|
||||
disableViewMoreDatesButton: false,
|
||||
hideSomeDaysForInitialView: null,
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
selectableDatesInshop: [], // NOTE: this and the mobile version below use monthNum (1-based), NOT monthIndex (0-based)
|
||||
selectableDatesMobile: [],
|
||||
durationTextBlockCopyForInshopOrDropoff: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
|
@ -242,12 +218,6 @@ export default {
|
|||
pricingByDayBasePrice: Number,
|
||||
pricingByDayUpcharge: Number,
|
||||
isPricingByDayExperiment: Boolean,
|
||||
timeSlotsForSelectedDate: Object,
|
||||
appointmentType: String,
|
||||
premiumAppointmentFee: Object,
|
||||
estimatedServiceMinutesMinimum: Number,
|
||||
estimatedServiceMinutesMaximum: Number,
|
||||
displayWaitList: Boolean,
|
||||
isMobileSelected: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
|
|
@ -309,80 +279,10 @@ export default {
|
|||
this.dispatchStoreAction(this.storeActions.SAVE_WAITLIST_REQUESTED, false, false);
|
||||
},
|
||||
},
|
||||
dropOffDurationText() {
|
||||
return this.getCmsContent("DropOffTimeSlotModal", cmsWidgetFieldMappings.DURATION);
|
||||
},
|
||||
sameDayDropoffDurationText() {
|
||||
return this.getCmsContent(
|
||||
"SameDayDropOffTimeSlotModal",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
},
|
||||
overnightDropoffDurationText() {
|
||||
return this.getCmsContent(
|
||||
"OvernightDropOffTimeSlotModal",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
},
|
||||
inshopDurationText() {
|
||||
const inshopDurationTextWithoutTime = this.getCmsContent(
|
||||
"TimeSlotModalQuestion",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
|
||||
const inshopDurationTime = getDisplayTextForDurationLength(
|
||||
this.estimatedServiceMinutesMinimum,
|
||||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${inshopDurationTextWithoutTime} <b>${inshopDurationTime}</b>`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
mobileDurationText() {
|
||||
const mobileDurationTextWithoutTime = this.getCmsContent(
|
||||
"TimeSlotModalQuestion",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
|
||||
const mobileDurationTime = getDisplayTextForDurationLength(
|
||||
this.estimatedServiceMinutesMinimum,
|
||||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${mobileDurationTextWithoutTime} <b>${mobileDurationTime}</b>`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
durationTextBlockCopy() {
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return this.mobileDurationText;
|
||||
} else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
return this.inshopDurationText;
|
||||
} else if (
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF ||
|
||||
this.appointmentType === AppointmentTypeStrings.DROP_OFF
|
||||
) {
|
||||
return this.durationTextBlockCopyForInshopOrDropoff ?? this.inshopDurationText;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
isSameDay() {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return false;
|
||||
}
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return this.selectedDate === todaysDate;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async initializeComponent(initialData) {
|
||||
await this.setCalendarData(initialData);
|
||||
this.$refs.timeSlotModalQuestion.initializeComponent();
|
||||
},
|
||||
fireDateSelectedEvent(event, date) {
|
||||
// Ignore if arrow key selected radioButton
|
||||
|
|
@ -949,42 +849,9 @@ export default {
|
|||
};
|
||||
window.requestAnimationFrame(step);
|
||||
},
|
||||
getSelectedTimeSlotInfo() {
|
||||
const supportingItems = this.getSupportingItems();
|
||||
|
||||
var isPremiumAppointment = false;
|
||||
if (supportingItems) {
|
||||
isPremiumAppointment =
|
||||
!!supportingItems.filter(
|
||||
(lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE
|
||||
).length > 0;
|
||||
}
|
||||
|
||||
const selectedTimeSlotInfo = {
|
||||
timeSlot: store.getters.order.schedule,
|
||||
isPremiumAppointment: isPremiumAppointment,
|
||||
};
|
||||
|
||||
return selectedTimeSlotInfo;
|
||||
},
|
||||
getSupportingItems() {
|
||||
return store.getters.lineItems.supportingItems;
|
||||
},
|
||||
handleWaitListRequested(value) {
|
||||
this.$emit("waitListRequested", value);
|
||||
},
|
||||
getDurationTextBlockCopyForInshopOrDropoff(selectedRouteCode) {
|
||||
if (selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
return this.overnightDropoffDurationText;
|
||||
} else if (selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.isSameDay) {
|
||||
return this.sameDayDropoffDurationText;
|
||||
} else {
|
||||
return this.dropOffDurationText;
|
||||
}
|
||||
}
|
||||
return this.inshopDurationText;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
|
|
@ -998,19 +865,10 @@ export default {
|
|||
this.scrollToElement("date-of-month-error");
|
||||
}
|
||||
},
|
||||
selectedTimeSlotInfo(newValue) {
|
||||
const routeCode = newValue?.timeSlot?.routeCode;
|
||||
this.durationTextBlockCopyForInshopOrDropoff =
|
||||
this.getDurationTextBlockCopyForInshopOrDropoff(routeCode);
|
||||
this.$emit("TimeSlotSelected", newValue); // needed to update footer button text on Schedule page and to save to Store correctly
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loader,
|
||||
ErrorMessage,
|
||||
timeSlotQuestion,
|
||||
textBlock,
|
||||
funnelSubHeader,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
121
src/layouts/schedule/duration-text-block/duration-text-block.vue
Normal file
121
src/layouts/schedule/duration-text-block/duration-text-block.vue
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<textBlock
|
||||
:customText="durationTextBlockCopy"
|
||||
justifyText="center"
|
||||
typeStyle="small"
|
||||
marginTopSizeOverride="0"
|
||||
class="duration-text-block" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
||||
// Constants
|
||||
import { AppointmentTypeStrings, cmsWidgetFieldMappings } from "@/constants/schedule-constants";
|
||||
|
||||
// Helpers
|
||||
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||
|
||||
export default {
|
||||
name: "duration-text-block",
|
||||
props: {
|
||||
isSameDay: Boolean,
|
||||
appointmentType: String,
|
||||
estimatedServiceMinutesMinimum: String,
|
||||
estimatedServiceMinutesMaximum: String,
|
||||
isOvernightDropoff: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedRouteCode: null,
|
||||
selectedAnswerForDropOffOrInshop: null,
|
||||
selectedAnswerForTimeSlots: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropOffDurationText() {
|
||||
return this.getCmsContent("DropOffTimeSlotModal", cmsWidgetFieldMappings.DURATION);
|
||||
},
|
||||
sameDayDropoffDurationText() {
|
||||
return this.getCmsContent(
|
||||
"SameDayDropOffTimeSlotModal",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
},
|
||||
overnightDropoffDurationText() {
|
||||
return this.getCmsContent(
|
||||
"OvernightDropOffTimeSlotModal",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
},
|
||||
inshopDurationText() {
|
||||
const inshopDurationTextWithoutTime = this.getCmsContent(
|
||||
"TimeSlotModalQuestion",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
|
||||
const inshopDurationTime = getDisplayTextForDurationLength(
|
||||
this.estimatedServiceMinutesMinimum,
|
||||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${inshopDurationTextWithoutTime} ${inshopDurationTime}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
mobileDurationText() {
|
||||
const mobileDurationTextWithoutTime = this.getCmsContent(
|
||||
"TimeSlotModalQuestion",
|
||||
cmsWidgetFieldMappings.DURATION
|
||||
);
|
||||
|
||||
const mobileDurationTime = getDisplayTextForDurationLength(
|
||||
this.estimatedServiceMinutesMinimum,
|
||||
this.estimatedServiceMinutesMaximum
|
||||
);
|
||||
|
||||
if (this.estimatedServiceMinutesMinimum && this.estimatedServiceMinutesMaximum) {
|
||||
return `${mobileDurationTextWithoutTime} ${mobileDurationTime}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
durationTextBlockCopy() {
|
||||
console.log(
|
||||
"durationTextBlockCopy",
|
||||
this.appointmentType,
|
||||
this.estimatedServiceMinutesMaximum,
|
||||
this.estimatedServiceMinutesMinimum
|
||||
);
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return this.mobileDurationText;
|
||||
} else if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||
return this.getDurationTextBlockCopyForDropoff();
|
||||
} else {
|
||||
return this.inshopDurationText;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getDurationTextBlockCopyForDropoff() {
|
||||
if (this.isOvernightDropoff) {
|
||||
return this.overnightDropoffDurationText;
|
||||
} else {
|
||||
if (this.isSameDay) {
|
||||
return this.sameDayDropoffDurationText;
|
||||
} else {
|
||||
return this.dropOffDurationText;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textBlock,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -113,6 +113,15 @@
|
|||
<div class="row date-picker-wrapper">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<div v-show="appointmentType" class="date-picker-header">
|
||||
<funnelSubHeader cmsWidgetName="DatePickerSubHeaderWidget" class="mt-5" />
|
||||
<durationTextBlock
|
||||
:isSameDay="isSameDay"
|
||||
:appointmentType="appointmentType"
|
||||
:estimatedServiceMinutesMinimum="estimatedServiceMinutesMinimum"
|
||||
:estimatedServiceMinutesMaximum="estimatedServiceMinutesMaximum"
|
||||
:isOvernightDropoff="isOvernightDropoff" />
|
||||
</div>
|
||||
<datePicker
|
||||
:currentZip="zipCode"
|
||||
:currentProviderNumber="selectedProvider?.providerNumber"
|
||||
|
|
@ -129,14 +138,25 @@
|
|||
:pricingByDayBasePrice="pricingByDayBasePrice"
|
||||
:pricingByDayUpcharge="pricingByDayUpcharge"
|
||||
:showPricingByDay="showPricingByDay"
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:isPricingByDayExperiment="isPricingByDayExperiment" />
|
||||
<timeSlotQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
v-if="showTimeSlotQuestion"
|
||||
@time-slot-selection-changed="updateTimeSlot"
|
||||
waitListLabelWidget="WaitListLabelWidget"
|
||||
waitListQuestionWidget="WaitListQuestionWidget"
|
||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
dropOffOrPickATimeQuestionCmsWidgetName="DropOffOrPickATimeQuestionWidget"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||
:estimatedServiceMinutesMinimum="getServiceMinutesMin"
|
||||
:estimatedServiceMinutesMaximum="getServiceMinutesMax"
|
||||
@timeSlotSelected="updateTimeSlot"
|
||||
:displayWaitList="displayWaitList"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:isSameDay="isSameDay"
|
||||
:selectedRouteCodeData="selectedRouteCodeData"
|
||||
@waitListRequested="handleWaitListRequested" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
|
|
@ -155,9 +175,10 @@
|
|||
import alert from "@/ux-components/alert/alert";
|
||||
import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question";
|
||||
import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question";
|
||||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
|
||||
import shopLocation from "@/layouts/schedule/shop-location/shop-location";
|
||||
import timeSlotQuestion from "@/layouts/schedule/time-slot-question/time-slot-question.vue";
|
||||
import durationTextBlock from "@/layouts/schedule/duration-text-block/duration-text-block.vue";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -622,6 +643,9 @@ export default {
|
|||
return this.isGlassServiceableMobile;
|
||||
}
|
||||
},
|
||||
showTimeSlotQuestion() {
|
||||
return this.selectedDate && this.appointmentTypeFromAppointmentTypeQuestion;
|
||||
},
|
||||
isMobileStaticRecalibrationApplicable() {
|
||||
return (
|
||||
this.displayMSR &&
|
||||
|
|
@ -781,6 +805,21 @@ export default {
|
|||
}
|
||||
return [];
|
||||
},
|
||||
selectedRouteCodeData() {
|
||||
console.log("1", JSON.stringify(this.selectedTimeSlotInfo));
|
||||
if (!this.selectedTimeSlotInfo?.timeSlot?.routeCode) {
|
||||
console.log("returning null");
|
||||
return null;
|
||||
}
|
||||
console.log("returning data");
|
||||
return {
|
||||
routeCode: this.selectedTimeSlotInfo.timeSlot.routeCode,
|
||||
isPremiumAppointment: this.selectedTimeSlotInfo.isPremiumAppointment,
|
||||
};
|
||||
},
|
||||
questionText() {
|
||||
return this.getCmsContent("YourSafeliteShopWidget", "QuestionText");
|
||||
},
|
||||
appointmentTypeStrings() {
|
||||
return AppointmentTypeStrings;
|
||||
},
|
||||
|
|
@ -791,12 +830,12 @@ export default {
|
|||
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
|
||||
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
|
||||
},
|
||||
getServiceMinutesMin() {
|
||||
estimatedServiceMinutesMinimum() {
|
||||
return this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMinimum
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMinimum;
|
||||
},
|
||||
getServiceMinutesMax() {
|
||||
estimatedServiceMinutesMaximum() {
|
||||
return this.isMobileSelected
|
||||
? this.selectableDatesMobile.estimatedServiceMinutesMaximum
|
||||
: this.selectableDatesInshop.estimatedServiceMinutesMaximum;
|
||||
|
|
@ -816,6 +855,18 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
isSameDay() {
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return this.selectedDate === todaysDate;
|
||||
},
|
||||
isOvernightDropoff() {
|
||||
return (
|
||||
this.selectedTimeSlotInfo?.timeSlot?.routeCode &&
|
||||
this.selectedTimeSlotInfo.timeSlot.routeCode.includes(
|
||||
RouteCodeFlags.OVERNIGHT_DROP_OFF
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
|
|
@ -1554,22 +1605,47 @@ export default {
|
|||
}
|
||||
},
|
||||
updateTimeSlot(timeSlotObj) {
|
||||
if (this.preSelectedDate && !timeSlotObj?.timeSlot?.date) return;
|
||||
this.selectedTimeSlotInfo = timeSlotObj;
|
||||
console.log("timeSlotObj", timeSlotObj);
|
||||
if (!timeSlotObj?.routeCode) {
|
||||
this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF;
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: null,
|
||||
routeCode: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
|
||||
(slot) => slot.id == timeSlotObj.routeCode
|
||||
);
|
||||
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: this.timeSlotsForSelectedDate.date,
|
||||
routeCode: timeSlotObj.routeCode,
|
||||
startTime: timeSlot.startTime,
|
||||
endTime: timeSlot.endTime,
|
||||
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
|
||||
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
|
||||
},
|
||||
isPremiumAppointment: timeSlotObj.isPremiumAppointment ? true : false,
|
||||
};
|
||||
|
||||
if (
|
||||
this.appointmentType !== AppointmentTypeStrings.MOBILE &&
|
||||
this.appointmentType !== null
|
||||
) {
|
||||
// update apptType based on routeCode to determine if it should be dropoff or inshop
|
||||
debugLog(`Updating appointment type based on route code.`);
|
||||
debugLog(`Route code =`, timeSlotObj.timeSlot.routeCode);
|
||||
debugLog(
|
||||
`Parsed type = `,
|
||||
this.getInShopOrDropOffApptType(timeSlotObj.timeSlot.routeCode)
|
||||
);
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(
|
||||
timeSlotObj.timeSlot.routeCode
|
||||
);
|
||||
debugLog(`Route code =`, timeSlotObj.routeCode);
|
||||
debugLog(`Parsed type = `, this.getInShopOrDropOffApptType(timeSlotObj.routeCode));
|
||||
this.appointmentType = this.getInShopOrDropOffApptType(timeSlotObj.routeCode);
|
||||
}
|
||||
},
|
||||
getInShopOrDropOffApptType(routeCode) {
|
||||
|
|
@ -1724,6 +1800,7 @@ export default {
|
|||
}
|
||||
},
|
||||
selectedTimeSlotInfo(newValue) {
|
||||
console.log("heard a change on selectedTimeslotInfo", newValue);
|
||||
this.updateFooterButtonText(newValue);
|
||||
},
|
||||
},
|
||||
|
|
@ -1736,10 +1813,13 @@ export default {
|
|||
datePicker,
|
||||
locationAlerts,
|
||||
textBlock,
|
||||
timeSlotQuestion,
|
||||
|
||||
alert,
|
||||
serviceZipModalQuestion,
|
||||
appointmentTypeQuestion,
|
||||
contentGroupModal,
|
||||
durationTextBlock,
|
||||
shopLocation,
|
||||
mobileFeeWaiverAlert,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<buttonQuestion
|
||||
v-if="isDropOffAppointmentAvailable"
|
||||
v-model="selectedAnswerForDropOffOrInshop"
|
||||
@update:modelValue="dropOffSelectionChanged"
|
||||
:questionText="dropOffOrPickATimeQuestionLabelText"
|
||||
:questionTextDescription="dropOffOrPickATimeQuestionDescription"
|
||||
:answers="answersForDropOffQuestion"
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
v-if="shouldDisplayTimeSlotQuestion"
|
||||
@update:modelValue="timeSlotSelectionChanged"
|
||||
buttonTypeString="timeSlotModalListButton"
|
||||
:buttonTypeObject="timeSlotModalListButton"
|
||||
class="mt-4 timeslots"
|
||||
|
|
@ -44,7 +46,7 @@
|
|||
v-if="supplementalInformationBlock"
|
||||
v-html="supplementalInformationBlock" />
|
||||
<div
|
||||
v-if="hasWaitListExperiment && displayWaitListFeature && selectedDate"
|
||||
v-if="hasWaitListExperiment && displayWaitListFeature"
|
||||
class="mt-5 bg-light rounded waitlist">
|
||||
<textBlock
|
||||
cmsWidgetName="WaitListLabelWidget"
|
||||
|
|
@ -59,7 +61,7 @@
|
|||
@click="waitListChecked" />
|
||||
</div>
|
||||
<div
|
||||
v-if="waitListRequested && displayWaitList && selectedDate"
|
||||
v-if="waitListRequested && displayWaitList"
|
||||
class="rounded waitlist-success"
|
||||
ref="waitlistSuccessMessage">
|
||||
<img :src="waitListSuccessImage" class="success-image" />
|
||||
|
|
@ -92,7 +94,6 @@ import {
|
|||
isDropOffRouteCode,
|
||||
militaryToTwelveHourTime,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||
|
||||
// Validation
|
||||
import { defineRule, useField } from "vee-validate";
|
||||
|
|
@ -116,23 +117,8 @@ defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
|
|||
|
||||
export default {
|
||||
name: "time-slot-question",
|
||||
emits: ["update:modelValue", "TimeSlotSelected", "click-event"],
|
||||
emits: ["update:modelValue", "time-slot-selection-changed"],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
timeSlot: {
|
||||
routeCode: null,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
}),
|
||||
},
|
||||
cmsWidgetName: String,
|
||||
mobileCmsWidgetName: String,
|
||||
mobilePremiumCmsWidgetName: String,
|
||||
dropoffCmsWidgetName: String,
|
||||
|
|
@ -142,21 +128,23 @@ export default {
|
|||
appointmentType: String,
|
||||
timeSlotsForSelectedDate: Object,
|
||||
premiumAppointmentFee: Object,
|
||||
estimatedServiceMinutesMinimum: Number,
|
||||
estimatedServiceMinutesMaximum: Number,
|
||||
validationRules: String,
|
||||
customComponentId: String,
|
||||
selectedDate: String,
|
||||
isSameDay: Boolean,
|
||||
displayWaitList: Boolean,
|
||||
selectedRouteCodeData: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedRouteCode: null,
|
||||
timeSlotModalListButton: timeSlotModalListButton,
|
||||
selectedAnswerForDropOffOrInshop: null,
|
||||
selectedAnswerForTimeSlots: null,
|
||||
selectedAnswerForDropOffOrInshop: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(this.selectedRouteCodeData);
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
const componentId = !props.customComponentId
|
||||
|
|
@ -186,20 +174,33 @@ export default {
|
|||
errors,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
waitListRequested(newVal) {
|
||||
if (newVal) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSuccessMessage();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectedRouteCodeData(newValue) {
|
||||
this.updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(newValue);
|
||||
},
|
||||
timeSlotsForSelectedDate() {
|
||||
this.selectedAnswerForDropOffOrInshop = null;
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
supplementalInformationBlock() {
|
||||
let appointmentTypeCmsWidgetName;
|
||||
|
||||
if (
|
||||
this.selectedDate == null ||
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF ||
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP ||
|
||||
this.appointmentType === AppointmentTypeStrings.DROP_OFF ||
|
||||
!this.availableTimeSlots
|
||||
) {
|
||||
if (!this.selectedAnswerForTimeSlots && !this.selectedAnswerForDropOffOrInshop) {
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
}
|
||||
return null;
|
||||
// This is planned to be used again for drop-off
|
||||
// Wrote this to be ready for that eventuality, is untested and no HTML work done yet
|
||||
|
|
@ -214,11 +215,7 @@ export default {
|
|||
// );
|
||||
// }
|
||||
} else {
|
||||
if (!this.selectedAnswerForTimeSlots && this.selectedRouteCode) {
|
||||
// Clearing selectedRouteCode if no time slot is selected
|
||||
this.resetSelectedTimeSlot();
|
||||
}
|
||||
appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes(
|
||||
appointmentTypeCmsWidgetName = this.selectedRouteCodeData?.routeCode?.includes(
|
||||
PREMIUM_TIME_SLOT_ID_FLAG
|
||||
)
|
||||
? this.mobilePremiumCmsWidgetName
|
||||
|
|
@ -299,13 +296,6 @@ export default {
|
|||
// return null;
|
||||
// }
|
||||
// },
|
||||
isSameDay() {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return false;
|
||||
}
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return this.selectedDate === todaysDate;
|
||||
},
|
||||
availableTimeSlots() {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return null;
|
||||
|
|
@ -407,29 +397,60 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
initializeComponent() {
|
||||
this.setSelectedRouteCodeFromParent();
|
||||
},
|
||||
async setSelectedTimeSlot() {
|
||||
this.selectedRouteCode =
|
||||
this.selectedAnswerForTimeSlots || this.selectedAnswerForDropOffOrInshop;
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
this.getSelectedTimeSlotInfoObject(this.selectedRouteCode)
|
||||
);
|
||||
},
|
||||
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
||||
selectedRouteCode,
|
||||
isSameDayRelevant = false
|
||||
) {
|
||||
if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
return this.overnightDropOffCmsWidgetName;
|
||||
} else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
return this.isSameDay && isSameDayRelevant
|
||||
? this.sameDayDropOffCmsWidgetName
|
||||
: this.dropoffCmsWidgetName;
|
||||
updateDropoffAndTimeSlotAnswersFromSelectedRouteCodeData(selectedRouteCodeData) {
|
||||
if (selectedRouteCodeData?.routeCode) {
|
||||
let routeCode = selectedRouteCodeData.routeCode;
|
||||
if (isDropOffRouteCode(routeCode)) {
|
||||
this.selectedAnswerForDropOffOrInshop = routeCode;
|
||||
} else {
|
||||
this.selectedAnswerForDropOffOrInshop = PICK_A_TIME_BUTTON_VALUE;
|
||||
if (this.selectedRouteCodeData.isPremiumAppointment) {
|
||||
routeCode = this.addPremiumFlagToInput(routeCode);
|
||||
}
|
||||
this.selectedAnswerForTimeSlots = routeCode;
|
||||
}
|
||||
} else {
|
||||
this.selectedAnswerForDropOffOrInshop = null;
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
}
|
||||
},
|
||||
dropOffSelectionChanged(newValue) {
|
||||
let newSelectedRouteCodeData = null;
|
||||
if (newValue && newValue != PICK_A_TIME_BUTTON_VALUE) {
|
||||
newSelectedRouteCodeData = {
|
||||
routeCode: newValue,
|
||||
isPremiumAppointment: false,
|
||||
};
|
||||
}
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
this.$emit("time-slot-selection-changed", newSelectedRouteCodeData);
|
||||
},
|
||||
timeSlotSelectionChanged(newValue) {
|
||||
if (newValue) {
|
||||
let routeCode = newValue;
|
||||
let isPremiumAppointment = routeCode.includes(PREMIUM_TIME_SLOT_ID_FLAG);
|
||||
if (isPremiumAppointment) {
|
||||
routeCode = this.removePremiumFlagFromInput(routeCode);
|
||||
}
|
||||
this.$emit("time-slot-selection-changed", {
|
||||
routeCode: routeCode,
|
||||
isPremiumAppointment: isPremiumAppointment,
|
||||
});
|
||||
}
|
||||
},
|
||||
// This is unused but planned to be used again
|
||||
// getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
||||
// selectedRouteCode,
|
||||
// isSameDayRelevant = false
|
||||
// ) {
|
||||
// if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
// return this.overnightDropOffCmsWidgetName;
|
||||
// } else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
// return this.isSameDay && isSameDayRelevant
|
||||
// ? this.sameDayDropOffCmsWidgetName
|
||||
// : this.dropoffCmsWidgetName;
|
||||
// }
|
||||
// },
|
||||
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
||||
return timeSlotsForSelectedDate
|
||||
.filter((timeSlot) => {
|
||||
|
|
@ -463,8 +484,6 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
this.resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate);
|
||||
|
||||
return availableTimeSlots;
|
||||
},
|
||||
getPremiumAppointmentTimeSlot(timeSlotData) {
|
||||
|
|
@ -480,35 +499,19 @@ export default {
|
|||
},
|
||||
};
|
||||
},
|
||||
setSelectedRouteCodeFromParent() {
|
||||
if (!this.modelValue?.timeSlot?.routeCode) {
|
||||
return;
|
||||
}
|
||||
const routeCodeFromParent = this.modelValue.timeSlot.routeCode;
|
||||
if (this.modelValue?.isPremiumAppointment) {
|
||||
// Mobile Only
|
||||
this.selectedAnswerForTimeSlots = this.addPremiumFlagToInput(routeCodeFromParent);
|
||||
} else {
|
||||
if (isDropOffRouteCode(routeCodeFromParent)) {
|
||||
this.selectedAnswerForDropOffOrInshop = routeCodeFromParent;
|
||||
} else {
|
||||
this.selectedAnswerForDropOffOrInshop = PICK_A_TIME_BUTTON_VALUE;
|
||||
this.selectedAnswerForTimeSlots = routeCodeFromParent;
|
||||
}
|
||||
}
|
||||
},
|
||||
getWaitListRequestedFromStore() {
|
||||
return store.getters.order.customer?.waitListRequested;
|
||||
},
|
||||
autoSelectTimeSlotIfOnlyOneIsAvailable() {
|
||||
const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length;
|
||||
if (this.appointmentType && numberOfOptions === 1) {
|
||||
if (numberOfOptions === 1) {
|
||||
if (this.availableTimeSlots.length > 0) {
|
||||
// this.availableTimeSlots only returns mobile/inshop slots so we know
|
||||
// the only available slot is not dropOFf
|
||||
this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value;
|
||||
} else {
|
||||
this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value;
|
||||
}
|
||||
this.setSelectedTimeSlot();
|
||||
}
|
||||
},
|
||||
addPremiumFlagToInput(routeCode) {
|
||||
|
|
@ -517,41 +520,6 @@ export default {
|
|||
removePremiumFlagFromInput(routeCode) {
|
||||
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, "");
|
||||
},
|
||||
getSelectedTimeSlotInfoObject(routeCode) {
|
||||
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
|
||||
if (routeCodeIncludesPremium) {
|
||||
routeCode = this.removePremiumFlagFromInput(routeCode);
|
||||
}
|
||||
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
|
||||
(slot) => slot.id == routeCode
|
||||
);
|
||||
|
||||
if (timeSlot) {
|
||||
return {
|
||||
timeSlot: {
|
||||
date: this.timeSlotsForSelectedDate.date,
|
||||
routeCode: timeSlot.id,
|
||||
startTime: timeSlot.startTime,
|
||||
endTime: timeSlot.endTime,
|
||||
jobMaxMinutes: this.estimatedServiceMinutesMaximum?.toString() || null,
|
||||
jobMinMinutes: this.estimatedServiceMinutesMinimum?.toString() || null,
|
||||
},
|
||||
isPremiumAppointment: routeCodeIncludesPremium ? true : false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
timeSlot: {
|
||||
date: null,
|
||||
routeCode: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
},
|
||||
waitListChecked(event) {
|
||||
this.$emit("waitListRequested", event.target.checked);
|
||||
},
|
||||
|
|
@ -561,65 +529,6 @@ export default {
|
|||
successMessageElement.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
},
|
||||
resetSelectedTimeSlotIfDateChange(timeSlotsForSelectedDate) {
|
||||
if (this.selectedRouteCode) {
|
||||
const selectedRouteCodeString = this.selectedRouteCode.replace(
|
||||
PREMIUM_TIME_SLOT_ID_FLAG,
|
||||
""
|
||||
);
|
||||
const matchingTimeSlot = timeSlotsForSelectedDate.find((slot) => {
|
||||
return slot.id === selectedRouteCodeString;
|
||||
});
|
||||
if (!matchingTimeSlot) {
|
||||
this.resetSelectedTimeSlot();
|
||||
}
|
||||
}
|
||||
},
|
||||
resetSelectedTimeSlot() {
|
||||
this.selectedRouteCode = null;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
waitListRequested(newVal) {
|
||||
if (newVal) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSuccessMessage();
|
||||
});
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
handler(newValue) {
|
||||
this.handleChange(newValue);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
selectedDate: {
|
||||
handler() {
|
||||
this.resetSelectedTimeSlot();
|
||||
this.selectedAnswerForDropOffOrInshop = null;
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
},
|
||||
},
|
||||
selectedRouteCode(newValue) {
|
||||
if (newValue) {
|
||||
this.setSelectedTimeSlot();
|
||||
}
|
||||
},
|
||||
selectedAnswerForDropOffOrInshop(newValue) {
|
||||
if (newValue == PICK_A_TIME_BUTTON_VALUE) {
|
||||
this.selectedRouteCode = null;
|
||||
this.setSelectedTimeSlot();
|
||||
} else {
|
||||
this.selectedAnswerForTimeSlots = null;
|
||||
this.selectedRouteCode = newValue;
|
||||
}
|
||||
},
|
||||
selectedAnswerForTimeSlots(newValue) {
|
||||
if (newValue) {
|
||||
this.selectedRouteCode = newValue;
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textBlock,
|
||||
|
|
|
|||
Loading…
Reference in a new issue