DigitalConsumer.ISS/src/layouts/tpa-search/tpa-search.vue
2024-01-22 17:15:11 -05:00

418 lines
17 KiB
Vue

<template>
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition">
<siteHeader
ref="siteHeader"
:cmsWidgetName="widget.siteHeader" />
<Form
id="searchProvidersForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<div class="mx-5">
<label
id="tpaSearchQuestionLabel"
for="tpaSearchQuestionField"
class="text-center fs-5 mt-5 mb-0 text-black w-100">
{{ tpaSearchQuestionLabel }}
</label>
<label
id="searchInstructions"
for="tpaSearchQuestionField"
class="text-center small darker-gray w-100 mb-4">
{{ searchInstructionsText }}
</label>
<div class="mb-5">
<textboxQuestion
id="tpaSearchQuestionField"
v-model="zipCode"
inputId="tpaSearchQuestionFieldInput"
:cmsWidgetName="widget.tpaSearchQuestion"
:includeSearchIcon="true"
:displayQuestionText="false"
isRequired
:isDisabled="reloadingProviders"
:validationRules="rules.zipCode"
@clickEvent="searchClick" />
</div>
</div>
</div>
</Form>
<googleMap
id="map"
class="mb-4"
:addresses="providerAddresses"
:zipCode="mapZipCode" />
<Form
id="providerSelectionForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
<div class="container-fluid pb-2">
<div class="mx-5">
<dropdownQuestion
id="searchRadiusFilter"
v-model="filter"
:cmsWidgetName="widget.filterByQuestion"
inputId="searchRadiusFilterInput"
:options="filterOptions"
disableAutoFill
:isDisabled="reloadingProviders"
:validationRules="rules.filter" />
<loader
v-if="reloadingProviders"
id="providersLoader"
loaderPosition="center"
loaderColor="blue"
:width="2"
:height="2"
class="my-4" />
<div v-else>
<div class="my-4">
<buttonQuestion
id="selectProviderQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="providerButtonData"
groupName="chooseShop"
textPosition="text-start"
isRequired
:validationRules="rules.provider"
:additionalButtonData="additionalButtonData" />
<alert
v-if="providers?.length === 0 ?? true"
id="alertNoNetworkProviders"
:cmsWidgetName="widget.noNetworkShopsAlert"
alertClass="alert-warning"
:isDismissible="false"
:manualHeadline="noNetworkShopsAlertHeaderText" />
</div>
<div class="text-center">
<textLink
id="preferredShopNotListedLink"
linkType="navigation"
href="javascript:void(0)"
:text="shopNotListedModalLink"
@clickEvent="doNotSeeMyShopLinkClick" />
</div>
</div>
</div>
<siteFooter
ref="siteFooter"
:cmsWidgetName="widget.siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
</Form>
</div>
</div>
</template>
<script>
// Components
import { Form } from 'vee-validate';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import alert from '@/ux-components/alert/alert.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import googleMap from '@/iss-components/google-map/google-map.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
import loader from '@/ux-components/loader/loader.vue';
// Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { useMainStore } from '@/store';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import globalRules from '@/constants/global-rules.js';
import widgetFields from '@/constants/cms-widget-fields.js';
import { shallowRef } from 'vue';
import { toTitleCase, toDisplayPhoneNumber } from '@/helpers/text-helper.js';
import bailoutMessage from '@/constants/bailoutMessage';
const radiusFilterPairs = [
{ radius: 15, filter: '15 miles' },
{ radius: 25, filter: '25 miles' },
{ radius: 50, filter: '50 miles' },
{ radius: 100, filter: '100 miles' }
];
function convertRadiusFilterToInteger(filter) {
const pair = radiusFilterPairs.find((p) => p.filter === filter);
return pair?.radius ?? 0;
}
async function getInitialSearchData() {
const customerZipCode = useMainStore().order.customer.address.zipCode;
const serviceLocationZipCode = useMainStore().order.serviceLocation.zipCode;
const zipCode = serviceLocationZipCode ?? customerZipCode;
const { searchFilter } = useMainStore().order.serviceLocation;
let { providerNumber } = useMainStore().order.serviceLocation.provider;
let providers = [];
let radius = 0;
if (searchFilter) {
radius = convertRadiusFilterToInteger(searchFilter);
const result = await useMainStore().getTpaProviders(zipCode, radius);
providers = result?.data?.shopProviders ?? [];
return { zipCode, filter: searchFilter, providers, providerNumber };
}
providerNumber = null;
let pairIndex = 0;
let filter = '';
while (providers.length === 0 && pairIndex < radiusFilterPairs.length) {
const pair = radiusFilterPairs[pairIndex];
radius = pair.radius;
filter = pair.filter;
// eslint-disable-next-line no-await-in-loop
const result = await useMainStore().getTpaProviders(zipCode, radius);
providers = result?.data?.shopProviders ?? [];
pairIndex += 1;
}
return { zipCode, filter, providers, providerNumber };
}
export default {
name: 'tpa-search',
components: {
siteHeader,
textboxQuestion,
dropdownQuestion,
buttonQuestion,
textLink,
alert,
loader,
googleMap,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const { zipCode, filter, providers, providerNumber } = await getInitialSearchData();
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
next(async (vm) => {
vm.setCmsContent(cmsContent);
vm.setInitialSearchData(zipCode, filter, providers, providerNumber);
});
},
data() {
return {
dataLoaded: false,
zipCode: null,
mapZipCode: null,
filter: '',
providers: [],
selectedProviderNumber: '',
reloadingProviders: false,
additionalButtonData: {
displayAvailabilityIndicators: false
},
widget: {
siteHeader: 'SiteHeaderWidget',
tpaSearchQuestion: 'TPASearchQuestion',
searchInstructions: 'SearchInstructions',
filterByQuestion: 'FilterByQuestion',
noNetworkShopsAlert: 'NoNetworkShopsAlertWidget',
shopNotListedLink: 'ShopNotListedLink',
siteFooter: 'SiteFooterWidget'
},
rules: {
zipCode: `${globalRules.ZIP_CODE_REQUIRED}|${globalRules.ZIP_CODE_SEARCH_FORMAT}`,
filter: globalRules.OPTION_REQUIRED, // TODO do we even need this?
provider: globalRules.OPTION_REQUIRED
},
shopListButton: shallowRef(shopListButton)
};
},
computed: {
filterOptions() {
const filterByAnswers = this.getCmsContent(
this.widget.filterByQuestion,
widgetFields.INPUT_QUESTION_WIDGET.ANSWERS
) ?? [];
const filterByAnswersObj = {};
[...filterByAnswers].forEach((answer) => {
filterByAnswersObj[answer.Name] = answer.Name;
});
return filterByAnswersObj;
},
tpaSearchQuestionLabel() {
return this.getCmsContent(
this.widget.tpaSearchQuestion,
widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT
);
},
searchInstructionsText() {
return this.getCmsContent(
this.widget.searchInstructions,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
shopNotListedModalLink() {
return this.getCmsContent(
this.widget.shopNotListedLink,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
radiusInMiles() {
return convertRadiusFilterToInteger(this.filter);
},
noNetworkShopsAlertHeaderText() {
return this.getCmsContent(
this.widget.noNetworkShopsAlert,
widgetFields.ALERT_WIDGET.HEADLINE_TEXT
)?.replaceAll('{custom:radiusInMiles}', this.radiusInMiles);
},
providerAddresses() {
return this.providers?.map((provider) => this.getProviderAddress(provider)) ?? [];
},
providerButtonData() {
return this.providers?.map((provider) => this.getShopButtonDataFromProvider(provider)) ?? [];
},
selectedProviderIsSafeliteShop() {
return this.providers?.find((p) => p.providerNumber === this.selectedProviderNumber)?.isSafeliteShop ?? false;
}
},
watch: {
async filter() {
if (this.dataLoaded) {
await this.getProviderButtonData().then((providers) => {
this.providers = providers;
});
this.mapZipCode = this.zipCode;
}
},
providers(newProviders) {
if (this.dataLoaded) {
this.selectedProviderNumber = newProviders?.length === 1 ?? false
? newProviders[0]?.providerNumber ?? ''
: '';
}
},
selectedProviderNumber(newNumber) {
const provider = this.providers?.find((p) => p.providerNumber === newNumber);
if (provider && this.dataLoaded) {
useMainStore().updateServiceLocation({
searchFilter: this.filter,
zipCode: this.zipCode,
provider: {
providerNumber: provider?.providerNumber,
address: {
streetAddress: provider.address?.streetAddress,
city: provider.address?.city,
state: provider.address?.state,
zipCode: provider.address?.zipCode,
zipCodeCtu: provider.address?.zipCodeCtu
},
companyName: provider?.companyName,
phoneNumber: provider?.phoneNumber
}
});
}
}
},
beforeUpdate() {
if (!this.dataLoaded) {
this.dataLoaded = true;
}
},
methods: {
setInitialSearchData(zipCode, filter, providers, providerNumber) {
this.zipCode = zipCode;
this.mapZipCode = zipCode;
this.filter = filter;
this.providers = providers;
this.selectedProviderNumber = providerNumber;
},
getProviderAddress(provider) {
const city = toTitleCase(provider?.address?.city);
const state = provider?.address?.state ?? '';
const zipCode = provider?.address?.zipCode ?? '';
let addressLine2 = '';
if (city) {
addressLine2 += city;
if (state || zipCode) {
addressLine2 += state ? ', ' : ' ';
}
}
if (state) {
addressLine2 += state;
addressLine2 += zipCode ? ' ' : '';
}
if (zipCode) {
addressLine2 += zipCode;
}
const addressLine1 = toTitleCase(provider?.address?.streetAddress);
const joinString = addressLine1.length > 0 && addressLine2.length > 0 ? ', ' : '';
return [addressLine1, addressLine2].join(joinString);
},
async getProviderButtonData() {
this.reloadingProviders = true;
const getTpaProvidersResult = await useMainStore().getTpaProviders(this.zipCode, this.radiusInMiles);
this.reloadingProviders = false;
return getTpaProvidersResult?.data?.shopProviders ?? [];
},
doNotSeeMyShopLinkClick() {
this.mainStore.setBailout(this.$router.currentRoute, bailoutMessage.doNotSeeMyShop());
this.$router.navigate(
this.navigationScenarios.CLICKED_DO_NOT_SEE_MY_SHOP_LINK,
this.$route
);
},
async searchClick() {
this.providers = await this.getProviderButtonData();
this.mapZipCode = this.zipCode;
},
forwardButtonAction() {
if (this.selectedProviderIsSafeliteShop) {
useMainStore().updateIsSafeliteProvider(true);
}
const scenario = this.selectedProviderIsSafeliteShop
? this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE_SHOP
: this.navigationScenarios.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route);
},
getCustomValueFromString(str) {
switch (str) {
case 'radiusInMiles':
return this.radiusInMiles;
default:
return null;
}
},
getShopButtonDataFromProvider(provider) {
const cellNumber = toDisplayPhoneNumber(provider?.phoneNumber);
const distance = provider?.distanceInMiles !== null && !Number.isNaN(parseFloat(provider?.distanceInMiles))
? +provider.distanceInMiles.toFixed(1)
: null;
return {
buttonLabel: provider?.companyName ?? '',
buttonLabelSubCopy: distance === null
? ''
: `${distance} mi`,
buttonBodyCopy: `${this.getProviderAddress(provider)}<br>${cellNumber ?? ''}`,
value: provider?.providerNumber ?? ''
};
}
}
};
</script>
<style lang="scss" scoped>
.darker-gray {
color: map-get($colors, "darker-gray");
}
</style>