Merge pull request #2658 from Safelite/feature/skiener/CASH-816-refactor
Feature/skiener/cash 816 refactor
This commit is contained in:
commit
3fd176559e
9 changed files with 190 additions and 605 deletions
|
|
@ -182,11 +182,11 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
focusSearchInput() {
|
||||
focusSearchInput(event) {
|
||||
// Focus cursor in input when search icon is clicked
|
||||
const field = document.querySelector("input");
|
||||
field.focus();
|
||||
this.$emit("search-icon-click");
|
||||
this.$emit("search-icon-click", event);
|
||||
},
|
||||
async imageChanged(e) {
|
||||
let file = e.target.files[0];
|
||||
|
|
|
|||
|
|
@ -105,23 +105,11 @@
|
|||
class="ps-4 pe-4 mt-4" />
|
||||
</div>
|
||||
<shopQuestionPopup
|
||||
modalWidgetName="ShopQuestionPopupWidget"
|
||||
:appointmentType="appointmentType"
|
||||
:modelValue="{
|
||||
zipCode: zipCode,
|
||||
state: state,
|
||||
}"
|
||||
:preSelectedProviderNumber="selectedProvider?.providerNumber"
|
||||
:shopProviderData="shopProviderData"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
v-if="isShopQuestionDisplayed"
|
||||
:selectedProviderNumberFromParent="selectedProvider?.providerNumber"
|
||||
:shopProviderDataFromParent="shopProviderData"
|
||||
@shop-selected="onShopSelected"
|
||||
@update:modelValue="onShopModelUpdated"
|
||||
@updated-zipcode-ctu="setZipcodeCtu"
|
||||
@update-shop-provider-data="shopProviderData = $event"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
:zipCode="zipCode"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
:zipCodeFromParent="zipCode"
|
||||
cmsWidgetName="YourSafeliteShopWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
|
@ -174,7 +162,6 @@ import shopQuestion from "@/layouts/service-location/shop-question/shop-question
|
|||
import shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -813,17 +800,7 @@ export default {
|
|||
return this.displayNoShopsAlert || !this.isMobileAddressValid;
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
};
|
||||
},
|
||||
|
|
@ -1168,43 +1145,20 @@ export default {
|
|||
startTime: "08:00",
|
||||
};
|
||||
},
|
||||
onShopSelected(providerObject) {
|
||||
const current = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === this.selectedProvider?.providerNumber
|
||||
);
|
||||
if (current) {
|
||||
this.selectedProvider = current;
|
||||
}
|
||||
onShopSelected(shopQuestionPopUpData) {
|
||||
this.shopProviderData = shopQuestionPopUpData.shopProviderData;
|
||||
this.setServiceabilityDetails(shopQuestionPopUpData.serviceabilityDetails);
|
||||
this.selectedProvider = this.shopProviderData.shopProviders.find((shopProvider) => {
|
||||
return shopProvider.providerNumber === shopQuestionPopUpData.selectedProviderNumber;
|
||||
});
|
||||
|
||||
let provider = this.shopProviderData?.shopProviders?.find(
|
||||
(p) => p.providerNumber === providerObject?.providerNumber
|
||||
);
|
||||
this.selectedProvider = provider;
|
||||
if (providerObject && providerObject.address) {
|
||||
this.zipCode = providerObject.searchedZip;
|
||||
this.state = providerObject.searchedState;
|
||||
}
|
||||
},
|
||||
setZipcodeCtu(zipcodeCtu) {
|
||||
this.zipCodeCtu = zipcodeCtu;
|
||||
},
|
||||
onShopModelUpdated(newModel) {
|
||||
if (newModel.zipCode) {
|
||||
this.zipCode = newModel.zipCode;
|
||||
}
|
||||
if (newModel.zipCodeCtu) {
|
||||
this.zipCodeCtu = newModel.zipCodeCtu;
|
||||
}
|
||||
if (newModel.state) {
|
||||
this.state = newModel.state;
|
||||
}
|
||||
if (this.shopProviderData && newModel.selectedProviderNumber) {
|
||||
const provider = this.shopProviderData.shopProviders.find(
|
||||
(p) => p.providerNumber === newModel.selectedProviderNumber
|
||||
);
|
||||
if (provider) {
|
||||
this.selectedProvider = provider;
|
||||
}
|
||||
// No zipCodeData comes back if zip was not changed
|
||||
if (shopQuestionPopUpData.zipCodeData) {
|
||||
this.zipCode = shopQuestionPopUpData.zipCodeData.zipCode;
|
||||
this.state = shopQuestionPopUpData.zipCodeData.state;
|
||||
this.zipCodeCtu = shopQuestionPopUpData.zipCodeData.zipCodeCtu;
|
||||
this.zipContainsMilitaryBase =
|
||||
shopQuestionPopUpData.zipCodeData.containsMilitaryBase;
|
||||
}
|
||||
},
|
||||
async getMoreScheduleData(startDate, endDate) {
|
||||
|
|
|
|||
|
|
@ -122,35 +122,6 @@ export async function getShopProviderData(serviceZipCode) {
|
|||
);
|
||||
}
|
||||
|
||||
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.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length >=
|
||||
numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? "high" : "low";
|
||||
|
||||
return shopStatus;
|
||||
}
|
||||
|
||||
export async function getClosestApplicableShops(serviceZipCode, carId, pageNameToLog) {
|
||||
if (!serviceZipCode) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
getPricedMobileFeePart,
|
||||
getServiceabilityDetails,
|
||||
getAvailabilityRating,
|
||||
} from "./service-location-helper";
|
||||
import { getPricedMobileFeePart, getServiceabilityDetails } from "./service-location-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
|
||||
jest.mock("@/store", () => ({
|
||||
|
|
@ -320,38 +316,4 @@ describe("service-location-helper.js", () => {
|
|||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAvailabilityRating", () => {
|
||||
it("Should return a 'high' rating", async () => {
|
||||
// Arrange
|
||||
const providerNumber = "0000001";
|
||||
const expected = "high";
|
||||
// Act
|
||||
const result = await getAvailabilityRating(
|
||||
"2023-06-30",
|
||||
"2023-07-06",
|
||||
"Inshop",
|
||||
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(
|
||||
"2023-06-30",
|
||||
"2023-07-06",
|
||||
"Inshop",
|
||||
providerNumber
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -148,11 +148,9 @@
|
|||
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 shopQuestionPopup from "@/layouts/service-location/shop-question/shop-question-popup";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import shopListButton from "@/layouts/service-location/shop-question/shop-list-button/shop-list-button";
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
|
|
@ -163,7 +161,6 @@ import contentGroupModal from "@/fmg-components/content-group-modal/content-grou
|
|||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
||||
// Supporting files
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import experimentMixin from "@/mixins/experiment-mixin.js";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
|
@ -450,17 +447,7 @@ export default {
|
|||
return this.displayNoShopsAlert || !this.isMobileAddressValid;
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.selectedAppointmentType,
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
cornerStyle="rounded"
|
||||
mask="#####"
|
||||
:includeSearchIcon="includeSearchIcon"
|
||||
@search-icon-click="$emit('search-icon-click')"
|
||||
:displayQuestionText="false"
|
||||
:isRequired="isRequired"
|
||||
:validationRules="computedValidationRules" />
|
||||
|
|
|
|||
|
|
@ -58,15 +58,22 @@ export default {
|
|||
beforeMount() {
|
||||
if (this.displayAvailabilityIndicators) {
|
||||
this.displayLoader();
|
||||
const startDate = this.additionalButtonData.startDate;
|
||||
const endDate = this.additionalButtonData.endDate;
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
const shopAppointmentType = this.additionalButtonData.shopAppointmentType;
|
||||
|
||||
this.additionalButtonData
|
||||
.availabilityRatingCallback(startDate, endDate, shopAppointmentType, this.value)
|
||||
.then((data) => {
|
||||
this.availabilityRating = data;
|
||||
});
|
||||
this.getAvailabilityRating(
|
||||
formattedStartDate,
|
||||
formattedEndDate,
|
||||
shopAppointmentType,
|
||||
this.value
|
||||
).then((data) => {
|
||||
this.availabilityRating = data;
|
||||
});
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -103,6 +110,29 @@ export default {
|
|||
displayLoader() {
|
||||
this.isLoaderDisplayed = true;
|
||||
},
|
||||
async getAvailabilityRating(startDate, endDate, shopAppointmentType, providerNumber) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length >=
|
||||
numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? "high" : "low";
|
||||
|
||||
return shopStatus;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loader,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="isDisplayed" class="shop-question" aria-live="polite">
|
||||
<div class="shop-question" aria-live="polite">
|
||||
<div class="modal-link">
|
||||
<text-link
|
||||
linkType="text"
|
||||
|
|
@ -13,20 +13,17 @@
|
|||
<modal
|
||||
ref="shopQuestionModal"
|
||||
:headerText="modalHeaderText"
|
||||
:onModalOpenedCallback="onModalOpened"
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
:onModalOpenedCallback="updateSelectedAnswer"
|
||||
:onModalClosedCallback="resetZipCodeData"
|
||||
:footerButtonText="modalFooterText"
|
||||
:footerButtonDisabled="
|
||||
displayInvalidZipAlert || displayNoShopsAlert || !selectedProviderNumber
|
||||
"
|
||||
:footerButtonDisabled="isModalFooterButtonDisabled"
|
||||
@footer-button-event="setZipCodeAndShop">
|
||||
<form @submit.prevent>
|
||||
<serviceZipQuestion
|
||||
ref="serviceZipQuestion"
|
||||
class="shop-question-zipcode"
|
||||
customInputId="serviceZipCode"
|
||||
v-model="internalModel.zipCode"
|
||||
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
|
||||
v-model="localZipCode"
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
@search-icon-click="onSearchZip"
|
||||
@keydown.enter="onSearchZip"
|
||||
|
|
@ -57,9 +54,9 @@
|
|||
:answers="answers"
|
||||
groupName="chooseShop"
|
||||
textPosition="text-start"
|
||||
v-model="selectedProviderNumber"
|
||||
v-model="localSelectedProviderNumber"
|
||||
isRequired
|
||||
:validationRules="this.selectedProviderNumber ? '' : 'option-required'" />
|
||||
:validationRules="this.localSelectedProviderNumber ? '' : 'option-required'" />
|
||||
<div class="show-more-shops-link">
|
||||
<textLink
|
||||
v-if="displaySeeMoreLocationsLink"
|
||||
|
|
@ -69,7 +66,7 @@
|
|||
linkType="text"
|
||||
:text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="getNextShopsFromList(3)"
|
||||
@click-event="updateShopListWithNextShops()"
|
||||
:aria-label="showMoreShopsLinkText" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -84,81 +81,43 @@ import serviceZipQuestion from "@/layouts/service-location/service-zip-modal-que
|
|||
import buttonQuestion from "@/digital-components/button-question/button-question.vue";
|
||||
import alert from "@/ux-components/alert/alert.vue";
|
||||
import shopListButton from "./shop-list-button/shop-list-button";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
// Supporting files
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { nextTick } from "vue";
|
||||
import { regex } from "@/helpers/validation-rules";
|
||||
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
import {
|
||||
getPricedMobileFeePart,
|
||||
getPricedRecycleFeePart,
|
||||
getServiceabilityDetails,
|
||||
getBillToAccountNumber,
|
||||
getClosestApplicableShops,
|
||||
getShopProviderData,
|
||||
getZipCodeData,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
|
||||
|
||||
export default {
|
||||
name: "shop-question-popup",
|
||||
mixins: [baseMixin],
|
||||
data() {
|
||||
return {
|
||||
isShopQuestionDisplayed: true,
|
||||
internalModel: this.copyModel(this.modelValue),
|
||||
answers: [],
|
||||
localZipCode: this.zipCodeFromParent,
|
||||
shopListButton: shopListButton,
|
||||
shopIndex: 0,
|
||||
displaySeeMoreLocationsLink: false,
|
||||
serviceZipCodeTextInputId: "",
|
||||
localShopProviderData: this.shopProviderData,
|
||||
selectedProviderNumber: null,
|
||||
isLoadingShops: false,
|
||||
lastSearchedZip: "",
|
||||
lastSuccessfulZip: "",
|
||||
hasLoadedShopData: false,
|
||||
displayInvalidZipAlert: false,
|
||||
areShopsAvialable: true,
|
||||
displayNoShopsAlert: false,
|
||||
numberOfShopsToDisplay: 0,
|
||||
localShopProviderData: this.shopProviderDataFromParent,
|
||||
localSelectedProviderNumber: null,
|
||||
zipCodeData: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
appointmentType: String,
|
||||
shopProviderData: Object,
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
state: "",
|
||||
zipCode: "",
|
||||
}),
|
||||
},
|
||||
isDisplayed: Boolean,
|
||||
modelWidgetName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isServiceableInshop: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
zipCode: {
|
||||
shopProviderDataFromParent: Object,
|
||||
zipCodeFromParent: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
preSelectedProviderNumber: {
|
||||
selectedProviderNumberFromParent: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
|
@ -167,14 +126,20 @@ export default {
|
|||
shopQuestionLinkText() {
|
||||
return this.getCmsContent("ChangeMyLocationLinkWidget", "Text");
|
||||
},
|
||||
modalName() {
|
||||
return this.modelWidgetName;
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent("ShopQuestionWidget", "QuestionText");
|
||||
},
|
||||
selectShopText() {
|
||||
return this.getCmsContent("YourSafeliteShopWidget", "QuestionText");
|
||||
modalFooterText() {
|
||||
return this.getCmsContent("SaveLocationWidget", "Text");
|
||||
},
|
||||
showMoreShopsLinkText() {
|
||||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
},
|
||||
displayInvalidZipAlert() {
|
||||
return this.zipCodeData && !this.zipCodeData.isValid;
|
||||
},
|
||||
displayNoShopsAlert() {
|
||||
return !this.shopProviders.length && !this.displayInvalidZipAlert;
|
||||
},
|
||||
modal() {
|
||||
return this.$refs.shopQuestionModal;
|
||||
|
|
@ -182,79 +147,32 @@ export default {
|
|||
shopProviders() {
|
||||
return this.localShopProviderData?.shopProviders ?? [];
|
||||
},
|
||||
modalFooterText() {
|
||||
return this.getCmsContent("SaveLocationWidget", "Text");
|
||||
},
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {},
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
// Used for availability indicators, could be refactored out of any availability indicator
|
||||
// logic if/when we are sure we'll never have separate drop off/ in shop logic
|
||||
shopAppointmentType: AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF,
|
||||
};
|
||||
},
|
||||
showMoreShopsLinkText() {
|
||||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
displaySeeMoreLocationsLink() {
|
||||
if (!this.shopProviders.length) return false;
|
||||
return this.numberOfShopsToDisplay < this.shopProviders.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal(event) {
|
||||
if (event) event.preventDefault();
|
||||
if (
|
||||
!this.shopProviderData ||
|
||||
!this.shopProviderData.shopProviders ||
|
||||
!this.shopProviderData.shopProviders.length
|
||||
) {
|
||||
// Optionally show a loading message or disable the link
|
||||
return;
|
||||
}
|
||||
this.$refs.shopQuestionModal.openModal();
|
||||
isModalFooterButtonDisabled() {
|
||||
return (
|
||||
this.displayInvalidZipAlert ||
|
||||
this.displayNoShopsAlert ||
|
||||
!this.localSelectedProviderNumber
|
||||
);
|
||||
},
|
||||
getSelectedProviderObject(providerNumber) {
|
||||
const provider =
|
||||
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
|
||||
new Provider();
|
||||
|
||||
return provider;
|
||||
},
|
||||
async getNextShopsFromList(numberToGet = 3) {
|
||||
const logFirstShopsDisplayed = this.shopIndex == 0;
|
||||
|
||||
const shopIterator = (array, n) => {
|
||||
const l = array.length;
|
||||
return () => {
|
||||
const end = this.shopIndex + n;
|
||||
const part = array.slice(this.shopIndex, end);
|
||||
this.shopIndex = end < l ? end : this.shopProviders.length;
|
||||
return part;
|
||||
};
|
||||
};
|
||||
|
||||
const toTitleCase = (str) => {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
const nextShop = shopIterator(this.shopProviders, numberToGet);
|
||||
|
||||
// Map API result data
|
||||
const mappedData = nextShop().map((shopProvider) => {
|
||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = toTitleCase(shopProvider.address.city);
|
||||
answers() {
|
||||
const updatedProvidersToDisplay = this.shopProviders.slice(
|
||||
0,
|
||||
this.numberOfShopsToDisplay
|
||||
);
|
||||
return updatedProvidersToDisplay.map((shopProvider) => {
|
||||
const streetAddress = this.toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = this.toTitleCase(shopProvider.address.city);
|
||||
const state = shopProvider.address.state;
|
||||
const zipCode = shopProvider.address.zipCode;
|
||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
||||
|
|
@ -267,354 +185,130 @@ export default {
|
|||
value: shopProvider.providerNumber,
|
||||
};
|
||||
});
|
||||
|
||||
var gaAction = this.GaActions.MORE_LOCATIONS_CLICKED;
|
||||
if (logFirstShopsDisplayed) {
|
||||
gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED;
|
||||
}
|
||||
if (
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP ||
|
||||
this.appointmentType === AppointmentTypeStrings.DROP_OFF ||
|
||||
this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF
|
||||
) {
|
||||
var shops = mappedData.map((shop) => {
|
||||
if (shop.value.length > 5 && shop.value.startsWith("00")) {
|
||||
return shop.value.substring(1);
|
||||
} else {
|
||||
return shop.value;
|
||||
}
|
||||
});
|
||||
|
||||
var joinedShops = shops.join(",");
|
||||
if (!joinedShops) {
|
||||
joinedShops = "no-shops";
|
||||
}
|
||||
|
||||
this.pushEventToGA(this.GaCategories.SERVICE_LOCATION, gaAction, joinedShops, true);
|
||||
}
|
||||
|
||||
if (this.answers.length === 0) {
|
||||
this.answers = mappedData;
|
||||
} else {
|
||||
mappedData.forEach((shop) => {
|
||||
this.answers.push(shop);
|
||||
});
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (this.shopIndex == this.shopProviders.length) {
|
||||
this.displaySeeMoreLocationsLink = false;
|
||||
} else {
|
||||
this.displaySeeMoreLocationsLink = true;
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
|
||||
this.scrollToPageBottom();
|
||||
},
|
||||
async handleUpdate(selectedShopIndex = null) {
|
||||
this.resetAnswers();
|
||||
if (selectedShopIndex >= 3) {
|
||||
await this.getNextShopsFromList(selectedShopIndex + 1);
|
||||
} else {
|
||||
await this.getNextShopsFromList();
|
||||
await nextTick();
|
||||
}
|
||||
},
|
||||
copyModel(modelToCopy) {
|
||||
return {
|
||||
state: modelToCopy.state,
|
||||
zipCode: modelToCopy.zipCode,
|
||||
};
|
||||
},
|
||||
resetAnswers() {
|
||||
this.answers = [];
|
||||
this.shopIndex = 0;
|
||||
},
|
||||
onModalOpened() {
|
||||
const preSelectedIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.preSelectedProviderNumber
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
// Reset local state from parent
|
||||
this.zipCodeData = null;
|
||||
this.localShopProviderData = this.shopProviderDataFromParent;
|
||||
this.localZipCode = this.zipCodeFromParent;
|
||||
// This will be set once the modal is open (updateSelectedAnswer()) to ensure that the
|
||||
// correct answer is selected
|
||||
this.localSelectedProviderNumber = null;
|
||||
|
||||
const selectedProviderNumberFromParentIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumberFromParent
|
||||
);
|
||||
|
||||
if (preSelectedIndex > -1) {
|
||||
this.shopIndex = Math.ceil((preSelectedIndex + 1) / 3) * 3;
|
||||
if (selectedProviderNumberFromParentIndex > -1) {
|
||||
this.numberOfShopsToDisplay =
|
||||
Math.ceil((selectedProviderNumberFromParentIndex + 1) / 3) * 3;
|
||||
} else {
|
||||
this.shopIndex = 3;
|
||||
this.numberOfShopsToDisplay = 3;
|
||||
}
|
||||
this.internalModel = this.copyModel(this.modelValue);
|
||||
this.internalModel.zipCode = this.zipCode;
|
||||
this.localShopProviderData = this.shopProviderData;
|
||||
this.answers = [];
|
||||
this.onSearchZip();
|
||||
this.focusOnZipInput();
|
||||
this.selectedProviderNumber = this.preSelectedProviderNumber;
|
||||
if (
|
||||
(!this.selectedProviderNumber ||
|
||||
!this.shopProviders.some(
|
||||
(p) => p.providerNumber == this.selectedProviderNumber
|
||||
)) &&
|
||||
this.shopProviders.length > 0
|
||||
) {
|
||||
this.selectedProviderNumber = this.shopProviders[0].providerNumber;
|
||||
|
||||
this.updateShopsForZip();
|
||||
this.$refs.shopQuestionModal.openModal();
|
||||
},
|
||||
updateSelectedAnswer() {
|
||||
this.localSelectedProviderNumber = this.selectedProviderNumberFromParent;
|
||||
},
|
||||
toTitleCase(str) {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
},
|
||||
updateShopListWithNextShops() {
|
||||
//gaAction = this.GaActions.SHOPS_FIRST_DISPLAYED; --This needs to actually fire on initial display, can never happen here
|
||||
this.numberOfShopsToDisplay += 3;
|
||||
this.pushShopsDisplayedGAEvent(this.answers);
|
||||
},
|
||||
pushShopsDisplayedGAEvent(nextShopListAnswers) {
|
||||
var shops = nextShopListAnswers.map((shop) => {
|
||||
if (shop.value.length > 5 && shop.value.startsWith("00")) {
|
||||
return shop.value.substring(1);
|
||||
} else {
|
||||
return shop.value;
|
||||
}
|
||||
});
|
||||
|
||||
var joinedShops = shops.join(",");
|
||||
if (!joinedShops) {
|
||||
joinedShops = "no-shops";
|
||||
}
|
||||
|
||||
this.pushEventToGA(
|
||||
this.GaCategories.SERVICE_LOCATION,
|
||||
this.GaActions.MORE_LOCATIONS_CLICKED,
|
||||
joinedShops,
|
||||
true
|
||||
);
|
||||
},
|
||||
closeModal() {
|
||||
this.modal.closeModal();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.internalModel = this.copyModel(this.modelValue);
|
||||
this.resetsOnZipInput();
|
||||
},
|
||||
resetsOnZipInput() {
|
||||
this.resetAlerts();
|
||||
},
|
||||
async onSearchZip(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.resetsOnZipInput();
|
||||
this.displayNoShopsAlert = false;
|
||||
const zip = this.internalModel.zipCode;
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
//this.selectedProviderNumber = null;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.zipCodeData = await this.getZipCodeData(this.localZipCode, "service-location");
|
||||
// Add zipCode to zipCodeData as it does not come back from endpoint
|
||||
this.zipCodeData.zipCode = this.localZipCode;
|
||||
if (this.zipCodeData.isValid) {
|
||||
if (this.isVehicleHeavyTruck) {
|
||||
// check the location endpoint to verify this zip can service a heavy truck
|
||||
const closestShops = await getClosestApplicableShops(
|
||||
this.internalModel.zipCode,
|
||||
this.localZipCode,
|
||||
this.carId,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!closestShops || closestShops.providers?.length === 0) {
|
||||
this.displayNoServiceAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
// We need an alert for heavy trucks
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
getShopProviderData(zip).then(async (result) => {
|
||||
if (this.displayNoShopsAlert === true) {
|
||||
this.displayNoShopsAlert = false;
|
||||
}
|
||||
getShopProviderData(this.localZipCode).then(async (result) => {
|
||||
this.localShopProviderData = result.data;
|
||||
this.updateShopsForZip(this.internalModel.zipCode);
|
||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
this.selectedProvider = new Provider(
|
||||
this.shopProviderData.mobileProviderNumber
|
||||
);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
this.numberOfShopsToDisplay = 3;
|
||||
this.updateShopsForZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
async updateShopsForZip(zipCode) {
|
||||
if (!this.appointmentType) {
|
||||
this.answers = [];
|
||||
this.isLoadingShops = false;
|
||||
updateShopsForZip() {
|
||||
if (!this.shopProviders.length) {
|
||||
return;
|
||||
}
|
||||
this.lastSearchedZip = zipCode;
|
||||
if (!zipCode) {
|
||||
this.isLoadingShops = false;
|
||||
this.answers = [];
|
||||
return;
|
||||
}
|
||||
this.isLoadingShops = true;
|
||||
if (!zipCode || !this.shopProviders.length) {
|
||||
this.answers = [];
|
||||
this.displayNoShopsAlert = true;
|
||||
this.displaySeeMoreLocationsLink = false;
|
||||
this.isLoadingShops = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIndex = this.shopProviders.findIndex(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumber
|
||||
);
|
||||
|
||||
// If not found, only show the first 3 shops
|
||||
if (selectedIndex === -1) {
|
||||
this.shopIndex = 3;
|
||||
}
|
||||
|
||||
const sortedShops = this.getShopsByZip(zipCode, this.shopProviders, this.shopIndex);
|
||||
|
||||
const toTitleCase = (str) => {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
const mappedData = sortedShops.map((shopProvider) => {
|
||||
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
|
||||
const city = toTitleCase(shopProvider.address.city);
|
||||
const state = shopProvider.address.state;
|
||||
const zipCode = shopProvider.address.zipCode;
|
||||
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
||||
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
buttonLabel: city,
|
||||
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
||||
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
|
||||
additionalButtonData: {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.appointmentType,
|
||||
},
|
||||
value: shopProvider.providerNumber,
|
||||
};
|
||||
});
|
||||
this.answers = mappedData;
|
||||
if (
|
||||
this.selectedProviderNumber &&
|
||||
!this.answers.some((a) => String(a.value) === String(this.selectedProviderNumber))
|
||||
!this.answers.some(
|
||||
(a) => String(a.value) === String(this.localSelectedProviderNumber)
|
||||
)
|
||||
) {
|
||||
this.selectedProviderNumber = null;
|
||||
this.localSelectedProviderNumber = null;
|
||||
}
|
||||
this.shopIndex = mappedData.length;
|
||||
this.displaySeeMoreLocationsLink = this.shopIndex < this.shopProviders.length;
|
||||
if (mappedData.length > 0) {
|
||||
this.lastSuccessfulZip = zipCode;
|
||||
}
|
||||
this.isLoadingShops = false;
|
||||
this.displayNoShopsAlert = this.answers.length === 0;
|
||||
},
|
||||
resetModalButtonStyle() {
|
||||
this.modal.resetButtonStyle();
|
||||
},
|
||||
onInputIdAssigned(inputId) {
|
||||
this.serviceZipCodeTextInputId = inputId;
|
||||
},
|
||||
focusOnZipInput() {
|
||||
const input = document.getElementById(this.serviceZipCodeTextInputId);
|
||||
input?.focus();
|
||||
},
|
||||
resetAlerts() {
|
||||
this.displayInvalidZipAlert = false;
|
||||
this.displayNoServiceAlert = false;
|
||||
this.displayNoShopsAlert = false;
|
||||
// I think we should call our new pushGA Event here with new mapped data
|
||||
},
|
||||
async setZipCodeAndShop() {
|
||||
if (
|
||||
this.displayInvalidZipAlert ||
|
||||
this.displayNoShopsAlert ||
|
||||
!this.selectedProviderNumber
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const zipToEmit = this.lastSearchedZip;
|
||||
|
||||
const selectedProvider = this.shopProviders.find(
|
||||
(provider) => provider.providerNumber == this.selectedProviderNumber
|
||||
(provider) => provider.providerNumber == this.localSelectedProviderNumber
|
||||
);
|
||||
this.resetAlerts();
|
||||
const serviceZipCode = selectedProvider.address.zipCode;
|
||||
|
||||
const zipCodeData = await this.getZipCodeData(zipToEmit, "service-location");
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
const shopQuestionPopUpData = {
|
||||
serviceabilityDetails: serviceabilityDetails.data,
|
||||
zipCodeData: this.zipCodeData,
|
||||
shopProviderData: this.localShopProviderData,
|
||||
selectedProviderNumber: this.localSelectedProviderNumber,
|
||||
};
|
||||
this.$emit("shop-selected", shopQuestionPopUpData);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
if (this.isVehicleHeavyTruck) {
|
||||
// check the location endpoint to verify this zip can service a heavy truck
|
||||
const closestShops = await getClosestApplicableShops(
|
||||
selectedProvider.address.zipCode,
|
||||
this.carId,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!closestShops || closestShops.providers?.length === 0) {
|
||||
this.displayNoServiceAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
selectedProvider.address.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = selectedProvider.address.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve recycle fee part
|
||||
const recycleFeePart = await getPricedRecycleFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
selectedProvider.address.zipCodeCtu
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
this.$emit("updated-recycle-fee-part", recycleFeePart);
|
||||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
this.$emit("updated-bill-to-account-number", billToAccountNumber);
|
||||
this.$emit("updated-zipcode-ctu", selectedProvider.address.zipCodeCtu);
|
||||
|
||||
this.$emit("update:modelValue", this.selectedValue);
|
||||
this.$emit("update-shop-provider-data", this.localShopProviderData);
|
||||
this.internalModel.state = zipCodeData.state;
|
||||
this.$emit("shop-selected", {
|
||||
providerNumber: selectedProvider?.providerNumber,
|
||||
address: selectedProvider?.address,
|
||||
searchedZip: zipToEmit,
|
||||
searchedState: this.internalModel.state,
|
||||
});
|
||||
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
getShopsByZip(zipCode, shops, numberToGet = 3) {
|
||||
return shops
|
||||
.filter(
|
||||
(shop) => shop.address && shop.address.zipCode && shop.address.zipCode !== ""
|
||||
)
|
||||
.sort((a, b) => a.distanceInMiles - b.distanceInMiles)
|
||||
.slice(0, numberToGet);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
shopProviderData: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.localShopProviderData = newVal;
|
||||
this.hasLoadedShopData = true;
|
||||
},
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ import { errorMessages } from "@/constants/error-messages";
|
|||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
|
|
@ -109,17 +107,7 @@ export default {
|
|||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||
},
|
||||
additionalButtonData() {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setDate(startDate.getDate() + 6);
|
||||
|
||||
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||
|
||||
return {
|
||||
availabilityRatingCallback: getAvailabilityRating,
|
||||
startDate: formattedStartDate,
|
||||
endDate: formattedEndDate,
|
||||
shopAppointmentType: this.selectedAppointmentType,
|
||||
};
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue