DigitalConsumer.FixMyGlass/src/layouts/scheduling/scheduling.vue

310 lines
12 KiB
Vue

<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
<loadingModal notFullScreen ref="loadingModal" />
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
<div class="container page-container-grouped-styles">
<div class="row">
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
<h5 class="fw-normal mb-0 dark-header" :class="headerColor">
<span>
{{ serviceLocationText }}
</span>
</h5>
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" alignLeft />
<datePicker
class="mt-5"
v-model="selectedDate"
:startDate="datePickerStartDate"
:endDate="datePickerEndDate"
:availableDates="availableDates"
:isLoadingDates="isLoadingDates"
@requestMoreDates="handleRequestMoreDates" />
<mobileSchedulingCard
v-if="showMobileSchedulingCard"
class="mt-4"
v-model="selectedMobileScheduling"
:timeSlots="mobileTimeSlotsForSelectedDate"
:premiumTimeSlotPrice="premiumTimeSlotPrice"
:zipCode="serviceZipCode"
:showFreeFlag="showMobileFreeFlag"
@zip-code-clicked="onMobileZipCodeClicked" />
<inshopSchedulingCard
v-for="{ provider } in inShopProvidersAndTimeslots"
v-show="selectedDate"
:key="provider.providerNumber"
class="mt-4"
v-model="selectedInshopSchedulingByProvider[provider.providerNumber]"
:provider="provider"
:timeSlots="getInshopTimeSlotsForSelectedDate(provider.providerNumber)"
:radioGroupName="`inshopSchedulingTimeSlot-${provider.providerNumber}`"
@address-clicked="onInshopAddressClicked(provider)" />
<navbar
cmsWidgetName="FunnelFooterWidget"
ref="navbar"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</Form>
</template>
<script>
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import navbar from "@/fmg-components/nav-bar/nav-bar";
import { Form } from "vee-validate";
import datePicker from "@/layouts/scheduling/date-picker/date-picker";
import mobileSchedulingCard from "@/layouts/scheduling/mobile-scheduling-card/mobile-scheduling-card";
import inshopSchedulingCard from "@/layouts/scheduling/inshop-scheduling-card/inshop-scheduling-card";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import store from "@/store";
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import {
flushPagePrereqsLogs,
hasServiceZipInfo,
hasGlassPartsOrRepairInfo,
hasInsuranceInfo,
} from "@/helpers/page-prerequisites-helper.js";
import { debugLog } from "@/helpers/debug-log-helper";
/**
* Returns a YYYY-MM-DD date string offset by the given number of days from a base date.
* @param {number} offsetDays - Number of days to offset (positive or negative).
* @param {Date} base - The starting date (defaults to today).
*/
function toDateString(offsetDays, base = new Date()) {
const d = new Date(base);
d.setDate(d.getDate() + offsetDays);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
const SCHEDULE_FETCH_DAYS = 15;
export default {
name: "scheduling",
async beforeRouteEnter(to, from, next) {
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const shopProviderData = await store.dispatch("getProviders", {
payload: { serviceZipCode },
pageNameToLog: to.name,
});
// Get the first 3 providers from the shopProviderData
const providers = shopProviderData?.data?.shopProviders?.slice(0, 3) ?? [];
const mobileProviderNumber = shopProviderData?.data?.mobileProviderNumber ?? null;
const startDate = toDateString(0);
const endDate = toDateString(SCHEDULE_FETCH_DAYS - 1);
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: fetchCmsContentForPage(to.name),
},
{
resultKey: "mobilePremiumFee",
promise: store.dispatch("getMobilePremiumFee", {
pageNameToLog: to.name,
}),
},
...providers.map((provider, i) => ({
resultKey: `inshopTimeSlots_${i}`,
promise: store.dispatch("getShopTimeSlots", {
payload: {
startDate,
endDate,
shopAppointmentType: "InshopOrDropoff",
providerNumber: provider.providerNumber,
},
pageNameToLog: to.name,
}),
})),
// Get the mobile time slots if a mobile provider number is available
...(mobileProviderNumber
? [
{
resultKey: "mobileTimeSlots",
promise: store.dispatch("getMobileTimeSlots", {
payload: { startDate, endDate, zipCode: serviceZipCode },
pageNameToLog: to.name,
}),
},
]
: []),
];
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.inShopProvidersAndTimeslots = providers.map((provider, i) => ({
provider,
timeSlots: resultMap[`inshopTimeSlots_${i}`] ?? null,
}));
vm.mobileProviderAndTimeSlot = mobileProviderNumber
? {
providerNumber: mobileProviderNumber,
timeSlots: resultMap.mobileTimeSlots ?? null,
}
: null;
vm.datesLoaded = true;
vm.mobilePremiumAppointmentFee = resultMap.mobilePremiumFee ?? null;
});
},
watch: {
selectedDate() {
this.selectedMobileScheduling = null;
this.selectedInshopSchedulingByProvider = {};
},
},
computed: {
serviceLocationText() {
return this.getCmsContent("ServiceLocationText", "Text");
},
serviceZipCode() {
return store.getters.order.serviceLocation.zipCode;
},
showMobileSchedulingCard() {
return Boolean(this.mobileProviderAndTimeSlot && this.selectedDate);
},
mobileTimeSlotsForSelectedDate() {
if (!this.selectedDate) {
return [];
}
const day = this.mobileProviderAndTimeSlot?.timeSlots?.days?.find(
(d) => d.date === this.selectedDate
);
return day?.timeSlots ?? [];
},
premiumTimeSlotPrice() {
if (this.mobilePremiumAppointmentFee?.partType !== PREMIUM_FEE_PART_TYPE) {
return null;
}
return this.getTotalLineItemPrice(this.mobilePremiumAppointmentFee);
},
showMobileFreeFlag() {
return true;
},
availableDates() {
if (!this.datesLoaded) return null;
const inshopDates = this.inShopProvidersAndTimeslots.flatMap(({ timeSlots }) =>
(timeSlots?.days ?? []).map((d) => d.date)
);
const mobileDates = (this.mobileProviderAndTimeSlot?.timeSlots?.days ?? []).map(
(d) => d.date
);
return [...new Set([...inshopDates, ...mobileDates])].sort();
},
},
data() {
return {
selectedDate: null,
isLoadingDates: false,
datesLoaded: false,
datePickerStartDate: toDateString(0),
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
inShopProvidersAndTimeslots: [],
mobileProviderAndTimeSlot: null,
mobilePremiumAppointmentFee: null,
selectedMobileScheduling: null,
selectedInshopSchedulingByProvider: {},
};
},
methods: {
getInshopTimeSlotsForSelectedDate(providerNumber) {
if (!this.selectedDate) {
return [];
}
const providerEntry = this.inShopProvidersAndTimeslots.find(
({ provider }) => provider.providerNumber === providerNumber
);
const day = providerEntry?.timeSlots?.days?.find((d) => d.date === this.selectedDate);
return day?.timeSlots ?? [];
},
onInshopAddressClicked(provider) {
// TODO: open Google Maps when address link functionality is implemented
console.log("onInshopAddressClicked", provider?.providerNumber);
},
onMobileZipCodeClicked() {
// TODO: open service zip modal when zip edit is implemented for scheduling page
},
arePagePrerequisitesValid() {
const order = store.getters.order;
const logQueue = [];
const serviceZip = hasServiceZipInfo(order, logQueue);
const glassPartsOrRepair = hasGlassPartsOrRepairInfo(order, logQueue);
const insuranceInfo = hasInsuranceInfo(order, logQueue);
const preReqResult = serviceZip && insuranceInfo && glassPartsOrRepair;
return preReqResult;
},
backButtonAction() {
const payment = store.getters.order.payment;
if (
payment?.isInsurance &&
(payment?.insuranceCoverage?.isVerified ||
store.getters.order.referralNumber.length === 6)
) {
navigateToHeritageFunnel({
shouldSaveSession: false,
pageNameToLog: this.pageName,
navType: "back",
});
} else {
this.$router.navigateWithoutSaving(
this.navigationScenarios.CLICKED_BACK,
this.pageName
);
}
},
async handleRequestMoreDates() {
this.isLoadingDates = true;
this.$refs.loadingModal.showModal();
// TODO: call getShopTimeSlots with the next date range and append results
// to this.availableDates and advance this.datePickerEndDate
console.log("handleRequestMoreDates");
this.isLoadingDates = false;
this.$refs.loadingModal.hideModal();
},
forwardButtonAction() {
const appointmentType = store.getters.order.serviceLocation.appointmentType; // TODO: remove this once we have a proper appointment type
if (appointmentType === AppointmentTypeStrings.MOBILE) {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD_WITH_MOBILE_SERVICE,
this.pageName
);
} else {
this.$router.navigateWithSaving(
this.navigationScenarios.CLICKED_FORWARD,
this.pageName
);
}
},
},
components: {
funnelHeader,
funnelSubHeader,
navbar,
Form,
datePicker,
mobileSchedulingCard,
inshopSchedulingCard,
loadingModal,
},
};
</script>
<style lang="scss" scoped>
.dark-header {
color: $black;
}
h5 {
line-height: 32px;
font-size: 1.25rem;
}
</style>