diff --git a/src/constants/experiments.js b/src/constants/experiments.js index f6d25f1a7..f89aad963 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -5,6 +5,7 @@ const experimentUniverses = { const experimentSettings = { GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index", SUPPRESS_VIN_CAPTURE: "SuppressVinCapture", + DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators", }; const experimentTriggers = { diff --git a/src/digital-components/button-question/button-question.vue b/src/digital-components/button-question/button-question.vue index a344286ef..7441ab422 100644 --- a/src/digital-components/button-question/button-question.vue +++ b/src/digital-components/button-question/button-question.vue @@ -256,7 +256,9 @@ export default { }, watch: { modelValue(newValue, oldValue) { - this.resetField(); + this.resetField({ + value: newValue, + }); }, answers() { //once we get the answers to display from parent, see if we need a GA event to log what we showed diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 37bdc8622..ae2c4042c 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -235,7 +235,8 @@ export default { date: this.selectedDate.dateString, startTime: timeSlotSelectedObject.startTime, endTime: timeSlotSelectedObject.endTime, - id: this.selectedTimeSlotData.id, + routeCode: this.selectedTimeSlotData.id, + jobMaxMinutes: this.selectableDatesData.estimatedServiceMinutesMaximum, }; } else { return null; @@ -320,11 +321,11 @@ export default { // Ex: April 25 return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" }); }, - // Expected input: "HH:MM:SS" + // Expected input: "HH:MM" getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) { let hours = parseInt(militaryTimeInput.split(":")[0]); const minutes = militaryTimeInput.split(":")[1]; - let meridianNotation = hours > 11 ? "PM" : "AM"; + const meridianNotation = hours > 11 ? "PM" : "AM"; if (hours > 12) { hours -= 12; } @@ -338,15 +339,9 @@ export default { this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); }, async forwardButtonAction() { - //TODO: replace properties with real values once they are available await this.dispatchStoreAction( this.storeActions.SAVE_SCHEDULE, - { - date: "2023-07-04T00:00:00", - startTime: "2023-07-04T12:00:00", - endTime: "2023-07-04T17:00:00", - routeCode: "03341-01820-S-B*20232*11 AM", - }, + this.appointmentDateAndTime, false ); 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 079df757d..a622dd875 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 @@ -44,7 +44,6 @@ import buttonQuestion from "@/digital-components/button-question/button-question import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button"; // TODO: Move this somewhere more global -import { DAYS_OF_WEEK, MONTHS_OF_YEAR } from "@/digital-components/date-picker/mixins/constants.js"; import { defineRule, useField } from "vee-validate"; import { errorMessages } from "@/constants/error-messages"; import { required } from "@/helpers/validation-rules"; @@ -80,7 +79,6 @@ export default { data() { return { selectedTimeSlotId: null, - isSelectedAppointmentPremium: null, timeSlotModalListButton: timeSlotModalListButton, }; }, @@ -96,7 +94,7 @@ export default { // Run component validation that is used at parent level this.handleChange(this.modelValue.id); }, - dateAndTimeSlotData(newValue, oldValue) { + availableTimeSlots(newValue) { this.autoSelectTimeSlotIfOnlyOneIsAvailable(newValue); }, }, @@ -190,34 +188,33 @@ export default { }, // fires any time the footer button is used, is fired before "onModalClosed" closeModal() { - if (this.selectedTimeSlotId.toString().includes(PREMIUM_TIME_SLOT_ID_FLAG)) { + let isSelectedAppointmentPremium = false; + if (this.selectedTimeSlotId.includes(PREMIUM_TIME_SLOT_ID_FLAG)) { this.selectedTimeSlotId = this.removePremiumFlagFromInput(this.selectedTimeSlotId); - this.isSelectedAppointmentPremium = true; - } else { - this.isSelectedAppointmentPremium = false; + isSelectedAppointmentPremium = true; } const selectedTimeSlotData = { id: this.selectedTimeSlotId, - isPremiumAppointment: this.isSelectedAppointmentPremium, + isPremiumAppointment: isSelectedAppointmentPremium, }; this.$emit("update:modelValue", selectedTimeSlotData); this.$refs["timeSlots"].closeModal(); }, // fires any time the modal is closed, AFTER "closeModal" fires if footer button is used onModalClosed() { - this.isSelectedAppointmentPremium = this.modelValue.isPremiumAppointment; - if (this.isSelectedAppointmentPremium) { + // Reset component state to parent's state + if (this.modelValue.isPremiumAppointment) { this.selectedTimeSlotId = this.addPremiumFlagToInput(this.modelValue.id); } else { this.selectedTimeSlotId = this.modelValue.id; } this.$emit("time-slot-modal-closed"); }, - // Expected input: "HH:MM:SS" + // Expected input: "HH:MM" getDisplayTextForMilitaryTime(militaryTimeInput) { let hours = parseInt(militaryTimeInput.split(":")[0]); const minutes = militaryTimeInput.split(":")[1]; - let meridianNotation = hours > 11 ? "PM" : "AM"; + const meridianNotation = hours > 11 ? "PM" : "AM"; if (hours > 12) { hours -= 12; } @@ -286,24 +283,17 @@ export default { }, }; }, - autoSelectTimeSlotIfOnlyOneIsAvailable(newdateAndTimeSlotDataValue) { - const numberOfOptions = newdateAndTimeSlotDataValue?.timeSlots.length; - if ( - numberOfOptions === 1 && - !( - this.appointmentType === AppointmentTypeStrings.MOBILE && - this.premiumAppointmentFee && - newdateAndTimeSlotDataValue.timeSlots[0].offerPremium - ) - ) { - this.selectedTimeSlotId = newdateAndTimeSlotDataValue.timeSlots[0].id; + autoSelectTimeSlotIfOnlyOneIsAvailable(newAvailableTimeSlotsValue) { + const numberOfOptions = newAvailableTimeSlotsValue?.length; + if (numberOfOptions === 1) { + this.selectedTimeSlotId = newAvailableTimeSlotsValue[0].value; } }, addPremiumFlagToInput(timeSlotId) { return (timeSlotId += PREMIUM_TIME_SLOT_ID_FLAG); }, removePremiumFlagFromInput(timeSlotId) { - return parseInt(timeSlotId.trim(PREMIUM_TIME_SLOT_ID_FLAG.length)); + return timeSlotId.substring(0, timeSlotId.length - PREMIUM_TIME_SLOT_ID_FLAG.length); }, }, components: { diff --git a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js index 157c28f2b..b100a50b5 100644 --- a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js +++ b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js @@ -1,4 +1,5 @@ import { storeActions } from "@/constants/store-actions"; +import store from "@/store"; import baseMixin from "@/mixins/base-mixin.js"; export async function getPricedMobileFeePart(serviceZipCode) { @@ -42,3 +43,48 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems) { return Promise.resolve(serviceabilityDetails); } + +export async function getAvailabilityRating( + startDate, + endDate, + shopAppointmentType, + providerNumber +) { + // For a given shop provider number and date range, get the appointment time slots available + const shopTimeSlots = await baseMixin.methods.dispatchStoreAction( + storeActions.GET_SHOP_TIME_SLOTS, + { + providerNumber: providerNumber, + startDate: startDate, + endDate: endDate, + shopAppointmentType: shopAppointmentType, + }, + false + ); + + // Rate the availability for the shop + let numberOfAppointmentsPerDay = []; + for (let i = 0; i < shopTimeSlots.data.days.length; i++) { + numberOfAppointmentsPerDay.push(shopTimeSlots.data.days[i].timeSlots.length); + } + + const dateRange = 7; + const minimumNumberOfAppointmentsPerDay = 1; + const numberOfDaysToEvaluate = 2; + + let daysWithMinimalAppointmentsCount = 0; + for (let i = 0; i < dateRange; i++) { + if (numberOfAppointmentsPerDay[i] >= minimumNumberOfAppointmentsPerDay) { + daysWithMinimalAppointmentsCount++; + if (daysWithMinimalAppointmentsCount >= numberOfDaysToEvaluate) { + break; + } + } + } + + const isGoodAvailability = daysWithMinimalAppointmentsCount >= numberOfDaysToEvaluate; + + const shopStatus = isGoodAvailability ? "high" : "low"; + + return Promise.resolve(shopStatus); +} diff --git a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.spec.js b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.spec.js index 755b0d870..81d336968 100644 --- a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.spec.js +++ b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.spec.js @@ -1,10 +1,186 @@ -import { getPricedMobileFeePart, getServiceabilityDetails } from "./service-location-helper"; +import { + getPricedMobileFeePart, + getServiceabilityDetails, + getAvailabilityRating, +} from "./service-location-helper"; import { storeActions } from "@/constants/store-actions"; +jest.mock("@/store", () => ({ + getters: { + order: { + vehicle: { + year: null, + make: null, + model: null, + style: null, + carId: null, + category: null, + vin: null, + imageUrl: null, + imageVifNumber: null, + imageColor: null, + registration: { + licensePlate: null, + address: null, + city: null, + state: null, + zipCode: null, + firstName: null, + lastName: null, + }, + }, + serviceLocation: { + address: null, + city: null, + state: null, + zipCode: null, + zipCodeCtu: null, + appointmentType: null, + provider: { + providerNumber: null, + address: { + streetAddress: null, + city: null, + state: null, + zip: null, + }, + }, + }, + customer: { + emailAddress: null, + }, + damage: { + isRepair: null, + numberOfChips: null, + glassToReplace: null, + partQuestionAnswers: null, + moldingQuestionAnswers: null, + capabilityQuestionAnswers: null, + }, + lineItems: { + glassParts: null, + supportingItems: null, + vaps: null, + serverData: null, + }, + payment: { + isInsurance: null, + insuranceCoverage: { + isVerified: null, + coverageStatus: null, + }, + parentAccountNumber: 0, + }, + schedule: { + date: null, + startTime: null, + endTime: null, + routeCode: null, + }, + referralNumber: null, + referralDate: null, + referralCorrelationId: null, + eon: null, + }, + }, +})); + const mockStoreActionGetMobileFeePart = storeActions.GET_MOBILE_FEE_PART; const mockStoreActionPriceOrderItemsAndSaveServerData = storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA; const mockStoreActionGetServiceabilityDetails = storeActions.GET_SERVICEABILITY_DETAILS; +const mockStoreActionGetShopTimeSlots = storeActions.GET_SHOP_TIME_SLOTS; + +const mockGetShopTimeSlotsGoodAvailability = { + estimatedServiceMinutesMinimum: 0, + estimatedServiceMinutesMaximimum: 0, + days: [ + { + date: "string", + timeSlots: [ + { + id: "string", + startTime: "", + endTime: "", + offerPremium: true, + }, + ], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [ + { + id: "string", + startTime: "", + endTime: "", + offerPremium: true, + }, + ], + }, + ], +}; + +const mockGetShopTimeSlotsLowAvailability = { + estimatedServiceMinutesMinimum: 0, + estimatedServiceMinutesMaximimum: 0, + days: [ + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [], + }, + { + date: "string", + timeSlots: [ + { + id: "string", + startTime: "", + endTime: "", + offerPremium: true, + }, + ], + }, + ], +}; jest.mock("@/mixins/base-mixin.js", () => ({ methods: { @@ -18,7 +194,7 @@ jest.mock("@/mixins/base-mixin.js", () => ({ }); }), - dispatchStoreAction: jest.fn().mockImplementation((actionName) => { + dispatchStoreAction: jest.fn().mockImplementation((actionName, request) => { if (actionName === mockStoreActionGetMobileFeePart) { return Promise.resolve({ data: { @@ -50,6 +226,14 @@ jest.mock("@/mixins/base-mixin.js", () => ({ isRecalibrationServiceableMobile: true, }); } + + if (actionName === mockStoreActionGetShopTimeSlots) { + if (request.providerNumber == "0000001") { + return Promise.resolve(mockGetShopTimeSlotsGoodAvailability); + } + + return Promise.resolve(mockGetShopTimeSlotsLowAvailability); + } }), }, })); @@ -124,4 +308,25 @@ describe("service-location-helper.js", () => { expect(result).toEqual(expected); }); }); + + describe("getAvailabilityRating", () => { + // it("Should return a 'Good' rating", async () => { + // // Arrange + // const providerNumber = "0000001"; + // const expected = "Good"; + // // Act + // const result = await getAvailabilityRating(providerNumber); + // // Assert + // expect(result).toEqual(expected); + // }); + // it("Should return a 'Low' rating", async () => { + // // Arrange + // const providerNumber = "0000000"; + // const expected = "Low"; + // // Act + // const result = await getAvailabilityRating(providerNumber); + // // Assert + // expect(result).toEqual(expected); + // }); + }); }); 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 fa5c1eb62..78094e76b 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 @@ -202,6 +202,7 @@ export default { this.internalModel = deepClone(this.modelValue); }, onModalClosed() { + this.displayInvalidZipAlert = false; this.internalModel = deepClone(this.modelValue); this.resetValidation(); }, @@ -236,17 +237,6 @@ export default { }); }, async setMobileLocation() { - // START - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD - let tempTest = false; - let internalModelAddressQuestions = this.internalModel.addressQuestions; - let modelValueAddressQuestions = this.modelValue.addressQuestions; - if (tempTest) { - // THIS WILL NEVER BE TRUE - console.warn(internalModelAddressQuestions); - console.warn(modelValueAddressQuestions); - } - // END - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD - if ( this.internalModel.addressQuestions.zipCode !== this.modelValue.addressQuestions.zipCode @@ -272,22 +262,19 @@ export default { this.$emit("updated-serviceability", serviceabilityDetails.data); this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); - // Update the page level model - this.$emit("update:modelValue", this.internalModel); - if (this.onZipUpdateCallback) { await this.onZipUpdateCallback(serviceZipCode); } + // Update the page level model + this.$emit("update:modelValue", this.internalModel); this.closeModal(); } } else { - // START - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD - if (tempTest) { - // THIS WILL NEVER BE TRUE - console.warn("the zips match and the exception was run"); - } - // END - TEMP CODE FROM A CAOUETTE 5/25/23 TO BE REMOVED BY EOD + // Update the page level model + this.$emit("update:modelValue", this.internalModel); + + this.closeModal(); } }, }, diff --git a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.spec.js b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.spec.js index b9eb6299b..1248d7ebb 100644 --- a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.spec.js +++ b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.spec.js @@ -2,7 +2,7 @@ import { mount } from "@vue/test-utils"; import shopListButton from "./shop-list-button"; import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"; -describe("service-package-radio.vue", () => { +describe("shop-list-button.vue", () => { it("Should include buttonLabel in html", async () => { // Arrange let { wrapper } = setupMocks({ @@ -49,6 +49,18 @@ describe("service-package-radio.vue", () => { }); }); +const startDate = new Date(); +const endDate = new Date(); +endDate.setDate(startDate.getDate() + 7); +const getAvailabilityRating = jest.fn().mockImplementation((actionName, request) => { + return Promise.resolve({ + data: {}, + }); +}); + +const formattedStartDate = startDate.toISOString().split("T")[0]; +const formattedEndDate = endDate.toISOString().split("T")[0]; + const mockProps = { buttonLabel: "buttonLabel test copy", buttonLabelSubCopy: "buttonLabelSubCopy test copy", @@ -58,6 +70,12 @@ const mockProps = { value: 0, modelValue: 0, groupName: "mockGroup", + additionalButtonData: { + availabilityRatingCallback: getAvailabilityRating, + startDate: formattedStartDate, + endDate: formattedEndDate, + shopAppointmentType: "Dropoff", + }, }; function setupMocks({ mountOptionsMockData = {} }) { diff --git a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue index b8003148f..73c07a85a 100644 --- a/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue +++ b/src/layouts/service-location/shop-question/shop-list-button/shop-list-button.vue @@ -11,26 +11,32 @@ {{ buttonLabel }} - {{ + {{ buttonLabelSubCopy }} - {{ buttonAuxillaryCopy }} + v-if="displayAvailabilityIndicators" + class="availability-indicator rounded-pill" + :class="availabilityRatingClass"> + + {{ + badgeText + }} + - + + + {{ screenReaderOnlyText }} - @@ -40,32 +46,59 @@ 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"; +import experimentMixin from "@/mixins/experiment-mixin"; +import { experimentSettings } from "@/constants/experiments"; export default { name: "shopListButton", mixins: [inputButtonWrapperMixin], - props: { - loaderColor: String, - loaderPosition: { - type: String, - default: "right", - }, + beforeMount() { + if (this.displayAvailabilityIndicators) { + this.displayLoader(); + const startDate = this.additionalButtonData.startDate; + const endDate = this.additionalButtonData.endDate; + const shopAppointmentType = this.additionalButtonData.shopAppointmentType; + + this.additionalButtonData + .availabilityRatingCallback(startDate, endDate, shopAppointmentType, this.value) + .then((data) => { + this.availabilityRating = data; + this.isLoaderDisplayed = false; + }); + } }, data() { return { isLoaderDisplayed: false, - availability: "low", + availabilityRating: null, }; }, + computed: { + displayAvailabilityIndicators() { + return experimentMixin.methods.hasSettingEqualTo( + experimentSettings.DISPLAY_AVAILABILITY_INDICATORS, + "true" + ); + }, + availabilityRatingClass() { + if (this.availabilityRating == null) { + return "gray"; + } else { + return this.availabilityRating == "high" ? "green" : "red"; + } + }, + badgeText() { + if (this.availabilityRating != null) { + return this.availabilityRating == "high" ? "Appts available" : "Appts low"; + } + + return ""; + }, + }, methods: { displayLoader() { this.isLoaderDisplayed = true; }, - preHandleAnswerChange() { - if (this.selectingInitiatesLoad) { - this.displayLoader(); - } - }, }, components: { loader, @@ -75,9 +108,6 @@ export default { diff --git a/src/layouts/service-location/shop-question/shop-question.vue b/src/layouts/service-location/shop-question/shop-question.vue index 3183cbc9e..2bc4cba7b 100644 --- a/src/layouts/service-location/shop-question/shop-question.vue +++ b/src/layouts/service-location/shop-question/shop-question.vue @@ -19,7 +19,8 @@ textPosition="text-start" v-model="selectedProviderNumber" isRequired - validationRules="option-required" /> + validationRules="option-required" + :additionalButtonData="additionalButtonData" /> diff --git a/src/store/index.js b/src/store/index.js index 2c4441acb..270203f89 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -551,6 +551,7 @@ export const actions = { payload: {}, }); }, + lookupVehicleByYmms(context, { year, make, model, style }) { return globalMethods.callHttpClient({ method: endpoints.LookupVehicleByYmms.method, @@ -558,6 +559,7 @@ export const actions = { payload: {}, }); }, + lookupVehicleByVin(context, { vin }) { return globalMethods.callHttpClient({ method: endpoints.LookupVehicleByVin.method, @@ -567,6 +569,7 @@ export const actions = { }, }); }, + lookupVinByPlate(context, { licensePlate, licenseState }) { return globalMethods.callHttpClient({ method: endpoints.LookupVinByPlate.method, @@ -577,6 +580,7 @@ export const actions = { }, }); }, + lookupVinByAddress( context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState } @@ -592,6 +596,7 @@ export const actions = { }, }); }, + lookupVinByImage(context, image) { const data = new FormData(); data.append("vinImage", image); @@ -602,6 +607,7 @@ export const actions = { isFormData: true, }); }, + isVinByAddressPermissible(context, zip) { return globalMethods.callHttpClient({ method: endpoints.IsVinByAddressPermissible.method, @@ -609,6 +615,7 @@ export const actions = { payload: {}, }); }, + getVehicleMakes(context, { year }) { return globalMethods.callHttpClient({ method: endpoints.GetVehicleMakes.method, @@ -616,6 +623,7 @@ export const actions = { payload: {}, }); }, + getVehicleModels(context, { year, make }) { return globalMethods.callHttpClient({ method: endpoints.GetVehicleModels.method, @@ -623,6 +631,7 @@ export const actions = { payload: {}, }); }, + getVehicleStyles(context, { year, make, model }) { return globalMethods.callHttpClient({ method: endpoints.GetVehicleStyles.method, @@ -630,6 +639,7 @@ export const actions = { payload: {}, }); }, + setVehicle(context, { year, make, model, style }) { return globalMethods .callHttpClient({ @@ -652,6 +662,7 @@ export const actions = { return response; }); }, + getDamageOptions(context, { carId }) { return globalMethods.callHttpClient({ methods: endpoints.GetDamageOptions.method, @@ -659,6 +670,7 @@ export const actions = { payload: {}, }); }, + validateZip(context, { zip }) { return globalMethods.callHttpClient({ methods: endpoints.ValidateZip.method, @@ -673,18 +685,22 @@ export const actions = { context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null); context.commit(storeMutations.UPDATE_VAPS, null); }, + resetRegistrationAndDependencies(context) { context.commit(storeMutations.RESET_REGISTRATION_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null); }, + resetPartsAndDependencies(context) { context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, null); }, + resetState(context) { context.commit(storeMutations.RESET_STATE); }, + resetSaveSessionPromise(context) { context.commit(storeMutations.RESET_SAVE_SESSION_PROMISE); }, @@ -699,12 +715,14 @@ export const actions = { }, }); }, + getHomepageName(context) { return globalMethods.callHttpClient({ method: endpoints.GetHomepageInfo.method, endpoint: endpoints.GetHomepageInfo.url(applicationConfig.APPLICATION_ABBREVIATION), }); }, + getPageData(context, { pageName }) { return globalMethods.callHttpClient({ method: endpoints.GetPageData.method, @@ -771,6 +789,7 @@ export const actions = { context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId); context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId); }, + logPageView( context, { @@ -812,6 +831,7 @@ export const actions = { } ); }, + logCustomEvent( context, { @@ -1119,6 +1139,7 @@ export const actions = { getShopTimeSlots(context, { startDate, endDate, shopAppointmentType, providerNumber }) { const order = context.state.order; const vehicle = context.state.order.vehicle; + let partNumbers = [ ...(order.lineItems.supportingItems ?? []), ...(order.lineItems.vaps ?? []), @@ -1161,6 +1182,7 @@ export const actions = { vin: vehicle.vin ?? "", }, }; + return globalMethods.callHttpClient({ method: endpoints.GetShopTimeSlots.method, endpoint: endpoints.GetShopTimeSlots.url, @@ -1168,6 +1190,7 @@ export const actions = { logApiCall: false, }); }, + getMobileTimeSlots(context, { startDate, endDate }) { const order = context.state.order; const vehicle = context.state.order.vehicle; @@ -1219,6 +1242,7 @@ export const actions = { logApiCall: false, }); }, + getMobileEarlyBirdFee(context) { const damageType = context.getters.damage.isRepair ? "Repair" : "Replace"; const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash"; @@ -1228,6 +1252,7 @@ export const actions = { endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`, }); }, + // Session API Actions saveSession(context) { const vehicle = context.getters.vehicle; @@ -1328,6 +1353,7 @@ export const actions = { }, }); }, + loadSession( context, { @@ -1405,6 +1431,7 @@ export const actions = { context.commit(storeMutations.UPDATE_YEAR, year); } }, + saveVehicleMake(context, make) { //Reset dependent state when changing if (context.state.order.vehicle.make !== make) { @@ -1425,6 +1452,7 @@ export const actions = { context.commit(storeMutations.UPDATE_MAKE, make); } }, + saveVehicleModel(context, model) { //Reset dependent state when changing if (context.state.order.vehicle.model !== model) { @@ -1444,6 +1472,7 @@ export const actions = { context.commit(storeMutations.UPDATE_MODEL, model); } }, + saveVehicleStyle(context, style) { //Reset dependent state when changing if (context.state.order.vehicle.style !== style) { @@ -1462,6 +1491,7 @@ export const actions = { context.commit(storeMutations.UPDATE_STYLE, style); } }, + saveVehicleDamage( context, { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount } @@ -1519,6 +1549,7 @@ export const actions = { context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo); } }, + saveRegistrationLicensePlateLookup( context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } @@ -1540,6 +1571,7 @@ export const actions = { context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo); } }, + saveRegistrationAddressLookup( context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo } @@ -1565,6 +1597,7 @@ export const actions = { context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo); } }, + savePartQuestionAnswers(context, partQuestionAnswersArray) { // if part question answers have changed, reset subsequent question answers const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( @@ -1603,6 +1636,7 @@ export const actions = { //Save new values context.commit(storeMutations.UPDATE_PART_QUESTION_ANSWERS, partQuestionAnswersArray); }, + resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) { const partsOrQuestionsDataToCompareWith = context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? @@ -1642,6 +1676,7 @@ export const actions = { }); } }, + saveMoldingQuestionAnswers(context, moldingQuestionAnswers) { const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( context.getters.damage.moldingQuestionAnswers, @@ -1670,6 +1705,7 @@ export const actions = { //Save new values context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, moldingQuestionAnswers); }, + saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) { const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue( context.getters.damage.capabilityQuestionAnswers, @@ -1696,18 +1732,23 @@ export const actions = { capabilityQuestionAnswers ); }, + savePaymentType(context, isInsurance) { context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance); }, + saveParentAccountNumber(context, parentAccountNumber) { context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber); }, + saveSupportingItems(context, supportingItems) { context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItems); }, + saveVaps(context, vaps) { context.commit(storeMutations.UPDATE_VAPS, vaps); }, + // Price order actions async priceOrderItemsAndSaveServerData( context, @@ -1760,16 +1801,20 @@ export const actions = { return availableLineItems; }, + // Misc order actions saveSchedule(context, scheduleInfo) { context.commit(storeMutations.UPDATE_SCHEDULE, scheduleInfo); }, + saveServiceLocation(context, serviceLocationInfo) { context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo); }, + saveEmail(context, email) { context.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, email); }, + saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) { //Reset dependent state when changing if (vehicleInfo.vin !== context.state.order.vehicle.vin) { @@ -1782,12 +1827,15 @@ export const actions = { context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo); } }, + saveGlassParts(context, parts) { context.commit(storeMutations.UPDATE_GLASS_PARTS, parts); }, + clearVin(context) { context.commit(storeMutations.UPDATE_VEHICLE_VIN, null); }, + isVinOptionalVehicle(context) { switch (context.state.order.vehicle.make.toLowerCase()) { case "mercedes benz": diff --git a/src/ux-components/loader/loader.vue b/src/ux-components/loader/loader.vue index daea9c511..0635614b8 100644 --- a/src/ux-components/loader/loader.vue +++ b/src/ux-components/loader/loader.vue @@ -3,7 +3,7 @@ class="loader" role="alert" aria-label="Loading new page" - v-bind:class="[this.loaderColor, this.loaderPosition]"> + v-bind:class="[this.loaderColor, this.loaderPosition, this.blockUi]"> @@ -82,5 +87,9 @@ export default { &.black:after { background-color: $black; } + + &.no-block::before { + z-index: -1; + } }