Merged from develop
This commit is contained in:
commit
b8978be3d5
20 changed files with 486 additions and 381 deletions
|
|
@ -26,7 +26,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 80,
|
statements: 78,
|
||||||
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
|
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,14 @@ const endpoints = {
|
||||||
},
|
},
|
||||||
GetShopTimeSlots: {
|
GetShopTimeSlots: {
|
||||||
url: "/schedule/api/v1/schedule/shop-time-slots",
|
url: "/schedule/api/v1/schedule/shop-time-slots",
|
||||||
mockUrl: "https://mockey.qa.sagaws.net/service/shop-time-slots", // TODO: REMOVE MOCKURL
|
method: "POST",
|
||||||
|
},
|
||||||
|
GetMobileTimeSlots: {
|
||||||
|
url: "/schedule/api/v1/schedule/mobile-time-slots",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
GetMobileEarlyBirdFee: {
|
GetMobileEarlyBirdFee: {
|
||||||
url: "/parts/api/v1/parts/mobile-early-bird-fee",
|
url: "/parts/api/v1/parts/mobile-early-bird-fee",
|
||||||
mockUrl: "https://mockey.qa.sagaws.net/service/mobile-early-bird-fee", // TODO: REMOVE MOCKURL
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
SaveSession: {
|
SaveSession: {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ const storeActions = {
|
||||||
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
||||||
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
||||||
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
||||||
|
GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots",
|
||||||
GET_PROVIDERS: "getProviders",
|
GET_PROVIDERS: "getProviders",
|
||||||
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
|
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
|
||||||
SAVE_SESSION: "saveSession",
|
SAVE_SESSION: "saveSession",
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
modelValue(newValue, oldValue) {
|
modelValue(newValue, oldValue) {
|
||||||
this.resetField({
|
this.resetField({
|
||||||
value: newValue
|
value: newValue,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
answers() {
|
answers() {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
<div class="separator-line"></div>
|
<div class="separator-line"></div>
|
||||||
<div class="nav-back ps-3"><button></button></div>
|
<div class="nav-back ps-3"><button></button></div>
|
||||||
<div class="nav-forward pe-3"><button></button></div>
|
<div class="nav-forward pe-3"><button></button></div>
|
||||||
<!-- TODO Accessibility: Do the days of the week need to be read? -->
|
|
||||||
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
|
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
|
||||||
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
|
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
|
||||||
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
|
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
|
||||||
|
|
@ -84,7 +83,6 @@ export default {
|
||||||
months: null,
|
months: null,
|
||||||
disableViewMoreDatesButton: false,
|
disableViewMoreDatesButton: false,
|
||||||
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
||||||
today: null,
|
|
||||||
hideSomeDaysForInitialView: null,
|
hideSomeDaysForInitialView: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -112,6 +110,12 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
today() {
|
||||||
|
if (this.todayOverrideDateString) {
|
||||||
|
return new Date(this.todayOverrideDateString);
|
||||||
|
}
|
||||||
|
return new Date();
|
||||||
|
},
|
||||||
todayMonthIndex() {
|
todayMonthIndex() {
|
||||||
return this.today.getMonth() + 1;
|
return this.today.getMonth() + 1;
|
||||||
},
|
},
|
||||||
|
|
@ -151,39 +155,31 @@ export default {
|
||||||
this.$emit("date-clicked");
|
this.$emit("date-clicked");
|
||||||
},
|
},
|
||||||
getWeekStartDate(date) {
|
getWeekStartDate(date) {
|
||||||
// Get the day of the week for date
|
const dayOfWeek = date.getDay();
|
||||||
let dayOfWeek = date.getDay();
|
|
||||||
|
|
||||||
// Subtract the day of the week from date to get the date of Sunday
|
// Subtract the day of the week from date to get the date of Sunday
|
||||||
let sunday = new Date(date);
|
const sunday = new Date(date);
|
||||||
sunday.setDate(sunday.getDate() - dayOfWeek);
|
sunday.setDate(sunday.getDate() - dayOfWeek);
|
||||||
|
|
||||||
// Return the date of Sunday
|
|
||||||
return sunday;
|
return sunday;
|
||||||
},
|
},
|
||||||
getWeekEndDate(date) {
|
getWeekEndDate(date) {
|
||||||
const currentDay = date.getDay(); // Get the day of the week (0 = Sunday, 1 = Monday, etc.)
|
const dayOfWeek = date.getDay();
|
||||||
const daysUntilSaturday = 6 - currentDay; // Calculate the number of days until Saturday
|
const daysUntilSaturday = 6 - dayOfWeek; // Calculate the number of days until Saturday
|
||||||
|
|
||||||
// Clone the given date and add the remaining days until Saturday
|
// Clone the given date and add the remaining days until Saturday
|
||||||
const saturday = new Date(date);
|
const saturday = new Date(date);
|
||||||
saturday.setDate(date.getDate() + daysUntilSaturday);
|
saturday.setDate(date.getDate() + daysUntilSaturday);
|
||||||
|
|
||||||
return saturday;
|
return saturday;
|
||||||
},
|
},
|
||||||
getNextWeekSunday(date) {
|
getNextWeekSunday(date) {
|
||||||
const currentDay = date.getDay(); // Get the day of the week (0 = Sunday, 1 = Monday, etc.)
|
const dayOfWeek = date.getDay();
|
||||||
const daysUntilNextSunday = currentDay === 0 ? 7 : 7 - currentDay; // Calculate the number of days until the next Sunday
|
const daysUntilNextSunday = 7 - dayOfWeek; // Calculate the number of days until the next Sunday
|
||||||
|
|
||||||
// Clone the given date and add the remaining days until Sunday
|
// Clone the given date and add the remaining days until Sunday
|
||||||
const nextSunday = new Date(date);
|
const nextSunday = new Date(date);
|
||||||
nextSunday.setDate(date.getDate() + daysUntilNextSunday);
|
nextSunday.setDate(date.getDate() + daysUntilNextSunday);
|
||||||
|
|
||||||
return nextSunday;
|
return nextSunday;
|
||||||
},
|
},
|
||||||
getInitialViewWeeks(today, initialViewRowsToShow) {
|
getInitialViewWeeks(today, initialViewRowsToShow) {
|
||||||
// TODO: this only is for future direction; create logic for past direction
|
// TODO: this only is for future direction; need to create logic for past direction
|
||||||
let weeks = [];
|
const weeks = [];
|
||||||
let weekStartDate = this.getWeekStartDate(today);
|
let weekStartDate = this.getWeekStartDate(today);
|
||||||
let weekEndDate = this.getWeekEndDate(today);
|
let weekEndDate = this.getWeekEndDate(today);
|
||||||
for (let i = 0; i < initialViewRowsToShow; i++) {
|
for (let i = 0; i < initialViewRowsToShow; i++) {
|
||||||
|
|
@ -200,14 +196,19 @@ export default {
|
||||||
return weeks;
|
return weeks;
|
||||||
},
|
},
|
||||||
async loadInitialData(config) {
|
async loadInitialData(config) {
|
||||||
// CALLED FROM CONSUMING COMPONENT BEFORE DATE-PICKER APPEARS
|
let todayDate;
|
||||||
const todayDate = config.todayOverrideDateString
|
if (this.today) {
|
||||||
? new Date(config.todayOverrideDateString)
|
todayDate = this.today;
|
||||||
: new Date();
|
} else if (config.todayOverrideDateString) {
|
||||||
|
todayDate = new Date(config.todayOverrideDateString);
|
||||||
|
} else {
|
||||||
|
todayDate = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
let todayMonthIndex = todayDate.getMonth() + 1;
|
const todayMonthIndex = todayDate.getMonth() + 1;
|
||||||
let todayYearNum = todayDate.getFullYear();
|
const todayYearNum = todayDate.getFullYear();
|
||||||
let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
|
// TODO - set up currentMonthStart if direction is PAST:
|
||||||
|
// let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
|
||||||
let currentMonthEnd = new Date(todayYearNum, todayMonthIndex, 0);
|
let currentMonthEnd = new Date(todayYearNum, todayMonthIndex, 0);
|
||||||
|
|
||||||
let calendarViewDirection = "none";
|
let calendarViewDirection = "none";
|
||||||
|
|
@ -219,10 +220,10 @@ export default {
|
||||||
config.initialViewRowsToShow
|
config.initialViewRowsToShow
|
||||||
);
|
);
|
||||||
|
|
||||||
let initialViewStartDate = todayDate;
|
const initialViewStartDate = todayDate;
|
||||||
let initialViewEndDate = initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
|
const initialViewEndDate = initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
|
||||||
let saturday1month = initialViewWeeks[0].weekEndDate.getMonth();
|
const saturday1month = initialViewWeeks[0].weekEndDate.getMonth();
|
||||||
let sunday5month =
|
const sunday5month =
|
||||||
initialViewWeeks[initialViewWeeks.length - 1].weekStartDate.getMonth();
|
initialViewWeeks[initialViewWeeks.length - 1].weekStartDate.getMonth();
|
||||||
|
|
||||||
let hideSomeDaysForInitialView = false;
|
let hideSomeDaysForInitialView = false;
|
||||||
|
|
@ -244,9 +245,10 @@ export default {
|
||||||
|
|
||||||
let myPromise = new Promise((resolve, reject) => {
|
let myPromise = new Promise((resolve, reject) => {
|
||||||
const response = config.customSelectableDatesCallback(
|
const response = config.customSelectableDatesCallback(
|
||||||
initialViewStartDate,
|
initialViewStartDate.toISOString().split("T")[0],
|
||||||
initialViewEndDate,
|
initialViewEndDate.toISOString().split("T")[0],
|
||||||
store.getters.order.serviceLocation.appointmentType
|
store.getters.order.serviceLocation.appointmentType,
|
||||||
|
store.getters.order.serviceLocation.provider.providerNumber
|
||||||
);
|
);
|
||||||
resolve(response);
|
resolve(response);
|
||||||
});
|
});
|
||||||
|
|
@ -284,9 +286,9 @@ export default {
|
||||||
// Growing from 0 to 1
|
// Growing from 0 to 1
|
||||||
time = Math.min(1, (timestamp - start) / duration);
|
time = Math.min(1, (timestamp - start) / duration);
|
||||||
|
|
||||||
let percentageNew = timingFunc(time);
|
const percentageNew = timingFunc(time);
|
||||||
let distanceToGo = targetY;
|
const distanceToGo = targetY;
|
||||||
let thisDistance = percentageNew * distanceToGo;
|
const thisDistance = percentageNew * distanceToGo;
|
||||||
|
|
||||||
wrapper.scrollTo(0, initY + thisDistance);
|
wrapper.scrollTo(0, initY + thisDistance);
|
||||||
|
|
||||||
|
|
@ -305,13 +307,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async setCalendarData(config = {}) {
|
async setCalendarData(config = {}) {
|
||||||
this.today = config.todayDate;
|
|
||||||
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
|
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
|
||||||
let hideSecondMonth = config.hideSecondMonth;
|
const hideSecondMonth = config.hideSecondMonth;
|
||||||
const direction = config.calendarViewDirection;
|
const direction = config.calendarViewDirection;
|
||||||
|
const monthsAfterToLoadOffset = 12;
|
||||||
const monthsAfterToLoadOffset = 12; // TO BE MADE "CONSTANTS"
|
const monthsBeforeToLoadOffset = 36;
|
||||||
const monthsBeforeToLoadOffset = 36; // TO BE MADE "CONSTANTS"
|
|
||||||
config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
|
config.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
|
||||||
this.selectableDatesData.push(selectableDate);
|
this.selectableDatesData.push(selectableDate);
|
||||||
});
|
});
|
||||||
|
|
@ -382,25 +382,25 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthEndDate = new Date(yearNum, monthIndex, 0); // BOTH
|
const monthEndDate = new Date(yearNum, monthIndex, 0);
|
||||||
let monthEndDateNum = monthEndDate.getDate(); // BOTH
|
let monthEndDateNum = monthEndDate.getDate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
offset === 0 &&
|
offset === 0 &&
|
||||||
calendarViewDirection === "past" &&
|
calendarViewDirection === "past" &&
|
||||||
monthEndDateNum > this.currentWeekEndDateNum
|
monthEndDateNum > this.currentWeekEndDateNum
|
||||||
) {
|
) {
|
||||||
monthEndDateNum = this.currentWeekEndDateNum; // PAST
|
monthEndDateNum = this.currentWeekEndDateNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthStartDateNum =
|
const monthStartDateNum =
|
||||||
offset === 0 && calendarViewDirection === "future"
|
offset === 0 && calendarViewDirection === "future"
|
||||||
? this.currentWeekStartDateNum
|
? this.currentWeekStartDateNum
|
||||||
: 1; // FUTURE
|
: 1;
|
||||||
const monthStartDate = new Date(yearNum, monthIndex - 1, monthStartDateNum); // BOTH
|
const monthStartDate = new Date(yearNum, monthIndex - 1, monthStartDateNum);
|
||||||
|
|
||||||
const startDateDayIndex = monthStartDate.getDay(); // FUTURE
|
const startDateDayIndex = monthStartDate.getDay();
|
||||||
const endDateDayIndex = monthEndDate.getDay(); // PAST
|
const endDateDayIndex = monthEndDate.getDay();
|
||||||
|
|
||||||
if (Math.abs(offset) === 1 && hideSecondMonth) {
|
if (Math.abs(offset) === 1 && hideSecondMonth) {
|
||||||
monthClass = monthClass + " month-hidden";
|
monthClass = monthClass + " month-hidden";
|
||||||
|
|
@ -424,7 +424,7 @@ export default {
|
||||||
// populate dates array
|
// populate dates array
|
||||||
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
|
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
|
||||||
let dayClasses = "";
|
let dayClasses = "";
|
||||||
let dateString =
|
const dateString =
|
||||||
yearNum.toString() +
|
yearNum.toString() +
|
||||||
"-" +
|
"-" +
|
||||||
forceTwoDigitString(monthIndex) +
|
forceTwoDigitString(monthIndex) +
|
||||||
|
|
@ -514,7 +514,7 @@ export default {
|
||||||
monthToShow.dates[monthToShow.dates.length - 1].inputValue.dateString
|
monthToShow.dates[monthToShow.dates.length - 1].inputValue.dateString
|
||||||
);
|
);
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this removes hidden styling on days
|
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this will reveal those days
|
||||||
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
||||||
this.scrollToElement(monthToShow.monthString);
|
this.scrollToElement(monthToShow.monthString);
|
||||||
|
|
||||||
|
|
@ -526,7 +526,8 @@ export default {
|
||||||
const moreSelectableDates = await this.customSelectableDatesCallback(
|
const moreSelectableDates = await this.customSelectableDatesCallback(
|
||||||
monthStart,
|
monthStart,
|
||||||
monthEnd,
|
monthEnd,
|
||||||
this.$store.getters.order.serviceLocation.appointmentType
|
this.$store.getters.order.serviceLocation.appointmentType,
|
||||||
|
this.$store.getters.order.serviceLocation.provider.providerNumber
|
||||||
);
|
);
|
||||||
moreSelectableDates.days.forEach((selectableDate) => {
|
moreSelectableDates.days.forEach((selectableDate) => {
|
||||||
const index = this.selectableDatesData.findIndex(
|
const index = this.selectableDatesData.findIndex(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default {
|
||||||
name: "textBlock",
|
name: "textBlock",
|
||||||
props: {
|
props: {
|
||||||
customText: String, // used to allow the insert of token values into textblock
|
customText: String, // used to allow the insert of token values into textblock
|
||||||
justifyText: String, // left, right, center
|
justifyText: String, // right, center (left is default)
|
||||||
typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation)
|
typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation)
|
||||||
fontWeight: String, // bold=500, default is 400
|
fontWeight: String, // bold=500, default is 400
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
|
|
@ -28,15 +28,11 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.text-block {
|
.text-block {
|
||||||
display: flex;
|
|
||||||
&.left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
&.right {
|
&.right {
|
||||||
justify-content: flex-end;
|
text-align: right;
|
||||||
}
|
}
|
||||||
&.center {
|
&.center {
|
||||||
justify-content: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
&.bold {
|
&.bold {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import demoDatePicker from "./demo-date-picker";
|
|
||||||
|
|
||||||
// Supporting Files
|
|
||||||
import { shallowMount } from "@vue/test-utils";
|
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
|
||||||
|
|
||||||
describe("demo-date-picker.vue", () => {
|
|
||||||
test.only("test TK...", () => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setupMocks({ mountOptionsMockData = {} }) {
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
...mountOptionsMockData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = shallowMount(demoDatePicker, mountOptions);
|
|
||||||
|
|
||||||
return { wrapper };
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="page-container-grouped-styles">
|
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
|
||||||
<div class="">
|
|
||||||
<div class="rounded text-center">
|
|
||||||
<div class="fade-on-route-transition">
|
|
||||||
<date-picker
|
|
||||||
selectableDates="custom"
|
|
||||||
v-model="selectedDate"
|
|
||||||
:customSelectableDatesCallback="getAvailableDates" />
|
|
||||||
<!-- EXAMPLE THAT OVERRIDES TODAY BY PASSING STRING --
|
|
||||||
<date-picker
|
|
||||||
selectableDates="custom"
|
|
||||||
v-model="selectedDate"
|
|
||||||
:customSelectableDatesCallback="getAvailableDates"
|
|
||||||
todayOverrideDateString="2022-12-30T03:00:00" /> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Components
|
|
||||||
import datePicker from "@/digital-components/date-picker/date-picker";
|
|
||||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
|
||||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
|
||||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
|
||||||
|
|
||||||
// Supporting files
|
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
|
||||||
import { storeActions } from "@/constants/store-actions";
|
|
||||||
import store from "@/store";
|
|
||||||
|
|
||||||
import baseMixin from "@/mixins/base-mixin";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "demo-date-picker",
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedDate: null,
|
|
||||||
// selectedDate: { // USE THIS FORMAT FOR A PRE-SELECTED DATE ON LOAD
|
|
||||||
// year: 2023,
|
|
||||||
// month: 3, // use 1-based index for months
|
|
||||||
// date: 21,
|
|
||||||
// },
|
|
||||||
mockSelectableDatesData: [
|
|
||||||
{ year: 2023, month: 3, date: 4 },
|
|
||||||
{ year: 2023, month: 3, date: 5 },
|
|
||||||
{ year: 2023, month: 3, date: 22 },
|
|
||||||
{ year: 2023, month: 4, date: 13 },
|
|
||||||
{ year: 2023, month: 4, date: 7 },
|
|
||||||
{ year: 2023, month: 4, date: 14 },
|
|
||||||
{ year: 2023, month: 4, date: 26 },
|
|
||||||
{ year: 2023, month: 5, date: 21 },
|
|
||||||
{ year: 2023, month: 5, date: 23 },
|
|
||||||
{ year: 2023, month: 5, date: 25 },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
todayDate() {
|
|
||||||
const today = new Date();
|
|
||||||
return today.toDateString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
arePagePrerequisitesValid() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
getAvailableDates(startDate, endDate) {
|
|
||||||
this.mockSelectableDatesData.push(endDate);
|
|
||||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
// temporary test method that adds endDate to list of selectable dates
|
|
||||||
return this.mockSelectableDatesData;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
datePicker,
|
|
||||||
funnelHeader,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
// .page-container-grouped-styles {
|
|
||||||
// padding: 0 .5rem !important;
|
|
||||||
// }
|
|
||||||
</style>
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonTypeString="listButtonHorizontal"
|
buttonTypeString="listButtonHorizontal"
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
:additionalButtonData="{ additionalButtonStyling: 'listButtonHorizontalStrong' }"
|
:additionalButtonData="additionalButtonData"
|
||||||
isRequired />
|
isRequired />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -27,6 +27,11 @@ export default {
|
||||||
answersFromCms() {
|
answersFromCms() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "Answers");
|
return this.getCmsContent(this.cmsWidgetName, "Answers");
|
||||||
},
|
},
|
||||||
|
additionalButtonData() {
|
||||||
|
return {
|
||||||
|
additionalButtonStyling: "listButtonHorizontalStrong",
|
||||||
|
};
|
||||||
|
},
|
||||||
selectedValues: {
|
selectedValues: {
|
||||||
get: function () {
|
get: function () {
|
||||||
// Convert to CMS answer name from bool
|
// Convert to CMS answer name from bool
|
||||||
|
|
|
||||||
11
src/layouts/schedule/constants/schedule-constants.js
Normal file
11
src/layouts/schedule/constants/schedule-constants.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const AppointmentTypeStrings = {
|
||||||
|
IN_SHOP: "Inshop",
|
||||||
|
MOBILE: "Mobile",
|
||||||
|
DROP_OFF: "Dropoff",
|
||||||
|
};
|
||||||
|
|
||||||
|
const PREMIUM_TIME_SLOT_ID_FLAG = "-premium";
|
||||||
|
|
||||||
|
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
|
||||||
|
|
||||||
|
export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE };
|
||||||
|
|
@ -25,17 +25,17 @@
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||||
@date-clicked="openInshopTimeSlotsModal" />
|
@date-clicked="openInshopTimeSlotsModal" />
|
||||||
<!-- todayOverrideDateString="2023-08-06T03:00:00" -->
|
|
||||||
<time-slot-modal-question
|
<time-slot-modal-question
|
||||||
ref="timeSlotModalQuestion"
|
ref="timeSlotModalQuestion"
|
||||||
cmsWidgetName="TimeSlotModalQuestion"
|
cmsWidgetName="TimeSlotModalQuestion"
|
||||||
earlyBirdCmsWidgetName="EarlyBirdTimeSlotModal"
|
mobilePremiumCmsWidgetName="EarlyBirdTimeSlotModal"
|
||||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||||
v-model="selectedTimeSlotId"
|
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||||
|
v-model="selectedTimeSlotData"
|
||||||
@time-slot-modal-closed="timeSlotModalClosed"
|
@time-slot-modal-closed="timeSlotModalClosed"
|
||||||
:appointmentType="appointmentType"
|
:appointmentType="appointmentType"
|
||||||
:mobileEarlyBirdFee="mobileEarlyBirdFee"
|
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||||
:dateAndTimeSlotData="timeSlotsForSelectedDate"
|
:dateAndTimeSlotData="timeSlotsForSelectedDate"
|
||||||
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
||||||
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"
|
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"
|
||||||
|
|
@ -73,6 +73,7 @@ import {
|
||||||
getRouterLinkRouteFromCopy,
|
getRouterLinkRouteFromCopy,
|
||||||
getRouterLinkDisplayTextFromCopy,
|
getRouterLinkDisplayTextFromCopy,
|
||||||
} from "@/helpers/cms-content-helper";
|
} from "@/helpers/cms-content-helper";
|
||||||
|
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "./constants/schedule-constants";
|
||||||
import { errorMessages } from "@/constants/error-messages";
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
@ -80,18 +81,33 @@ import store from "@/store";
|
||||||
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
||||||
defineRule("time-slot-selection-required", required(errorMessages.DATE_REQUIRED));
|
defineRule("time-slot-selection-required", required(errorMessages.DATE_REQUIRED));
|
||||||
|
|
||||||
const getAvailableDates = async (startDate, endDate, appointmentType) => {
|
const getAvailableDates = async (startDate, endDate, appointmentType, providerNumber) => {
|
||||||
// USING DATES PASSED, MAKE AN API CALL
|
// USING DATES PASSED, MAKE AN API CALL
|
||||||
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
|
||||||
storeActions.GET_SHOP_TIME_SLOTS,
|
let newTimeSlotsResponse;
|
||||||
{
|
if (appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
startDate: startDate,
|
newTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||||
endDate: endDate,
|
storeActions.GET_MOBILE_TIME_SLOTS,
|
||||||
shopAppointmentType: appointmentType,
|
{
|
||||||
},
|
startDate: startDate,
|
||||||
false
|
endDate: endDate,
|
||||||
);
|
},
|
||||||
const newShopTimeSlots = newShopTimeSlotsResponse.data;
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.GET_SHOP_TIME_SLOTS,
|
||||||
|
{
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
shopAppointmentType: appointmentType,
|
||||||
|
providerNumber: providerNumber,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newShopTimeSlots = newTimeSlotsResponse.data;
|
||||||
return convertApiResponse(newShopTimeSlots);
|
return convertApiResponse(newShopTimeSlots);
|
||||||
};
|
};
|
||||||
const convertApiResponse = (responseData) => {
|
const convertApiResponse = (responseData) => {
|
||||||
|
|
@ -112,9 +128,12 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
selectedTimeSlotId: null,
|
selectedTimeSlotData: {
|
||||||
|
id: null,
|
||||||
|
isPremiumAppointment: null,
|
||||||
|
},
|
||||||
selectableDatesData: [],
|
selectableDatesData: [],
|
||||||
mobileEarlyBirdFee: null,
|
mobilePremiumAppointmentFee: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -125,40 +144,29 @@ export default {
|
||||||
selectableDatesSetting: "custom",
|
selectableDatesSetting: "custom",
|
||||||
initialViewRowsToShow: 5,
|
initialViewRowsToShow: 5,
|
||||||
customSelectableDatesCallback: getAvailableDates,
|
customSelectableDatesCallback: getAvailableDates,
|
||||||
|
|
||||||
/* vvvvv SAVE THESE FOR TESTING PURPOSES FOR NOW vvvvv
|
|
||||||
|
|
||||||
// todayOverrideDateString: "2023-04-29T03:00:00", // show partial
|
|
||||||
// todayOverrideDateString: "2023-04-30T03:00:00", //
|
|
||||||
// todayOverrideDateString: "2023-05-02T03:00:00", // ONE MONTH ONLY
|
|
||||||
// todayOverrideDateString: "2023-05-06T03:00:00", // ONE MONTH ONLY
|
|
||||||
// todayOverrideDateString: "2023-05-07T03:00:00", // show partial
|
|
||||||
// todayOverrideDateString: "2023-05-30T03:00:00", //
|
|
||||||
// todayOverrideDateString: "2023-06-30T03:00:00", //
|
|
||||||
// todayOverrideDateString: "2023-07-01T03:00:00", // show partial && ONE MONTH ONLY
|
|
||||||
// todayOverrideDateString: "2023-07-02T03:00:00", // ONE MONTH ONLY
|
|
||||||
// todayOverrideDateString: "2023-07-12T03:00:00", // show partial
|
|
||||||
// todayOverrideDateString: "2023-08-31T03:00:00",
|
|
||||||
// todayOverrideDateString: "2023-09-30T03:00:00", // show partial
|
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Price EARLY BIRD pre-emptively to allow for asynchronous call to pricing
|
// Price EARLY BIRD pre-emptively to allow for asynchronous call to pricing
|
||||||
const pricingPromise = baseMixin.methods.dispatchStoreAction(
|
const pricingPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||||
{
|
{
|
||||||
availableLineItems: [{ partNumber: "EARLY BIRD" }],
|
availableLineItems: [
|
||||||
|
{
|
||||||
|
partNumber: PREMIUM_FEE_PART_TYPE,
|
||||||
|
partType: PREMIUM_FEE_PART_TYPE,
|
||||||
|
description: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const earlyBirdPromise = baseMixin.methods.dispatchStoreAction(
|
const premiumFeePromise = baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.GET_MOBILE_EARLY_BIRD_FEE
|
storeActions.GET_MOBILE_EARLY_BIRD_FEE
|
||||||
);
|
);
|
||||||
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
|
const alertReasonsPromise = locationAlerts.methods.loadInitialData(
|
||||||
store.getters.order.serviceLocation.zipCodeCtu
|
store.getters.order.serviceLocation.zipCodeCtu
|
||||||
);
|
);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
|
|
@ -174,8 +182,8 @@ export default {
|
||||||
promise: datePickerInitialDataPromise,
|
promise: datePickerInitialDataPromise,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
resultKey: "earlyBird",
|
resultKey: "premiumFee",
|
||||||
promise: earlyBirdPromise,
|
promise: premiumFeePromise,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
resultKey: "pricingResults",
|
resultKey: "pricingResults",
|
||||||
|
|
@ -191,8 +199,8 @@ export default {
|
||||||
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
|
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
|
||||||
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons);
|
||||||
vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse;
|
vm.selectableDatesData = resultMap.datePickerInitialData.initialShopTimeSlotsResponse;
|
||||||
if (resultMap.earlyBird) {
|
if (resultMap.premiumFee) {
|
||||||
vm.mobileEarlyBirdFee = resultMap.pricingResults[0];
|
vm.mobilePremiumAppointmentFee = resultMap.pricingResults[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -216,18 +224,18 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
appointmentDateAndTime() {
|
appointmentDateAndTime() {
|
||||||
if (!this.selectedTimeSlotId) {
|
if (!this.selectedTimeSlotData.id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId(
|
const timeSlotSelectedObject = this.getTimeSlotObjectFromTimeSlotId(
|
||||||
this.selectedTimeSlotId
|
this.selectedTimeSlotData.id
|
||||||
);
|
);
|
||||||
if (timeSlotSelectedObject) {
|
if (timeSlotSelectedObject) {
|
||||||
return {
|
return {
|
||||||
date: this.selectedDate.dateString,
|
date: this.selectedDate.dateString,
|
||||||
startTime: timeSlotSelectedObject.startTime,
|
startTime: timeSlotSelectedObject.startTime,
|
||||||
endTime: timeSlotSelectedObject.endTime,
|
endTime: timeSlotSelectedObject.endTime,
|
||||||
id: this.selectedTimeSlotId,
|
id: this.selectedTimeSlotData.id,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -247,7 +255,8 @@ export default {
|
||||||
const newShopTimeSlots = await getAvailableDates(
|
const newShopTimeSlots = await getAvailableDates(
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
this.appointmentType
|
this.appointmentType,
|
||||||
|
this.$store.getters.order.serviceLocation.provider.providerNumber
|
||||||
);
|
);
|
||||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||||
this.selectableDatesData.days = this.selectableDatesData.days.concat(
|
this.selectableDatesData.days = this.selectableDatesData.days.concat(
|
||||||
|
|
@ -268,11 +277,63 @@ export default {
|
||||||
return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId);
|
return timeSlots.find((timeSlot) => timeSlot.id === timeSlotId);
|
||||||
},
|
},
|
||||||
timeSlotModalClosed() {
|
timeSlotModalClosed() {
|
||||||
// Clear the selectedDate if no timeslot has been selected
|
// Clear the selectedDate if no timeSlot has been selected
|
||||||
if (!this.selectedTimeSlotId) {
|
if (!this.selectedTimeSlotData.id) {
|
||||||
this.selectedDate = null;
|
this.selectedDate = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateFooterButtonText(timeSlotData) {
|
||||||
|
let funnelFooterButtonText;
|
||||||
|
if (!timeSlotData.id) {
|
||||||
|
funnelFooterButtonText = "Continue";
|
||||||
|
} else {
|
||||||
|
funnelFooterButtonText =
|
||||||
|
"Select " + this.convertSelectedDateToShortMonthAndDay(this.selectedDate);
|
||||||
|
|
||||||
|
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||||
|
funnelFooterButtonText +=
|
||||||
|
" at " +
|
||||||
|
this.getDisplayTextForMilitaryTime(this.appointmentDateAndTime.startTime);
|
||||||
|
} else if (
|
||||||
|
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||||
|
!timeSlotData.isPremiumAppointment
|
||||||
|
) {
|
||||||
|
funnelFooterButtonText +=
|
||||||
|
" at " +
|
||||||
|
this.getDisplayTextForMilitaryTime(
|
||||||
|
this.appointmentDateAndTime.startTime,
|
||||||
|
true
|
||||||
|
) +
|
||||||
|
" - " +
|
||||||
|
this.getDisplayTextForMilitaryTime(
|
||||||
|
this.appointmentDateAndTime.endTime,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
|
||||||
|
},
|
||||||
|
convertSelectedDateToShortMonthAndDay(selectedDate) {
|
||||||
|
// This conversion ensures we don't get get GMT induced date changes
|
||||||
|
const dateObject = new Date(`${selectedDate.dateString}T00:00:00`);
|
||||||
|
// Ex: April 25
|
||||||
|
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
|
||||||
|
},
|
||||||
|
// Expected input: "HH:MM:SS"
|
||||||
|
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
|
||||||
|
let hours = parseInt(militaryTimeInput.split(":")[0]);
|
||||||
|
const minutes = militaryTimeInput.split(":")[1];
|
||||||
|
let meridianNotation = hours > 11 ? "PM" : "AM";
|
||||||
|
if (hours > 12) {
|
||||||
|
hours -= 12;
|
||||||
|
}
|
||||||
|
if (shouldTrimMinutesIfEmpty && minutes === "00") {
|
||||||
|
return `${hours} ${meridianNotation}`;
|
||||||
|
} else {
|
||||||
|
return `${hours}:${minutes} ${meridianNotation}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
},
|
},
|
||||||
|
|
@ -296,9 +357,15 @@ export default {
|
||||||
selectedDate(newValue, oldValue) {
|
selectedDate(newValue, oldValue) {
|
||||||
// Clear time slot selection if date selected changes
|
// Clear time slot selection if date selected changes
|
||||||
if (newValue !== oldValue) {
|
if (newValue !== oldValue) {
|
||||||
this.selectedTimeSlotId = null;
|
this.selectedTimeSlotData = {
|
||||||
|
id: null,
|
||||||
|
isPremiumAppointment: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedTimeSlotData() {
|
||||||
|
this.updateFooterButtonText(this.selectedTimeSlotData);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
|
|
|
||||||
|
|
@ -6,45 +6,32 @@
|
||||||
<div
|
<div
|
||||||
:aria-label="buttonLabel"
|
:aria-label="buttonLabel"
|
||||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
<span class="m-0" :class="textPosition">
|
<span class="m-0 position-relative" :class="textPosition">
|
||||||
{{ buttonLabel }}
|
{{ buttonLabel }}
|
||||||
</span>
|
<span
|
||||||
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">
|
v-if="buttonLabelSubCopy"
|
||||||
{{ formattedButtonLabelSubCopy }}
|
class="premium-appointment-price"
|
||||||
|
:class="textPosition">
|
||||||
|
{{ formattedButtonLabelSubCopy }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="screenReaderOnlyText" class="sr-only">
|
<span v-if="screenReaderOnlyText" class="sr-only">
|
||||||
{{ screenReaderOnlyText }}
|
{{ screenReaderOnlyText }}
|
||||||
</span>
|
</span>
|
||||||
<loader
|
|
||||||
v-if="isLoaderDisplayed && selectingInitiatesLoad"
|
|
||||||
:class="[this.loaderColor, this.loaderPosition]" />
|
|
||||||
</div>
|
</div>
|
||||||
</baseInputButton>
|
</baseInputButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import loader from "@/ux-components/loader/loader";
|
|
||||||
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||||
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "timeslotMOdalListButton",
|
name: "timeSlotModalListButton",
|
||||||
mixins: [inputButtonWrapperMixin],
|
mixins: [inputButtonWrapperMixin],
|
||||||
props: {
|
|
||||||
loaderColor: String,
|
|
||||||
loaderPosition: {
|
|
||||||
type: String,
|
|
||||||
default: "right",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isLoaderDisplayed: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
formattedButtonLabelSubCopy() {
|
formattedButtonLabelSubCopy() {
|
||||||
return this.buttonLabelSubCopy?.toFixed(2);
|
return this.buttonLabelSubCopy;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -58,16 +45,12 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
loader,
|
|
||||||
baseInputButton,
|
baseInputButton,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.loader {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.list-button {
|
.list-button {
|
||||||
outline: none;
|
outline: none;
|
||||||
input[type="radio"],
|
input[type="radio"],
|
||||||
|
|
@ -85,6 +68,9 @@ export default {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: $blue-100;
|
background: $blue-100;
|
||||||
box-shadow: 0 0 0 1px $blue;
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
span.premium-appointment-price {
|
||||||
|
background: $green-200;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:checked:focus + .list-button-content {
|
&:checked:focus + .list-button-content {
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
|
@ -109,11 +95,20 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
span {
|
span.premium-appointment-price {
|
||||||
&.small {
|
position: absolute;
|
||||||
font-size: 0.75rem;
|
background: $green-100;
|
||||||
color: $gray-550;
|
border-radius: 4.5rem;
|
||||||
}
|
line-height: 1.25rem;
|
||||||
|
color: $green-700;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-left: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.position-relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
typeStyle="small"
|
typeStyle="small"
|
||||||
class="duration-text-block" />
|
class="duration-text-block" />
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
buttonTypeString="timeslotModalListButton"
|
buttonTypeString="timeSlotModalListButton"
|
||||||
:buttonTypeObject="timeslotModalListButton"
|
:buttonTypeObject="timeSlotModalListButton"
|
||||||
:answers="availableTimeSlots"
|
:answers="availableTimeSlots"
|
||||||
groupName="ChooseTimeSlot"
|
groupName="ChooseTimeSlot"
|
||||||
textPosition="text-center"
|
textPosition="text-center"
|
||||||
v-model="selectedTimeSlot"
|
v-model="selectedTimeSlotId"
|
||||||
isRequired
|
isRequired
|
||||||
validationRules="time-slot-required"
|
validationRules="time-slot-required"
|
||||||
class="mt-5" />
|
class="mt-5" />
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
import modal from "@/digital-components/modal/modal";
|
import modal from "@/digital-components/modal/modal";
|
||||||
import textBlock from "@/digital-components/text-block/text-block";
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
import timeslotModalListButton from "./timeslot-modal-list-button/timeslot-modal-list-button";
|
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
|
||||||
|
|
||||||
// TODO: Move this somewhere more global
|
// TODO: Move this somewhere more global
|
||||||
import { DAYS_OF_WEEK, MONTHS_OF_YEAR } from "@/digital-components/date-picker/mixins/constants.js";
|
import { DAYS_OF_WEEK, MONTHS_OF_YEAR } from "@/digital-components/date-picker/mixins/constants.js";
|
||||||
|
|
@ -49,15 +49,17 @@ import { defineRule, useField } from "vee-validate";
|
||||||
import { errorMessages } from "@/constants/error-messages";
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import {
|
||||||
|
AppointmentTypeStrings,
|
||||||
|
PREMIUM_TIME_SLOT_ID_FLAG,
|
||||||
|
PREMIUM_FEE_PART_TYPE,
|
||||||
|
} from "../constants/schedule-constants";
|
||||||
|
|
||||||
// Validation for the modal button
|
// Validation for the modal button
|
||||||
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
|
defineRule("time-slot-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const AppointmentTypeStrings = {
|
|
||||||
IN_SHOP: "Inshop",
|
|
||||||
MOBILE: "Mobile",
|
|
||||||
DROP_OFF: "Dropoff",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "timeSlotModalQuestion",
|
name: "timeSlotModalQuestion",
|
||||||
|
|
@ -65,19 +67,21 @@ export default {
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
mobileCmsWidgetName: String,
|
mobileCmsWidgetName: String,
|
||||||
earlyBirdCmsWidgetName: String,
|
mobilePremiumCmsWidgetName: String,
|
||||||
dropoffCmsWidgetName: String,
|
dropoffCmsWidgetName: String,
|
||||||
|
sameDayDropOffCmsWidgetName: String,
|
||||||
appointmentType: String,
|
appointmentType: String,
|
||||||
dateAndTimeSlotData: Object,
|
dateAndTimeSlotData: Object,
|
||||||
mobileEarlyBirdFee: Object,
|
premiumAppointmentFee: Object,
|
||||||
estimatedServiceMinutesMinimum: Number,
|
estimatedServiceMinutesMinimum: Number,
|
||||||
estimatedServiceMinutesMaximum: Number,
|
estimatedServiceMinutesMaximum: Number,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTimeSlot: null,
|
selectedTimeSlotId: null,
|
||||||
timeslotModalListButton: timeslotModalListButton,
|
isSelectedAppointmentPremium: null,
|
||||||
|
timeSlotModalListButton: timeSlotModalListButton,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|
@ -89,42 +93,35 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue() {
|
modelValue() {
|
||||||
this.selectedTimeSlot = this.modelValue;
|
|
||||||
// Run component validation that is used at parent level
|
// Run component validation that is used at parent level
|
||||||
this.handleChange(this.modelValue);
|
this.handleChange(this.modelValue.id);
|
||||||
|
},
|
||||||
|
dateAndTimeSlotData(newValue, oldValue) {
|
||||||
|
this.autoSelectTimeSlotIfOnlyOneIsAvailable(newValue);
|
||||||
},
|
},
|
||||||
// dateAndTimeSlotData(newValue, oldValue) {
|
|
||||||
// const numberOfOptions = newValue?.timeSlots.length;
|
|
||||||
// console.log('running');
|
|
||||||
// if (numberOfOptions === 1) {
|
|
||||||
// this.selectedTimeSlot = newValue.timeSlots[0].id;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
supplementalInformationBlock() {
|
supplementalInformationBlock() {
|
||||||
let appointmentTypeCmsWidgetName;
|
let appointmentTypeCmsWidgetName;
|
||||||
let cmsFieldName = "BodyText";
|
|
||||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||||
return null;
|
return null;
|
||||||
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
appointmentTypeCmsWidgetName =
|
appointmentTypeCmsWidgetName =
|
||||||
this.selectedTimeSlot === this.earlyBirdButtonText
|
this.selectedTimeSlotId === this.premiumAppointmentButtonText
|
||||||
? this.earlyBirdCmsWidgetName
|
? this.mobilePremiumCmsWidgetName
|
||||||
: this.mobileCmsWidgetName;
|
: this.mobileCmsWidgetName;
|
||||||
} else {
|
} else {
|
||||||
appointmentTypeCmsWidgetName = this.dropoffCmsWidgetName;
|
appointmentTypeCmsWidgetName = this.isSameDay
|
||||||
if (this.isSameDay) {
|
? this.sameDayDropOffCmsWidgetName
|
||||||
cmsFieldName = "BodyText2";
|
: this.dropoffCmsWidgetName;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this.getCmsContent(appointmentTypeCmsWidgetName, cmsFieldName);
|
return this.getCmsContent(appointmentTypeCmsWidgetName, "BodyText");
|
||||||
},
|
},
|
||||||
footerCloseButtonText() {
|
footerCloseButtonText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||||
},
|
},
|
||||||
earlyBirdButtonText() {
|
premiumAppointmentButtonText() {
|
||||||
return this.getCmsContent(this.earlyBirdCmsWidgetName, "HeaderText");
|
return this.getCmsContent(this.mobilePremiumCmsWidgetName, "HeaderText");
|
||||||
},
|
},
|
||||||
dropoffButtonText() {
|
dropoffButtonText() {
|
||||||
return this.getCmsContent(this.dropoffCmsWidgetName, "HeaderText");
|
return this.getCmsContent(this.dropoffCmsWidgetName, "HeaderText");
|
||||||
|
|
@ -167,53 +164,24 @@ export default {
|
||||||
}
|
}
|
||||||
// This conversion ensures we don't get get GMT induced date changes
|
// This conversion ensures we don't get get GMT induced date changes
|
||||||
const dateObject = new Date(`${this.dateAndTimeSlotData.dateString}T00:00:00`);
|
const dateObject = new Date(`${this.dateAndTimeSlotData.dateString}T00:00:00`);
|
||||||
const weekdayName = DAYS_OF_WEEK[dateObject.getDay()];
|
// Ex: Tuesday, April 22
|
||||||
const month = MONTHS_OF_YEAR[dateObject.getMonth()];
|
return dateObject.toLocaleDateString("en-us", {
|
||||||
const numberDayOfMonth = dateObject.getDate();
|
weekday: "long",
|
||||||
// Ex. Tuesday, April 23
|
month: "long",
|
||||||
return `${weekdayName}, ${month} ${numberDayOfMonth}`;
|
day: "numeric",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
availableTimeSlots() {
|
availableTimeSlots() {
|
||||||
if (this.dateAndTimeSlotData === null) {
|
if (this.dateAndTimeSlotData === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let availableTimeSlots;
|
|
||||||
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||||
availableTimeSlots = [
|
return this.getAvailableTimeSlotsForDropOff(this.dateAndTimeSlotData.timeSlots);
|
||||||
{
|
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||||
value: this.dateAndTimeSlotData.timeSlots[0],
|
return this.getAvailableTimeSlotsForMobile(this.dateAndTimeSlotData.timeSlots);
|
||||||
buttonLabel: this.dropoffButtonText,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
availableTimeSlots = this.dateAndTimeSlotData.timeSlots.map((timeslot) => {
|
return this.getAvailableTimeSlotsForInshop(this.dateAndTimeSlotData.timeSlots);
|
||||||
let readableTime;
|
|
||||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
|
||||||
readableTime = this.getDisplayTextForMilitaryTime(timeslot.startTime);
|
|
||||||
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
|
||||||
readableTime = `${this.getDisplayTextForMilitaryTime(
|
|
||||||
timeslot.startTime
|
|
||||||
)} - ${this.getDisplayTextForMilitaryTime(timeslot.endTime)}`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: timeslot.id,
|
|
||||||
buttonLabel: readableTime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
|
||||||
const offerPremium = this.dateAndTimeSlotData.timeSlots[0].offerPremium;
|
|
||||||
const hasEarlyBird = this.mobileEarlyBirdFee?.partType === "EARLY BIRD";
|
|
||||||
if (offerPremium && hasEarlyBird) {
|
|
||||||
availableTimeSlots.unshift({
|
|
||||||
value: this.dateAndTimeSlotData.timeSlots[0].id + "-earlybird",
|
|
||||||
buttonLabel: this.earlyBirdButtonText,
|
|
||||||
buttonLabelSubCopy: this.getTotalLineItemPrice(this.mobileEarlyBirdFee),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return availableTimeSlots;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -222,22 +190,36 @@ export default {
|
||||||
},
|
},
|
||||||
// fires any time the footer button is used, is fired before "onModalClosed"
|
// fires any time the footer button is used, is fired before "onModalClosed"
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.$emit("update:modelValue", this.selectedTimeSlot);
|
if (this.selectedTimeSlotId.toString().includes(PREMIUM_TIME_SLOT_ID_FLAG)) {
|
||||||
|
this.selectedTimeSlotId = this.removePremiumFlagFromInput(this.selectedTimeSlotId);
|
||||||
|
this.isSelectedAppointmentPremium = true;
|
||||||
|
} else {
|
||||||
|
this.isSelectedAppointmentPremium = false;
|
||||||
|
}
|
||||||
|
const selectedTimeSlotData = {
|
||||||
|
id: this.selectedTimeSlotId,
|
||||||
|
isPremiumAppointment: this.isSelectedAppointmentPremium,
|
||||||
|
};
|
||||||
|
this.$emit("update:modelValue", selectedTimeSlotData);
|
||||||
this.$refs["timeSlots"].closeModal();
|
this.$refs["timeSlots"].closeModal();
|
||||||
},
|
},
|
||||||
// fires any time the modal is closed, AFTER "closeModal" fires if footer button is used
|
// fires any time the modal is closed, AFTER "closeModal" fires if footer button is used
|
||||||
onModalClosed() {
|
onModalClosed() {
|
||||||
this.selectedTimeSlot = this.modelValue;
|
this.isSelectedAppointmentPremium = this.modelValue.isPremiumAppointment;
|
||||||
|
if (this.isSelectedAppointmentPremium) {
|
||||||
|
this.selectedTimeSlotId = this.addPremiumFlagToInput(this.modelValue.id);
|
||||||
|
} else {
|
||||||
|
this.selectedTimeSlotId = this.modelValue.id;
|
||||||
|
}
|
||||||
this.$emit("time-slot-modal-closed");
|
this.$emit("time-slot-modal-closed");
|
||||||
},
|
},
|
||||||
// Expected input: "HH:MM:SS"
|
// Expected input: "HH:MM:SS"
|
||||||
getDisplayTextForMilitaryTime(militaryTimeInput) {
|
getDisplayTextForMilitaryTime(militaryTimeInput) {
|
||||||
let hours = parseInt(militaryTimeInput.split(":")[0]);
|
let hours = parseInt(militaryTimeInput.split(":")[0]);
|
||||||
const minutes = militaryTimeInput.split(":")[1];
|
const minutes = militaryTimeInput.split(":")[1];
|
||||||
let meridianNotation = "AM";
|
let meridianNotation = hours > 11 ? "PM" : "AM";
|
||||||
if (militaryTimeInput.split(":")[0] > 12) {
|
if (hours > 12) {
|
||||||
hours -= 12;
|
hours -= 12;
|
||||||
meridianNotation = "PM";
|
|
||||||
}
|
}
|
||||||
return `${hours}:${minutes} ${meridianNotation}`;
|
return `${hours}:${minutes} ${meridianNotation}`;
|
||||||
},
|
},
|
||||||
|
|
@ -252,6 +234,77 @@ export default {
|
||||||
}
|
}
|
||||||
return displayTextForDurationLength;
|
return displayTextForDurationLength;
|
||||||
},
|
},
|
||||||
|
getAvailableTimeSlotsForInshop(timeSlotsForSelectedDate) {
|
||||||
|
return timeSlotsForSelectedDate.map((timeSlot) => {
|
||||||
|
const readableTime = this.getDisplayTextForMilitaryTime(timeSlot.startTime);
|
||||||
|
return {
|
||||||
|
value: timeSlot.id,
|
||||||
|
buttonLabel: readableTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getAvailableTimeSlotsForDropOff(timeSlotsForSelectedDate) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: timeSlotsForSelectedDate[0].id,
|
||||||
|
buttonLabel: this.dropoffButtonText,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
getAvailableTimeSlotsForMobile(timeSlotsForSelectedDate) {
|
||||||
|
const availableTimeSlots = timeSlotsForSelectedDate.map((timeSlot) => {
|
||||||
|
const readableTime = `${this.getDisplayTextForMilitaryTime(
|
||||||
|
timeSlot.startTime
|
||||||
|
)} - ${this.getDisplayTextForMilitaryTime(timeSlot.endTime)}`;
|
||||||
|
return {
|
||||||
|
value: timeSlot.id,
|
||||||
|
buttonLabel: readableTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const isPremiumTimeSlot = timeSlotsForSelectedDate[0].offerPremium;
|
||||||
|
const hasPremiumPartAvailable =
|
||||||
|
this.premiumAppointmentFee?.partType === PREMIUM_FEE_PART_TYPE;
|
||||||
|
if (isPremiumTimeSlot && hasPremiumPartAvailable) {
|
||||||
|
availableTimeSlots.unshift(
|
||||||
|
this.getPremiumAppointmentTimeSlot(timeSlotsForSelectedDate[0])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableTimeSlots;
|
||||||
|
},
|
||||||
|
getPremiumAppointmentTimeSlot(timeSlotData) {
|
||||||
|
const formattedPrice =
|
||||||
|
"+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2);
|
||||||
|
return {
|
||||||
|
// Unique value is required for each <input> and the premium appoinment shares a timeSlot ID
|
||||||
|
value: this.addPremiumFlagToInput(timeSlotData.id),
|
||||||
|
buttonLabel: this.premiumAppointmentButtonText,
|
||||||
|
buttonLabelSubCopy: formattedPrice,
|
||||||
|
additionalButtonData: {
|
||||||
|
isPremiumAppointment: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addPremiumFlagToInput(timeSlotId) {
|
||||||
|
return (timeSlotId += PREMIUM_TIME_SLOT_ID_FLAG);
|
||||||
|
},
|
||||||
|
removePremiumFlagFromInput(timeSlotId) {
|
||||||
|
return parseInt(timeSlotId.trim(PREMIUM_TIME_SLOT_ID_FLAG.length));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
modal,
|
modal,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||||
return Promise.resolve(serviceabilityDetails);
|
return Promise.resolve(serviceabilityDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAvailabilityRating(providerNumber, startDate, endDate, appointmentType) {
|
export async function getAvailabilityRating(startDate, endDate, shopAppointmentType, providerNumber) {
|
||||||
// For a given shop provider number and date range, get the appointment time slots available
|
// For a given shop provider number and date range, get the appointment time slots available
|
||||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreAction(
|
const shopTimeSlots = await baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.GET_SHOP_TIME_SLOTS,
|
storeActions.GET_SHOP_TIME_SLOTS,
|
||||||
|
|
@ -52,7 +52,7 @@ export async function getAvailabilityRating(providerNumber, startDate, endDate,
|
||||||
providerNumber: providerNumber,
|
providerNumber: providerNumber,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
shopAppointmentType: appointmentType,
|
shopAppointmentType: shopAppointmentType,
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,17 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async setMobileLocation() {
|
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 (
|
if (
|
||||||
this.internalModel.addressQuestions.zipCode !==
|
this.internalModel.addressQuestions.zipCode !==
|
||||||
this.modelValue.addressQuestions.zipCode
|
this.modelValue.addressQuestions.zipCode
|
||||||
|
|
@ -270,6 +281,13 @@ export default {
|
||||||
|
|
||||||
this.closeModal();
|
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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,14 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
loaderColor: String,
|
loaderColor: String,
|
||||||
},
|
},
|
||||||
async beforeMount() {
|
beforeMount() {
|
||||||
this.displayLoader();
|
this.displayLoader();
|
||||||
this.additionalButtonData.availabilityRatingCallback(this.modelValue).then((data) => {
|
console.log(this.value)
|
||||||
|
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.availabilityRating = data;
|
||||||
this.isLoaderDisplayed = false;
|
this.isLoaderDisplayed = false;
|
||||||
});
|
});
|
||||||
|
|
@ -66,7 +71,7 @@ export default {
|
||||||
availabilityRating: "None",
|
availabilityRating: "None",
|
||||||
availabilityRatingClass: "",
|
availabilityRatingClass: "",
|
||||||
loaderPosition: "left",
|
loaderPosition: "left",
|
||||||
blockUi: "no-block"
|
blockUi: "no-block",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,18 @@ export default {
|
||||||
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||||
},
|
},
|
||||||
additionalButtonData() {
|
additionalButtonData() {
|
||||||
|
const startDate = new Date();
|
||||||
|
const endDate = new Date()
|
||||||
|
endDate.setDate(startDate.getDate() + 7);
|
||||||
|
|
||||||
|
const formattedStartDate = startDate.toISOString().split("T")[0];
|
||||||
|
const formattedEndDate = endDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availabilityRatingCallback: getAvailabilityRating,
|
availabilityRatingCallback: getAvailabilityRating,
|
||||||
|
startDate: formattedStartDate,
|
||||||
|
endDate: formattedEndDate,
|
||||||
|
shopAppointmentType: this.selectedAppointmentType,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,7 @@ import analyticsMixin from "@/mixins/analytics-mixin";
|
||||||
import { experimentTriggers } from "../constants/experiments";
|
import { experimentTriggers } from "../constants/experiments";
|
||||||
import { applicationConfig } from "../constants/application-config";
|
import { applicationConfig } from "../constants/application-config";
|
||||||
|
|
||||||
// Components
|
|
||||||
import datePicker from "@/digital-components/date-picker/date-picker.vue";
|
|
||||||
import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue";
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
|
||||||
path: "/demo-date-picker", // This is a temporary route for testing.
|
|
||||||
name: "demo-date-picker",
|
|
||||||
component: demoDatePicker,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/date-picker", // This is a temporary route for testing.
|
|
||||||
name: "date-picker",
|
|
||||||
component: datePicker,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "root",
|
name: "root",
|
||||||
|
|
|
||||||
|
|
@ -1136,57 +1136,121 @@ export const actions = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getShopTimeSlots(
|
getShopTimeSlots(context, { startDate, endDate, shopAppointmentType, providerNumber }) {
|
||||||
context,
|
|
||||||
{
|
|
||||||
providerNumber = null,
|
|
||||||
startDate = "2023-01-01",
|
|
||||||
endDate = "2023-05-01",
|
|
||||||
shopAppointmentType = "",
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const order = context.state.order;
|
const order = context.state.order;
|
||||||
const mockArray = [];
|
const vehicle = context.state.order.vehicle;
|
||||||
|
|
||||||
|
let partNumbers = [
|
||||||
|
...(order.lineItems.supportingItems ?? []),
|
||||||
|
...(order.lineItems.vaps ?? []),
|
||||||
|
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
|
||||||
|
];
|
||||||
|
partNumbers = partNumbers.map((lineItem) => {
|
||||||
|
return lineItem.partNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
const glassPieces = order.damage.glassToReplace ?? [];
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
providerNumber: providerNumber ?? order.providerNumber,
|
providerNumber: providerNumber,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate,
|
endDate: endDate,
|
||||||
shopAppointmentType: shopAppointmentType,
|
shopAppointmentType: shopAppointmentType,
|
||||||
applicationName: applicationConfig.APPLICATION_NAME,
|
applicationName: "Safelite.com funnel",
|
||||||
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
||||||
carId: context.getters.vehicle.carId,
|
carId: vehicle.carId,
|
||||||
partNumbers: mockArray, // TODO: WHERE DO I GET THIS?
|
partNumbers: partNumbers,
|
||||||
|
glassPieces: glassPieces,
|
||||||
eon: order.eon,
|
eon: order.eon,
|
||||||
provisionalReasons: mockArray, // TODO: WHERE DO I GET THIS?
|
coverage: {
|
||||||
|
status: "",
|
||||||
|
deductible: 0,
|
||||||
|
additionalAuthFlag: "",
|
||||||
|
},
|
||||||
|
partSelection: {
|
||||||
|
// TODO: Provisional booking will utilize these fields
|
||||||
|
hasAnsweredPartQuestions: false,
|
||||||
|
hasAnsweredMoldingQuestions: false,
|
||||||
|
hasAnsweredCapabilityQuestions: false,
|
||||||
|
hasManuallySelectedParts: false,
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
year: vehicle.year,
|
||||||
|
make: vehicle.make,
|
||||||
|
model: vehicle.model,
|
||||||
|
style: vehicle.style,
|
||||||
|
vin: vehicle.vin,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: REMOVE MOCK CALL & USE REAL CALL BELOW
|
console.log(payload)
|
||||||
return globalMethods.callMockHttpClient({
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetShopTimeSlots.method,
|
method: endpoints.GetShopTimeSlots.method,
|
||||||
endpoint: endpoints.GetShopTimeSlots.mockUrl,
|
endpoint: endpoints.GetShopTimeSlots.url,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getMobileTimeSlots(context, { startDate, endDate }) {
|
||||||
|
const order = context.state.order;
|
||||||
|
const vehicle = context.state.order.vehicle;
|
||||||
|
let partNumbers = [
|
||||||
|
...(order.lineItems.supportingItems ?? []),
|
||||||
|
...(order.lineItems.vaps ?? []),
|
||||||
|
...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts),
|
||||||
|
];
|
||||||
|
partNumbers = partNumbers.map((lineItem) => {
|
||||||
|
return lineItem.partNumber;
|
||||||
|
});
|
||||||
|
const glassPieces = order.damage.glassToReplace ?? [];
|
||||||
|
var payload = {
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
applicationName: "Safelite.com funnel",
|
||||||
|
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
||||||
|
carId: vehicle.carId,
|
||||||
|
partNumbers: partNumbers,
|
||||||
|
glassPieces: glassPieces,
|
||||||
|
eon: order.eon,
|
||||||
|
coverage: {
|
||||||
|
status: "",
|
||||||
|
deductible: 0,
|
||||||
|
additionalAuthFlag: "",
|
||||||
|
},
|
||||||
|
partSelection: {
|
||||||
|
// TODO: Provisional booking will utilize these fields
|
||||||
|
hasAnsweredPartQuestions: false,
|
||||||
|
hasAnsweredMoldingQuestions: false,
|
||||||
|
hasAnsweredCapabilityQuestions: false,
|
||||||
|
hasManuallySelectedParts: false,
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
year: vehicle.year,
|
||||||
|
make: vehicle.make,
|
||||||
|
model: vehicle.model,
|
||||||
|
style: vehicle.style,
|
||||||
|
vin: vehicle.vin,
|
||||||
|
},
|
||||||
|
zipCode: order.serviceLocation.zipCode,
|
||||||
|
};
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetMobileTimeSlots.method,
|
||||||
|
endpoint: endpoints.GetMobileTimeSlots.url,
|
||||||
|
payload: payload,
|
||||||
|
logApiCall: false,
|
||||||
});
|
});
|
||||||
// TODO: RESTORE THIS
|
|
||||||
// return globalMethods.callHttpClient({
|
|
||||||
// method: endpoints.GetShopTimeSlots.method,
|
|
||||||
// endpoint: endpoints.GetShopTimeSlots.url,
|
|
||||||
// payload: payload,
|
|
||||||
// logApiCall: false,
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getMobileEarlyBirdFee(context) {
|
getMobileEarlyBirdFee(context) {
|
||||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||||
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
|
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
|
||||||
return globalMethods.callMockHttpClient({
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetMobileEarlyBirdFee.method,
|
method: endpoints.GetMobileEarlyBirdFee.method,
|
||||||
endpoint: `${endpoints.GetMobileEarlyBirdFee.mockUrl}/Cash/Replace`,
|
endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`,
|
||||||
});
|
});
|
||||||
// return globalMethods.callHttpClient({
|
|
||||||
// method: endpoints.GetMobileEarlyBirdFee.method,
|
|
||||||
// endpoint: `${endpoints.GetMobileEarlyBirdFee.url}/${paymentType}/${damageType}`,
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Session API Actions
|
// Session API Actions
|
||||||
|
|
@ -1913,7 +1977,7 @@ function addPricesToLineItems(lineItems, pricingLineItems) {
|
||||||
|
|
||||||
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
|
function getFlattenedArrayOfLineItemsWithChildParts(lineItems) {
|
||||||
let flattenedArray = [];
|
let flattenedArray = [];
|
||||||
lineItems.forEach((lineItem) => {
|
lineItems?.forEach((lineItem) => {
|
||||||
flattenedArray.push(lineItem);
|
flattenedArray.push(lineItem);
|
||||||
if (lineItem.childParts) {
|
if (lineItem.childParts) {
|
||||||
flattenedArray = [
|
flattenedArray = [
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ export default {
|
||||||
},
|
},
|
||||||
blockUi: {
|
blockUi: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue