CSR-1137 | All timeslot implementations working
This commit is contained in:
parent
6ca3d916ba
commit
23711787dd
10 changed files with 343 additions and 52 deletions
|
|
@ -103,6 +103,11 @@ const endpoints = {
|
||||||
mockUrl: "https://mockey.qa.sagaws.net/service/shop-time-slots", // TODO: REMOVE MOCKURL
|
mockUrl: "https://mockey.qa.sagaws.net/service/shop-time-slots", // TODO: REMOVE MOCKURL
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
GetMobileEarlyBirdFee: {
|
||||||
|
url: "/parts/api/v1/parts/mobile-early-bird-fee",
|
||||||
|
mockUrl: "https://mockey.qa.sagaws.net/service/mobile-early-bird-fee", // TODO: REMOVE MOCKURL
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
SaveSession: {
|
SaveSession: {
|
||||||
url: "/order/api/v1/order/save-session",
|
url: "/order/api/v1/order/save-session",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const storeActions = {
|
||||||
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
||||||
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
||||||
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
||||||
|
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
|
||||||
SAVE_SESSION: "saveSession",
|
SAVE_SESSION: "saveSession",
|
||||||
LOAD_SESSION: "loadSession",
|
LOAD_SESSION: "loadSession",
|
||||||
UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse",
|
UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse",
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ export default {
|
||||||
getComponentLoopWrapperClasses() {
|
getComponentLoopWrapperClasses() {
|
||||||
let classes;
|
let classes;
|
||||||
switch (this.buttonTypeString) {
|
switch (this.buttonTypeString) {
|
||||||
|
case "timeSlotModalListButton":
|
||||||
case "listButton":
|
case "listButton":
|
||||||
classes = "w-100";
|
classes = "w-100";
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,6 @@ export default {
|
||||||
onModalClosedCallback: {
|
onModalClosedCallback: {
|
||||||
type: Function,
|
type: Function,
|
||||||
},
|
},
|
||||||
allowManualFooterDisableOverride: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isFooterDisabledManualOverride: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const modalId = `modal-${crypto.randomUUID()}`;
|
const modalId = `modal-${crypto.randomUUID()}`;
|
||||||
|
|
@ -105,13 +98,10 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isFooterButtonDisabled() {
|
isFooterButtonDisabled() {
|
||||||
if (this.allowManualFooterDisableOverride !== null) {
|
if (!this.meta.touched) {
|
||||||
return this.isFooterDisabledManualOverride;
|
|
||||||
} else if (!this.meta.touched) {
|
|
||||||
return !this.meta.valid;
|
return !this.meta.valid;
|
||||||
} else {
|
|
||||||
return !this.meta.dirty || !this.meta.valid;
|
|
||||||
}
|
}
|
||||||
|
return !this.meta.dirty || !this.meta.valid;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="text-block w-100 mt-2 d-flex"
|
class="text-block w-100 mt-2"
|
||||||
:class="[justifyText, typeStyle, fontWeight]"
|
:class="[justifyText, typeStyle, fontWeight]"
|
||||||
v-html="this.TextBlockCopy"></div>
|
v-html="this.TextBlockCopy"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -28,6 +28,7 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.text-block {
|
.text-block {
|
||||||
|
display: flex;
|
||||||
&.left {
|
&.left {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,20 @@
|
||||||
<time-slot-modal-question
|
<time-slot-modal-question
|
||||||
ref="timeSlotModalQuestion"
|
ref="timeSlotModalQuestion"
|
||||||
cmsWidgetName="TimeSlotModalQuestion"
|
cmsWidgetName="TimeSlotModalQuestion"
|
||||||
|
earlyBirdCmsWidgetName="EarlyBirdTimeSlotModal"
|
||||||
|
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||||
|
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||||
v-model="selectedTimeSlotId"
|
v-model="selectedTimeSlotId"
|
||||||
:dateAndTimeSlotData="TimeSlotsForSelectedDate"
|
:appointmentType="appointmentType"
|
||||||
|
:mobileEarlyBirdFee="mobileEarlyBirdFee"
|
||||||
|
:dateAndTimeSlotData="timeSlotsForSelectedDate"
|
||||||
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
||||||
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"/>
|
:estimatedServiceMinutesMaximum="
|
||||||
|
selectableDatesData.estimatedServiceMinutesMaximum"
|
||||||
|
validationRules="time-slot-selection-required"/>
|
||||||
|
<button @click="setAppointmentType('Inshop')">Set to Inshop</button>
|
||||||
|
<button @click="setAppointmentType('Mobile')">Set to Mobile</button>
|
||||||
|
<button @click="setAppointmentType('Dropoff')">Set to Dropoff</button>
|
||||||
<button @click="openInshopTimeSlotsModal">Click for timeslots</button>
|
<button @click="openInshopTimeSlotsModal">Click for timeslots</button>
|
||||||
<funnel-footer
|
<funnel-footer
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
|
@ -67,6 +77,7 @@ import { errorMessages } from "@/constants/error-messages";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
|
||||||
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
||||||
|
defineRule("time-slot-selection-required", required(errorMessages.DATE_REQUIRED));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "schedule",
|
name: "schedule",
|
||||||
|
|
@ -75,11 +86,27 @@ export default {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
selectedTimeSlotId: null,
|
selectedTimeSlotId: null,
|
||||||
selectableDatesData: [],
|
selectableDatesData: [],
|
||||||
|
mobileEarlyBirdFee: null,
|
||||||
|
TEMPORARYappointmentType: "Inshop",
|
||||||
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
const earlyBirdPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.GET_MOBILE_EARLY_BIRD_FEE
|
||||||
|
);
|
||||||
|
// Price EARLY BIRD pre-emptively to allow for asynchronous call to pricing
|
||||||
|
// const pricingPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
|
// storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
|
// {
|
||||||
|
// availableLineItems: [
|
||||||
|
// {partNumber: "EARLY BIRD"}
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// false
|
||||||
|
// );
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
|
|
@ -87,6 +114,14 @@ export default {
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resultKey: "earlyBird",
|
||||||
|
promise: earlyBirdPromise,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// resultKey: "pricingResults",
|
||||||
|
// promise: pricingPromise,
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
@ -94,6 +129,13 @@ export default {
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
vm.mobileEarlyBirdFee = resultMap.earlyBird;
|
||||||
|
vm.mobileEarlyBirdFee.laborAmount = 15;
|
||||||
|
vm.mobileEarlyBirdFee.sellingPrice = 0;
|
||||||
|
vm.mobileEarlyBirdFee.kitPrice = 0;
|
||||||
|
// if (resultMap.earlyBird) {
|
||||||
|
// vm.mobileEarlyBirdFee = resultMap.pricingResults;
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -104,17 +146,25 @@ 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
|
// 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);
|
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
|
||||||
},
|
},
|
||||||
TimeSlotsForSelectedDate() {
|
appointmentType() {
|
||||||
|
return this.TEMPORARYappointmentType;
|
||||||
|
//return this.$store.getters.order.serviceLocation.appointmentType;
|
||||||
|
},
|
||||||
|
timeSlotsForSelectedDate() {
|
||||||
if (this.selectedDate === null) {
|
if (this.selectedDate === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.selectableDatesData.find(selectableDate => selectableDate.dateString === this.selectedDate.dateString);
|
return this.selectableDatesData.find(
|
||||||
|
(selectableDate) => selectableDate.dateString === this.selectedDate.dateString
|
||||||
|
);
|
||||||
},
|
},
|
||||||
appointmentDateAndTime() {
|
appointmentDateAndTime() {
|
||||||
if (!this.selectedTimeSlotId) {
|
if (!this.selectedTimeSlotId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId(this.selectedTimeSlotId);
|
const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId(
|
||||||
|
this.selectedTimeSlotId
|
||||||
|
);
|
||||||
if (timeSlotSelectedObject) {
|
if (timeSlotSelectedObject) {
|
||||||
return {
|
return {
|
||||||
date: this.selectedDate.dateString,
|
date: this.selectedDate.dateString,
|
||||||
|
|
@ -125,7 +175,6 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -138,6 +187,7 @@ export default {
|
||||||
// NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE?
|
// NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE?
|
||||||
},
|
},
|
||||||
async getAvailableDates(startDate, endDate) {
|
async getAvailableDates(startDate, endDate) {
|
||||||
|
console.log('calling getAvailableDates', startDate, endDate);
|
||||||
// USING DATES PASSED, MAKE AN API CALL
|
// USING DATES PASSED, MAKE AN API CALL
|
||||||
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.GET_SHOP_TIME_SLOTS,
|
storeActions.GET_SHOP_TIME_SLOTS,
|
||||||
|
|
@ -151,13 +201,14 @@ export default {
|
||||||
const newShopTimeSlots = newShopTimeSlotsResponse.data;
|
const newShopTimeSlots = newShopTimeSlotsResponse.data;
|
||||||
// console.log("newShopTimeSlots ", newShopTimeSlots)
|
// console.log("newShopTimeSlots ", newShopTimeSlots)
|
||||||
|
|
||||||
|
|
||||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||||
this.selectableDatesData = this.selectableDatesData.concat(
|
this.selectableDatesData = this.selectableDatesData.concat(
|
||||||
this.convertApiResponse(newShopTimeSlots.days)
|
this.convertApiResponse(newShopTimeSlots.days)
|
||||||
);
|
);
|
||||||
this.selectableDatesData.estimatedServiceMinutesMaximum = newShopTimeSlots.estimatedServiceMinutesMaximum;
|
this.selectableDatesData.estimatedServiceMinutesMaximum =
|
||||||
this.selectableDatesData.estimatedServiceMinutesMinimum = newShopTimeSlots.estimatedServiceMinutesMinimum;
|
newShopTimeSlots.estimatedServiceMinutesMaximum;
|
||||||
|
this.selectableDatesData.estimatedServiceMinutesMinimum =
|
||||||
|
newShopTimeSlots.estimatedServiceMinutesMinimum;
|
||||||
console.log("this.selectableDatesData is now: ", this.selectableDatesData);
|
console.log("this.selectableDatesData is now: ", this.selectableDatesData);
|
||||||
|
|
||||||
// RETURN AGGREGATE DATE DATA
|
// RETURN AGGREGATE DATE DATA
|
||||||
|
|
@ -175,12 +226,17 @@ export default {
|
||||||
});
|
});
|
||||||
return responseData;
|
return responseData;
|
||||||
},
|
},
|
||||||
|
setAppointmentType(appointmentType) {
|
||||||
|
this.TEMPORARYappointmentType = appointmentType;
|
||||||
|
},
|
||||||
openInshopTimeSlotsModal() {
|
openInshopTimeSlotsModal() {
|
||||||
this.$refs["timeSlotModalQuestion"].openModal();
|
this.$refs["timeSlotModalQuestion"].openModal();
|
||||||
},
|
},
|
||||||
getTimeSlotObjectFromTimeSlotId(timeSlotId) {
|
getTimeSlotObjectFromTimeSlotId(timeSlotId) {
|
||||||
const timeSlots = this.selectableDatesData.find(selectableDate => selectableDate.dateString === this.selectedDate.dateString).timeSlots;
|
const timeSlots = this.selectableDatesData.find(
|
||||||
return timeSlots.find(timeSlot => timeSlot.id === timeSlotId);
|
(selectableDate) => selectableDate.dateString === this.selectedDate.dateString
|
||||||
|
).timeSlots;
|
||||||
|
return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId);
|
||||||
},
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,32 @@
|
||||||
class="time-slots-modal"
|
class="time-slots-modal"
|
||||||
:headerText="dateSelectedReadableDate"
|
:headerText="dateSelectedReadableDate"
|
||||||
:footerButtonText="footerCloseButtonText"
|
:footerButtonText="footerCloseButtonText"
|
||||||
allowManualFooterDisableOverride
|
|
||||||
:isFooterDisabledManualOverride="selectedTimeSlot === null"
|
|
||||||
:onModalOpenedCallback="onModalOpened"
|
:onModalOpenedCallback="onModalOpened"
|
||||||
:onModalClosedCallback="onModalClosed"
|
:onModalClosedCallback="onModalClosed"
|
||||||
@footer-button-event="closeModal">
|
@footer-button-event="closeModal">
|
||||||
<textBlock
|
<textBlock
|
||||||
:customText="appointmentDurationTextWithTime"
|
v-show="inshopAppointmentDurationTextWithTime"
|
||||||
|
:customText="inshopAppointmentDurationTextWithTime"
|
||||||
justifyText="center"
|
justifyText="center"
|
||||||
typeStyle="small"
|
typeStyle="small"
|
||||||
class="mb-5" />
|
class="duration-text-block" />
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
class="radioQuestion"
|
buttonTypeString="timeslotModalListButton"
|
||||||
|
:buttonTypeObject="timeslotModalListButton"
|
||||||
:answers="availableTimeSlots"
|
:answers="availableTimeSlots"
|
||||||
groupName="ChooseTimeSlot"
|
groupName="ChooseTimeSlot"
|
||||||
textPosition="text-center"
|
textPosition="text-center"
|
||||||
v-model="selectedTimeSlot"
|
v-model="selectedTimeSlot"
|
||||||
isRequired />
|
isRequired
|
||||||
|
validationRules="time-slot-required"
|
||||||
|
class="mt-5"/>
|
||||||
|
<div class="mt-1 mb-2" v-if="supplementalInformationBlock" v-html="supplementalInformationBlock"></div>
|
||||||
|
<textBlock
|
||||||
|
v-show="shouldShowDropoffDisclaimerText"
|
||||||
|
:customText="dropoffDisclaimerText"
|
||||||
|
justifyText="left"
|
||||||
|
typeStyle="caption"
|
||||||
|
class="mb-2" />
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -29,54 +38,116 @@
|
||||||
import modal from "@/digital-components/modal/modal";
|
import modal from "@/digital-components/modal/modal";
|
||||||
import textBlock from "@/digital-components/text-block/text-block";
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
|
import timeslotModalListButton from "./timeslot-modal-list-button/timeslot-modal-list-button";
|
||||||
|
|
||||||
import { daysOfWeek, monthsOfYear } from "@/constants/scheduling.js";
|
import { daysOfWeek, monthsOfYear } from "@/constants/scheduling.js";
|
||||||
|
import {defineRule, useField} from "vee-validate";
|
||||||
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
|
||||||
|
// Validation for the modal button
|
||||||
|
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const AppointmentTypeStrings = {
|
||||||
|
IN_SHOP: "Inshop",
|
||||||
|
MOBILE: "Mobile",
|
||||||
|
DROP_OFF: "Dropoff"
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "timeSlotModalQuestion",
|
name: "timeSlotModalQuestion",
|
||||||
props: {
|
props: {
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
mobileCmsWidgetName: String,
|
||||||
|
earlyBirdCmsWidgetName: String,
|
||||||
|
dropoffCmsWidgetName: String,
|
||||||
|
appointmentType: String,
|
||||||
dateAndTimeSlotData: Object,
|
dateAndTimeSlotData: Object,
|
||||||
|
mobileEarlyBirdFee: Object,
|
||||||
estimatedServiceMinutesMinimum: Number,
|
estimatedServiceMinutesMinimum: Number,
|
||||||
estimatedServiceMinutesMaximum: Number,
|
estimatedServiceMinutesMaximum: Number,
|
||||||
|
validationRules: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTimeSlot: null,
|
selectedTimeSlot: null,
|
||||||
|
timeslotModalListButton: timeslotModalListButton,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { handleChange } =
|
||||||
|
useField("time-slot-modal-question", props.validationRules);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleChange
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue() {
|
modelValue() {
|
||||||
this.selectedTimeSlot = this.modelValue;
|
this.selectedTimeSlot = this.modelValue;
|
||||||
|
this.handleChange(this.modelValue);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
appointmentDurationTextFromCms() {
|
appointmentDurationTextFromCms() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||||
},
|
},
|
||||||
|
supplementalInformationBlock() {
|
||||||
|
let appointmentTypeCmsWidgetName;
|
||||||
|
let cmsFieldName = "BodyText";
|
||||||
|
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||||
|
return null;
|
||||||
|
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
appointmentTypeCmsWidgetName = this.selectedTimeSlot === this.earlyBirdButtonText ? this.earlyBirdCmsWidgetName : this.mobileCmsWidgetName;
|
||||||
|
} else {
|
||||||
|
appointmentTypeCmsWidgetName = this.dropoffCmsWidgetName;
|
||||||
|
if (this.isSameDay) {
|
||||||
|
cmsFieldName = "BodyText2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.getCmsContent(appointmentTypeCmsWidgetName, cmsFieldName);
|
||||||
|
},
|
||||||
footerCloseButtonText() {
|
footerCloseButtonText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||||
},
|
},
|
||||||
|
earlyBirdButtonText() {
|
||||||
|
return this.getCmsContent(this.earlyBirdCmsWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
dropoffButtonText() {
|
||||||
|
return this.getCmsContent(this.dropoffCmsWidgetName, "HeaderText");
|
||||||
|
},
|
||||||
|
dropoffDisclaimerText() {
|
||||||
|
return this.getCmsContent(this.dropoffCmsWidgetName, "FooterText");
|
||||||
|
},
|
||||||
|
shouldShowDropoffDisclaimerText() {
|
||||||
|
return this.appointmentType === AppointmentTypeStrings.DROP_OFF && !this.isSameDay;
|
||||||
|
},
|
||||||
|
isSameDay() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
dateSelectedReadableDate() {
|
dateSelectedReadableDate() {
|
||||||
if (this.dateAndTimeSlotData === null) {
|
if (this.dateAndTimeSlotData === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// This conversion ensures we don't get get GMT induced date changes
|
// This conversion ensures we don't get get GMT induced date changes
|
||||||
const dateObject = new Date (`${this.dateAndTimeSlotData.dateString}T00:00:00`);
|
const dateObject = new Date(`${this.dateAndTimeSlotData.dateString}T00:00:00`);
|
||||||
const weekdayName = daysOfWeek[dateObject.getDay()];
|
const weekdayName = daysOfWeek[dateObject.getDay()];
|
||||||
const month = monthsOfYear[dateObject.getMonth()];
|
const month = monthsOfYear[dateObject.getMonth()];
|
||||||
const numberDayOfMonth = dateObject.getDate();
|
const numberDayOfMonth = dateObject.getDate();
|
||||||
// Ex. Tuesday, April 23
|
// Ex. Tuesday, April 23
|
||||||
return `${weekdayName}, ${month} ${numberDayOfMonth}`;
|
return `${weekdayName}, ${month} ${numberDayOfMonth}`;
|
||||||
},
|
},
|
||||||
appointmentDurationTextWithTime() {
|
inshopAppointmentDurationTextWithTime() {
|
||||||
|
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let durationSubHeaderText = this.appointmentDurationTextFromCms + " ";
|
let durationSubHeaderText = this.appointmentDurationTextFromCms + " ";
|
||||||
if (this.estimatedServiceMinutesMaximum >= 120) {
|
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||||
durationSubHeaderText += `${this.estimatedServiceMinutesMinimum / 60} - ${
|
durationSubHeaderText += "All day";
|
||||||
this.estimatedServiceMinutesMaximum / 60} hours`;
|
|
||||||
} else {
|
} else {
|
||||||
durationSubHeaderText += `${this.estimatedServiceMinutesMinimum} - ${this.estimatedServiceMinutesMaximum} minutes`
|
durationSubHeaderText += this.getDisplayTextForDurationLength(this.estimatedServiceMinutesMinimum, this.estimatedServiceMinutesMaximum);
|
||||||
}
|
}
|
||||||
return durationSubHeaderText;
|
return durationSubHeaderText;
|
||||||
},
|
},
|
||||||
|
|
@ -84,20 +155,33 @@ export default {
|
||||||
if (this.dateAndTimeSlotData === null) {
|
if (this.dateAndTimeSlotData === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.dateAndTimeSlotData.timeSlots.map(timeslot => {
|
let availableTimeSlots;
|
||||||
let hours = timeslot.startTime.split(":")[0];
|
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||||
let minutes = timeslot.startTime.split(":")[1];
|
availableTimeSlots = [{value: "Dropoff", buttonLabel: this.dropoffButtonText}]
|
||||||
let meridianNotation = "AM";
|
} else {
|
||||||
if (timeslot.startTime.split(":")[0] > 12) {
|
availableTimeSlots = this.dateAndTimeSlotData.timeSlots.map((timeslot) => {
|
||||||
hours -= 12;
|
let readableTime;
|
||||||
meridianNotation = "PM";
|
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||||
|
readableTime = this.getDisplayTextForMilitaryTime(timeslot.startTime);
|
||||||
|
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
readableTime = `${this.getDisplayTextForMilitaryTime(timeslot.startTime)} - ${this.getDisplayTextForMilitaryTime(timeslot.endTime)}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: timeslot.id,
|
||||||
|
buttonLabel: readableTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
|
const offerPremium = this.dateAndTimeSlotData.timeSlots[0].offerPremium;
|
||||||
|
const hasEarlyBird = this.mobileEarlyBirdFee?.partType === "EARLY BIRD";
|
||||||
|
if (offerPremium && hasEarlyBird) {
|
||||||
|
availableTimeSlots.unshift({value: this.dateAndTimeSlotData.timeSlots[0].id + "-earlybird", buttonLabel: this.earlyBirdButtonText, buttonLabelSubCopy: this.getTotalLineItemPrice(this.mobileEarlyBirdFee)});
|
||||||
}
|
}
|
||||||
let readableTime = `${hours}:${minutes} ${meridianNotation}`;
|
}
|
||||||
return {
|
return availableTimeSlots;
|
||||||
value: timeslot.id,
|
|
||||||
buttonLabel: readableTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -113,6 +197,28 @@ export default {
|
||||||
onModalClosed() {
|
onModalClosed() {
|
||||||
this.selectedTimeSlot = this.modelValue;
|
this.selectedTimeSlot = this.modelValue;
|
||||||
},
|
},
|
||||||
|
// Expected input: "HH:MM:SS"
|
||||||
|
getDisplayTextForMilitaryTime(militaryTimeInput) {
|
||||||
|
let hours = militaryTimeInput.split(":")[0];
|
||||||
|
const minutes = militaryTimeInput.split(":")[1];
|
||||||
|
let meridianNotation = "AM";
|
||||||
|
if (militaryTimeInput.split(":")[0] > 12) {
|
||||||
|
hours -= 12;
|
||||||
|
meridianNotation = "PM";
|
||||||
|
}
|
||||||
|
return `${hours}:${minutes} ${meridianNotation}`;
|
||||||
|
},
|
||||||
|
getDisplayTextForDurationLength(durationMinimum, durationMaximum) {
|
||||||
|
let displayTextForDurationLength;
|
||||||
|
if (durationMaximum >= 120) {
|
||||||
|
displayTextForDurationLength = `${durationMinimum / 60} - ${
|
||||||
|
durationMaximum / 60
|
||||||
|
} hours`;
|
||||||
|
} else {
|
||||||
|
displayTextForDurationLength = `${durationMinimum} - ${durationMaximum} minutes`;
|
||||||
|
}
|
||||||
|
return displayTextForDurationLength;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
modal,
|
modal,
|
||||||
|
|
@ -131,7 +237,7 @@ export default {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
.text-block {
|
.text-block.duration-text-block {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
<template>
|
||||||
|
<baseInputButton
|
||||||
|
v-bind="$props"
|
||||||
|
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||||
|
v-model="selectedValue">
|
||||||
|
<div
|
||||||
|
:aria-label="buttonLabel"
|
||||||
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
|
<span class="m-0" :class="textPosition">
|
||||||
|
{{ buttonLabel }}
|
||||||
|
</span>
|
||||||
|
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">
|
||||||
|
{{ formattedButtonLabelSubCopy }}
|
||||||
|
</span>
|
||||||
|
<span v-if="screenReaderOnlyText" class="sr-only">
|
||||||
|
{{ screenReaderOnlyText }}
|
||||||
|
</span>
|
||||||
|
<loader
|
||||||
|
v-if="isLoaderDisplayed && selectingInitiatesLoad"
|
||||||
|
:class="[this.loaderColor, this.loaderPosition]" />
|
||||||
|
</div>
|
||||||
|
</baseInputButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import loader from "@/ux-components/loader/loader";
|
||||||
|
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "timeslotMOdalListButton",
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
props: {
|
||||||
|
loaderColor: String,
|
||||||
|
loaderPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "right",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaderDisplayed: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formattedButtonLabelSubCopy() {
|
||||||
|
return this.buttonLabelSubCopy?.toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
displayLoader() {
|
||||||
|
this.isLoaderDisplayed = true;
|
||||||
|
},
|
||||||
|
preHandleAnswerChange() {
|
||||||
|
if (this.selectingInitiatesLoad) {
|
||||||
|
this.displayLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
loader,
|
||||||
|
baseInputButton,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.loader {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.list-button {
|
||||||
|
outline: none;
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
position: static; //override bootstrap
|
||||||
|
|
||||||
|
&:focus-visible + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:focus + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 500;
|
||||||
|
background: $blue-100;
|
||||||
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
}
|
||||||
|
&:checked:focus + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content p,
|
||||||
|
&:checked + .list-button-content span {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content span:nth-child(2) {
|
||||||
|
font-weight: 400;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list-button-content {
|
||||||
|
color: $gray-600;
|
||||||
|
position: relative;
|
||||||
|
background: $white;
|
||||||
|
transition: all 150ms linear;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
&.small {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $gray-550;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -16,6 +16,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setCmsContent(cmsContent) {
|
setCmsContent(cmsContent) {
|
||||||
|
console.log(cmsContent);
|
||||||
this.$root.cmsContentByWidget = cmsContent;
|
this.$root.cmsContentByWidget = cmsContent;
|
||||||
},
|
},
|
||||||
getCmsContent(widgetName, fieldName) {
|
getCmsContent(widgetName, fieldName) {
|
||||||
|
|
|
||||||
|
|
@ -1033,7 +1033,18 @@ export const actions = {
|
||||||
// logApiCall: false,
|
// logApiCall: false,
|
||||||
// });
|
// });
|
||||||
},
|
},
|
||||||
|
getMobileEarlyBirdFee(context) {
|
||||||
|
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||||
|
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
|
||||||
|
return globalMethods.callMockHttpClient({
|
||||||
|
method: endpoints.GetMobileEarlyBirdFee.method,
|
||||||
|
endpoint: `${endpoints.GetMobileEarlyBirdFee.mockUrl}/Cash/Replace`,
|
||||||
|
});
|
||||||
|
// return globalMethods.callHttpClient({
|
||||||
|
// method: endpoints.GetMobileEarlyBirdFee.method,
|
||||||
|
// endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`,
|
||||||
|
// });
|
||||||
|
},
|
||||||
// Session API Actions
|
// Session API Actions
|
||||||
saveSession(context) {
|
saveSession(context) {
|
||||||
const vehicle = context.getters.vehicle;
|
const vehicle = context.getters.vehicle;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue