diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue
index e40dd13ba..6045d3a4c 100644
--- a/src/layouts/schedule/schedule.vue
+++ b/src/layouts/schedule/schedule.vue
@@ -11,24 +11,24 @@
class="mb-3 text-link-small"
marginTopSizeOverride="1" />
-
-
+
- {
+ if (value.date == null ||
+ value.startTime == null ||
+ value.endTime == null ||
+ value.routeCode == null ||
+ value.estimatedServiceMinutesMaximum) {
+ return errorMessages.DATE_REQUIRED;
+ }
+ return true;
+});
// Define constants
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
@@ -171,10 +182,7 @@ export default {
data() {
return {
selectedDate: this.getSelectedDate(),
- selectedTimeSlotData: {
- id: this.getSelectedRouteCode(),
- isPremiumAppointment: this.isMobilePremiumFeeOnOrderInVuex(),
- },
+ selectedTimeSlot: this.getSelectedTimeSlot(),
selectableDatesData: [],
mobilePremiumAppointmentFee: null,
};
@@ -217,6 +225,7 @@ export default {
store.getters.order.serviceLocation.zipCodeCtu,
store.getters.order.serviceLocation.provider?.address?.zipCodeCtu
);
+
// Settle promises and get results
const promiseResultMap = [
{
@@ -248,7 +257,7 @@ export default {
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
? resultMap.premiumFeeWithPrice[0]
: null;
- vm.updateFooterButtonText(vm.selectedTimeSlotData);
+ vm.updateFooterButtonText(vm.selectedTimeSlot);
});
},
computed: {
@@ -266,30 +275,11 @@ export default {
if (!this.selectedDate) {
return null;
}
+
return this.selectableDatesData.days?.find(
(selectableDate) => selectableDate.date === this.selectedDate
);
},
- appointmentDateAndTime() {
- if (!this.selectedTimeSlotData.id) {
- return null;
- }
- const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId(
- this.selectedTimeSlotData.id
- );
- if (timeSlotSelectedObject) {
- return {
- date: this.selectedDate,
- startTime: timeSlotSelectedObject.startTime,
- endTime: timeSlotSelectedObject.endTime,
- routeCode: this.selectedTimeSlotData.id,
- jobMaxMinutes:
- this.selectableDatesData.estimatedServiceMinutesMaximum.toString(),
- };
- } else {
- return null;
- }
- },
},
methods: {
splitCopyOnCMSPlaceHolder,
@@ -328,11 +318,8 @@ export default {
openInshopTimeSlotsModal() {
this.$refs["timeSlotModalQuestion"].openModal();
},
- getTimeSlotObjectFromTimeSlotId(timeSlotId) {
- const timeSlots = this.selectableDatesData.days.find(
- (selectableDate) => selectableDate.date === this.selectedDate
- )?.timeSlots;
- if (timeSlots) return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId);
+ getSelectedTimeSlot() {
+ return store.getters.order.schedule;
},
getSelectedDate() {
return store.getters.order.schedule.date;
@@ -340,45 +327,39 @@ export default {
getSelectedRouteCode() {
return store.getters.order.schedule.routeCode;
},
+ getSupportingItems() {
+ return store.getters.lineItems.supportingItems;
+ },
isMobilePremiumFeeOnOrderInVuex() {
const supportingItemsFromVuex = store.getters.lineItems.supportingItems;
return !!supportingItemsFromVuex.filter(
(lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE
).length;
},
- timeSlotModalClosed() {
- // Clear the selectedDate if no timeSlot has been selected
- if (!this.selectedTimeSlotData.id) {
- this.selectedDate = null;
- }
- },
- updateFooterButtonText(timeSlotData) {
+ updateFooterButtonText(timeSlot) {
let funnelFooterButtonText;
- if (!timeSlotData.id || !this.appointmentDateAndTime) {
+ if (!timeSlot || !timeSlot.date) {
funnelFooterButtonText = "Continue";
} else {
- funnelFooterButtonText =
- "Select " + this.convertSelectedDateToShortMonthAndDay(this.selectedDate);
+ funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
+ timeSlot.date
+ )}`;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
- funnelFooterButtonText +=
- " at " +
- this.getDisplayTextForMilitaryTime(this.appointmentDateAndTime.startTime);
+ funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
+ timeSlot.startTime
+ )}`;
} else if (
this.appointmentType === AppointmentTypeStrings.MOBILE &&
- !timeSlotData.isPremiumAppointment
+ !timeSlot.isPremiumAppointment
) {
- funnelFooterButtonText +=
- " at " +
- this.getDisplayTextForMilitaryTime(
- this.appointmentDateAndTime.startTime,
- true
- ) +
- " - " +
- this.getDisplayTextForMilitaryTime(
- this.appointmentDateAndTime.endTime,
- true
- );
+ funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
+ timeSlot.startTime,
+ true
+ )} - ${this.getDisplayTextForMilitaryTime(
+ timeSlot.endTime,
+ true
+ )}`;
}
}
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
@@ -408,21 +389,16 @@ export default {
},
forwardButtonAction() {
this.updateSupportingItems();
- this.dispatchStoreAction(
- this.storeActions.SAVE_SCHEDULE,
- this.appointmentDateAndTime,
- false
- );
+ this.dispatchStoreAction(this.storeActions.SAVE_SCHEDULE, this.selectedTimeSlot, false);
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
},
-
updateSupportingItems() {
- const supportingItems = store.getters.lineItems.supportingItems;
+ const supportingItems = this.getSupportingItems();
// if we have a premium fee(early bird), then save/update supporting items
if (
this.appointmentType === AppointmentTypeStrings.MOBILE &&
- this.selectedTimeSlotData?.isPremiumAppointment
+ this.selectedTimeSlot?.isPremiumAppointment
) {
const earlyBirdIndex = supportingItems.findIndex(
(item) => item.partType == PREMIUM_FEE_PART_TYPE
@@ -462,17 +438,8 @@ export default {
},
},
watch: {
- selectedDate(newValue, oldValue) {
- // Clear time slot selection if date selected changes
- if (newValue !== oldValue) {
- this.selectedTimeSlotData = {
- id: null,
- isPremiumAppointment: null,
- };
- }
- },
- selectedTimeSlotData(newValue) {
- this.updateFooterButtonText(this.selectedTimeSlotData);
+ selectedTimeSlot(newValue) {
+ this.updateFooterButtonText(newValue);
},
},
components: {
diff --git a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
index eff2358f0..077372bf2 100644
--- a/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
+++ b/src/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue
@@ -6,33 +6,37 @@
:footerButtonText="footerCloseButtonText"
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
- @footer-button-event="closeModal">
-
-
-
-
+ @isModalOpened="setModalStatus"
+ @footer-button-event="setTimeSlot">
+
+
+
+
+
+
@@ -41,21 +45,27 @@
import modal from "@/digital-components/modal/modal";
import textBlock from "@/digital-components/text-block/text-block";
import buttonQuestion from "@/digital-components/button-question/button-question";
-import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper";
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
-// TODO: Move this somewhere more global
+// Helpers
+import { convertDateStringToDate } from "@/layouts/schedule/helpers/schedule-helper";
+import { deepClone } from "@/helpers/object-helper";
+
+// Validation - TODO: Move this somewhere more global?
import { defineRule, useField } from "vee-validate";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
+import { v4 as uuidv4 } from "uuid";
+
// Constants
import {
AppointmentTypeStrings,
+ RouteCodeFlags,
PREMIUM_TIME_SLOT_ID_FLAG,
PREMIUM_FEE_PART_TYPE,
- RouteCodeFlags,
} from "@/constants/schedule-constants";
+
const cmsWidgetFieldMappings = {
MODAL_CLOSE_BUTTON: "FooterText",
SUPPLEMENTAL_INFORMATION: "BodyText",
@@ -67,12 +77,18 @@ const cmsWidgetFieldMappings = {
// Validation for the modal button
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
-// Constants
-
export default {
- name: "timeSlotModalQuestion",
+ name: "time-slot-modal-question",
+ emits: ["update:modelValue"],
props: {
- modelValue: Object,
+ modelValue: {
+ type: Object,
+ default: () => ({
+ id: null,
+ isPremiumAppointment: null,
+ }),
+ },
+
cmsWidgetName: String,
mobileCmsWidgetName: String,
mobilePremiumCmsWidgetName: String,
@@ -85,32 +101,76 @@ export default {
estimatedServiceMinutesMinimum: Number,
estimatedServiceMinutesMaximum: Number,
validationRules: String,
+ customComponentId: String,
},
data() {
return {
- selectedTimeSlotId: this.getModifiedSelectedTimeSlotId(),
+ internalModel: deepClone(this.modelValue),
+ isModalOpened: false,
timeSlotModalListButton: timeSlotModalListButton,
};
},
setup(props) {
- const { handleChange } = useField("time-slot-modal-question", props.validationRules);
- // Run validation on component load
- handleChange(props.modelValue.id);
+ const uuid = uuidv4();
+ const componentId = !props.customComponentId
+ ? `component-${uuid}`
+ : props.customComponentId;
+
+ const modelValue = deepClone(props).modelValue;
+ const initialValue = modelValue;
+
+ const fieldOptions = {
+ value: modelValue,
+ initialValue: initialValue,
+ };
+
+ const { errorMessage, handleChange, meta, validate, errors } = useField(
+ componentId,
+ props.validationRules,
+ fieldOptions
+ );
+
return {
+ componentId,
+ errorMessage,
handleChange,
+ validate,
+ meta,
+ errors,
};
},
watch: {
- modelValue() {
- this.selectedTimeSlotId = this.getModifiedSelectedTimeSlotId();
- // Run component validation that is used at parent level
- this.handleChange(this.modelValue.id);
+ modelValue: {
+ handler(newValue) {
+ this.internalModel = deepClone(newValue);
+ this.handleChange(newValue);
+ },
},
availableTimeSlots(newValue) {
this.autoSelectTimeSlotIfOnlyOneIsAvailable(newValue);
},
},
computed: {
+ selectedValue: {
+ get: function () {
+ return this.modelValue;
+ },
+ set: function (newValue) {
+ this.$emit("update:modelValue", newValue);
+ },
+ },
+ selectedTimeSlotId: {
+ get: function () {
+ return this.selectedValue?.id;
+ },
+ set: function (newValue) {
+ // Button Question only supports Number, or String data types so we must get the full object to emit
+ this.selectedValue = this.getSelectedTimeSlotObject(newValue);
+ },
+ },
+ modal() {
+ return this.$refs["timeSlots"];
+ },
supplementalInformationBlock() {
let appointmentTypeCmsWidgetName;
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
@@ -281,35 +341,26 @@ export default {
},
methods: {
openModal() {
- this.$refs["timeSlots"].openModal();
+ this.modal.openModal();
+ },
+ onModalOpened() {
+ this.internalModel = deepClone(this.modelValue);
+ },
+ setModalStatus(isOpened) {
+ this.isModalOpened = isOpened;
},
- // fires any time the footer button is used, is fired before "onModalClosed"
closeModal() {
- let isSelectedAppointmentPremium = false;
- if (this.selectedTimeSlotId.includes(PREMIUM_TIME_SLOT_ID_FLAG)) {
- this.selectedTimeSlotId = this.removePremiumFlagFromInput(this.selectedTimeSlotId);
- isSelectedAppointmentPremium = true;
- }
- const selectedTimeSlotData = {
- id: this.selectedTimeSlotId,
- isPremiumAppointment: isSelectedAppointmentPremium,
- };
- this.$emit("update:modelValue", selectedTimeSlotData);
- this.$refs["timeSlots"].closeModal();
+ this.modal.closeModal();
},
- // fires any time the modal is closed, AFTER "closeModal" fires if footer button is used
onModalClosed() {
- this.$emit("time-slot-modal-closed");
+ this.internalModel = deepClone(this.modelValue);
},
- getModifiedSelectedTimeSlotId() {
- if (this.modelValue.isPremiumAppointment) {
- return this.addPremiumFlagToInput(this.modelValue.id);
- } else {
- return this.modelValue.id;
- }
+ setTimeSlot() {
+ this.$emit("update:modelValue", this.internalModel);
+ this.closeModal();
},
- // Expected input: "HH:MM"
getDisplayTextForMilitaryTime(militaryTimeInput) {
+ // Expected input: "HH:MM"
let hours = parseInt(militaryTimeInput.split(":")[0]);
const minutes = militaryTimeInput.split(":")[1];
const meridianNotation = hours > 11 ? "PM" : "AM";
@@ -414,6 +465,19 @@ export default {
removePremiumFlagFromInput(timeSlotId) {
return timeSlotId.substring(0, timeSlotId.length - PREMIUM_TIME_SLOT_ID_FLAG.length);
},
+ getSelectedTimeSlotObject(timeSlotId) {
+ const timeSlot = this.dateAndTimeSlotData.timeSlots?.find(
+ (timeSlot) => timeSlot.id == timeSlotId
+ );
+
+ return {
+ date: this.dateAndTimeSlotData.date,
+ startTime: timeSlot.startTime,
+ endTime: timeSlot.endTime,
+ routeCode: timeSlot.id,
+ jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
+ };
+ },
},
components: {
modal,
diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
index 8c442a052..07c6535dd 100644
--- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
+++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
@@ -265,7 +265,6 @@ export default {
modelValue: {
handler(newValue) {
this.internalModel = deepClone(newValue);
-
this.handleChange(newValue);
},
deep: true,