Merge remote-tracking branch 'origin/feature/CSR-886' into feature/CSR-1137
This commit is contained in:
commit
01e294d7fb
44 changed files with 2214 additions and 378 deletions
|
|
@ -28,4 +28,6 @@ module.exports = {
|
||||||
// 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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||||
|
// silent: true,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const GaActions = {
|
||||||
CLICKED: "Clicked",
|
CLICKED: "Clicked",
|
||||||
VIF: "vif",
|
VIF: "vif",
|
||||||
SUBMITTED: "Submitted",
|
SUBMITTED: "Submitted",
|
||||||
|
DISPLAYED: "Displayed",
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaLabels = {
|
const GaLabels = {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,14 @@ const endpoints = {
|
||||||
url: "/parts/api/v1/parts/supporting-items",
|
url: "/parts/api/v1/parts/supporting-items",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
GetAlertReasons: {
|
||||||
|
url: "/location/api/v1/location/alert-reasons",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
GetProviders: {
|
||||||
|
url: "/location/api/v1/location/providers",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
GetCapabilityQuestions: {
|
GetCapabilityQuestions: {
|
||||||
url: "/parts/api/v1/parts/capability-questions",
|
url: "/parts/api/v1/parts/capability-questions",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
const monthsOfYear = [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
];
|
|
||||||
|
|
||||||
const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
||||||
|
|
||||||
export { monthsOfYear, daysOfWeek };
|
|
||||||
|
|
@ -21,6 +21,7 @@ const storeActions = {
|
||||||
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
|
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
|
||||||
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
|
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
|
||||||
LOOKUP_VIN_BY_IMAGE: "lookupVinByImage",
|
LOOKUP_VIN_BY_IMAGE: "lookupVinByImage",
|
||||||
|
GET_ALERT_REASONS_BY_CTU: "getAlertReasonsByCtu",
|
||||||
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
||||||
GET_PARTS: "getParts",
|
GET_PARTS: "getParts",
|
||||||
GET_WIPERS: "getWipers",
|
GET_WIPERS: "getWipers",
|
||||||
|
|
@ -32,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_PROVIDERS: "getProviders",
|
||||||
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
|
GET_MOBILE_EARLY_BIRD_FEE: "getMobileEarlyBirdFee",
|
||||||
SAVE_SESSION: "saveSession",
|
SAVE_SESSION: "saveSession",
|
||||||
LOAD_SESSION: "loadSession",
|
LOAD_SESSION: "loadSession",
|
||||||
|
|
@ -61,6 +63,7 @@ const storeActions = {
|
||||||
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
|
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
|
||||||
SAVE_VIN_LOOKUP: "saveVinLookup",
|
SAVE_VIN_LOOKUP: "saveVinLookup",
|
||||||
SAVE_SERVICE_LOCATION: "saveServiceLocation",
|
SAVE_SERVICE_LOCATION: "saveServiceLocation",
|
||||||
|
SAVE_SCHEDULE: "saveSchedule",
|
||||||
SAVE_EMAIL: "saveEmail",
|
SAVE_EMAIL: "saveEmail",
|
||||||
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
|
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
|
||||||
SAVE_VIN: "saveVin",
|
SAVE_VIN: "saveVin",
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ const storeMutations = {
|
||||||
UPDATE_REGISTRATION: "updateRegistration",
|
UPDATE_REGISTRATION: "updateRegistration",
|
||||||
|
|
||||||
UPDATE_SERVICE_LOCATION: "updateServiceLocation",
|
UPDATE_SERVICE_LOCATION: "updateServiceLocation",
|
||||||
|
UPDATE_SCHEDULE: "updateSchedule",
|
||||||
|
|
||||||
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
|
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-bu
|
||||||
import listCard from "@/ux-components/list-card/list-card";
|
import listCard from "@/ux-components/list-card/list-card";
|
||||||
import radio from "@/ux-components/radio/radio";
|
import radio from "@/ux-components/radio/radio";
|
||||||
import { useField, ErrorMessage } from "vee-validate";
|
import { useField, ErrorMessage } from "vee-validate";
|
||||||
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "buttonQuestion",
|
name: "buttonQuestion",
|
||||||
|
|
@ -119,7 +120,7 @@ export default {
|
||||||
isRequired: Boolean,
|
isRequired: Boolean,
|
||||||
isOverflowScrollable: Boolean,
|
isOverflowScrollable: Boolean,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
modelValue: [Array, Number, String],
|
modelValue: [Array, Number, String, Object],
|
||||||
value: [Number, String],
|
value: [Number, String],
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
suppressError: Boolean,
|
suppressError: Boolean,
|
||||||
|
|
@ -128,6 +129,10 @@ export default {
|
||||||
additionalButtonStyling: String,
|
additionalButtonStyling: String,
|
||||||
isSmallQuestionText: Boolean,
|
isSmallQuestionText: Boolean,
|
||||||
customButtonQuestionId: String,
|
customButtonQuestionId: String,
|
||||||
|
logDisplayedValuesEvent: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const propsClone = Object.assign({}, props);
|
const propsClone = Object.assign({}, props);
|
||||||
|
|
@ -250,8 +255,33 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue() {
|
modelValue(newValue) {
|
||||||
this.resetField();
|
this.resetField({
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
answers() {
|
||||||
|
//once we get the answers to display from parent, see if we need a GA event to log what we showed
|
||||||
|
if (this.logDisplayedValuesEvent && this.answers.length > 0) {
|
||||||
|
var eventLabel = "";
|
||||||
|
//build comma separated list of all items in button list that we are going to display on page
|
||||||
|
this.answers.forEach((item) => {
|
||||||
|
if (item.Name) {
|
||||||
|
eventLabel += item.Name + ",";
|
||||||
|
}
|
||||||
|
if (item.buttonLabel) {
|
||||||
|
eventLabel += item.buttonLabel + ",";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eventLabel = eventLabel.slice(0, -1); //remove the last comma
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.$route.query[queryStrings.FMG_PAGE],
|
||||||
|
this.GaActions.DISPLAYED,
|
||||||
|
eventLabel,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
test.todo("some test to be written in the future");
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="months" class="date-picker text-center" :class="calendarViewDirection">
|
<div class="date-picker text-center" :class="calendarViewDirection">
|
||||||
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||||
<legend class="sr-only">Select a day and time</legend>
|
<legend class="sr-only">Select a day and time</legend>
|
||||||
<div
|
<div
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||||
class="calendar-grid-container position-relative"
|
class="calendar-grid-container position-relative"
|
||||||
:class="[
|
:class="[
|
||||||
hasPartialMonthInitialView === true ? 'partial-month-initial-view' : '',
|
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
||||||
month.monthClass,
|
month.monthClass,
|
||||||
]">
|
]">
|
||||||
<div class="month-year body-small d-flex align-items-center small">
|
<div class="month-year body-small d-flex align-items-center small">
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<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>
|
||||||
<!-- Do the days of the weeek need to be read? -->
|
<!-- 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>
|
||||||
|
|
@ -38,10 +38,10 @@
|
||||||
:class="[
|
:class="[
|
||||||
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
|
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
|
||||||
date.dayClasses,
|
date.dayClasses,
|
||||||
isSelectableDate(date.inputValue) ? 'selectable-day' : '',
|
date.isSelectable ? 'selectable-day' : '',
|
||||||
]">
|
]">
|
||||||
<input
|
<input
|
||||||
:disabled="!isSelectableDate(date.inputValue)"
|
:disabled="!date.isSelectable"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="day-of-month"
|
name="day-of-month"
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
|
|
@ -52,12 +52,17 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<loader
|
||||||
|
:class="[!isLoading ? 'date-picker-hidden' : '']"
|
||||||
|
loaderColor="blue"
|
||||||
|
loaderPosition="center" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<button
|
<button
|
||||||
v-if="calendarViewDirection === 'future'"
|
v-if="calendarViewDirection === 'future' && !disableViewMoreDatesButton"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-link"
|
class="btn btn-link"
|
||||||
@click="goForward">
|
:disabled="isLoading"
|
||||||
|
@click="showAnotherMonth">
|
||||||
View more dates
|
View more dates
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,58 +70,29 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { monthsOfYear } from "@/constants/scheduling.js";
|
import loader from "@/ux-components/loader/loader";
|
||||||
|
import { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR } from "./mixins/constants";
|
||||||
const SelectableDaysOptions = Object.freeze({
|
import { selectableDaysOptions, requiredParameter, forceTwoDigitString } from "./mixins/helpers";
|
||||||
CUSTOM: "custom",
|
|
||||||
PAST: "past",
|
|
||||||
});
|
|
||||||
|
|
||||||
const requiredParameter = () => {
|
|
||||||
throw new Error("parameter is required");
|
|
||||||
};
|
|
||||||
|
|
||||||
const forceTwoDigitString = (monthNum) => {
|
|
||||||
const newString = monthNum.toString();
|
|
||||||
return newString.length === 1 ? "0" + newString : newString;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TIMINGFUNC_MAP = {
|
|
||||||
linear: (t) => t,
|
|
||||||
"ease-in": (t) => t * t,
|
|
||||||
"ease-out": (t) => t * (2 - t),
|
|
||||||
"ease-in-out": (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
|
||||||
};
|
|
||||||
const BUFFER_OFFSET = 10;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "datePicker",
|
name: "datePicker",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
monthsBeforeToLoadOffset: 0,
|
isLoading: true,
|
||||||
monthsAfterToLoadOffset: 12,
|
|
||||||
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
|
||||||
hasPartialMonthInitialView: true,
|
|
||||||
hasPartialMonthInitialViewNEW: true,
|
|
||||||
initialViewRowsToShow: 5,
|
|
||||||
initialViewRowsTally: 0,
|
|
||||||
isAddingNewMonth: false,
|
|
||||||
currentScrollTop: 0,
|
|
||||||
months: null,
|
months: null,
|
||||||
lastApiCallStartDate: null,
|
disableViewMoreDatesButton: false,
|
||||||
lastApiCallEndDate: null,
|
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
||||||
|
today: null,
|
||||||
|
hideSomeDaysForInitialView: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.setCalendarData();
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
selectableDatesSetting: {
|
selectableDatesSetting: {
|
||||||
type: String,
|
type: String,
|
||||||
validator(value) {
|
validator(value) {
|
||||||
return Object.values(SelectableDaysOptions).includes(value);
|
return Object.values(selectableDaysOptions).includes(value);
|
||||||
},
|
},
|
||||||
default: SelectableDaysOptions.PAST,
|
default: selectableDaysOptions.PAST,
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -134,10 +110,25 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
todayDate() {
|
todayMonthIndex() {
|
||||||
return this.todayOverrideDateString
|
return this.today.getMonth() + 1;
|
||||||
? new Date(this.todayOverrideDateString)
|
},
|
||||||
: new Date();
|
todayYearNum() {
|
||||||
|
return this.today.getFullYear();
|
||||||
|
},
|
||||||
|
todayDayIndex() {
|
||||||
|
return this.today.getDay();
|
||||||
|
},
|
||||||
|
todayDateNum() {
|
||||||
|
return this.today.getDate();
|
||||||
|
},
|
||||||
|
currentWeekStartDateNum() {
|
||||||
|
return this.todayDayIndex >= this.todayDateNum
|
||||||
|
? 1
|
||||||
|
: this.todayDateNum - this.todayDayIndex;
|
||||||
|
},
|
||||||
|
currentWeekEndDateNum() {
|
||||||
|
return this.todayDateNum + (6 - this.todayDayIndex);
|
||||||
},
|
},
|
||||||
calendarViewDirection() {
|
calendarViewDirection() {
|
||||||
if (this.selectableDatesSetting === "past") return "past";
|
if (this.selectableDatesSetting === "past") return "past";
|
||||||
|
|
@ -149,13 +140,129 @@ export default {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
},
|
},
|
||||||
set(newSelectedDate) {
|
set(newSelectedDate) {
|
||||||
console.log("newSelectedDate ", newSelectedDate);
|
|
||||||
this.$emit("update:modelValue", newSelectedDate);
|
this.$emit("update:modelValue", newSelectedDate);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getWeekStartDate(date) {
|
||||||
|
// Get the day of the week for date
|
||||||
|
let dayOfWeek = date.getDay();
|
||||||
|
|
||||||
|
// Subtract the day of the week from date to get the date of Sunday
|
||||||
|
let sunday = new Date(date);
|
||||||
|
sunday.setDate(sunday.getDate() - dayOfWeek);
|
||||||
|
|
||||||
|
// Return the date of Sunday
|
||||||
|
return sunday;
|
||||||
|
},
|
||||||
|
getWeekEndDate(date) {
|
||||||
|
const currentDay = date.getDay(); // Get the day of the week (0 = Sunday, 1 = Monday, etc.)
|
||||||
|
const daysUntilSaturday = 6 - currentDay; // Calculate the number of days until Saturday
|
||||||
|
|
||||||
|
// Clone the given date and add the remaining days until Saturday
|
||||||
|
const saturday = new Date(date);
|
||||||
|
saturday.setDate(date.getDate() + daysUntilSaturday);
|
||||||
|
|
||||||
|
return saturday;
|
||||||
|
},
|
||||||
|
getNextWeekSunday(date) {
|
||||||
|
const currentDay = date.getDay(); // Get the day of the week (0 = Sunday, 1 = Monday, etc.)
|
||||||
|
const daysUntilNextSunday = currentDay === 0 ? 7 : 7 - currentDay; // Calculate the number of days until the next Sunday
|
||||||
|
|
||||||
|
// Clone the given date and add the remaining days until Sunday
|
||||||
|
const nextSunday = new Date(date);
|
||||||
|
nextSunday.setDate(date.getDate() + daysUntilNextSunday);
|
||||||
|
|
||||||
|
return nextSunday;
|
||||||
|
},
|
||||||
|
getInitialViewWeeks(today, initialViewRowsToShow) {
|
||||||
|
// TODO: this only is for future direction; create logic for past direction
|
||||||
|
let weeks = [];
|
||||||
|
let weekStartDate = this.getWeekStartDate(today);
|
||||||
|
let weekEndDate = this.getWeekEndDate(today);
|
||||||
|
for (let i = 0; i < initialViewRowsToShow; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
weekStartDate = this.getNextWeekSunday(weekEndDate);
|
||||||
|
weekEndDate = this.getWeekEndDate(weekStartDate);
|
||||||
|
}
|
||||||
|
weeks.push({
|
||||||
|
weekNum: i + 1,
|
||||||
|
weekStartDate: weekStartDate,
|
||||||
|
weekEndDate: weekEndDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return weeks;
|
||||||
|
},
|
||||||
|
async loadInitialData(config) {
|
||||||
|
// CALLED FROM CONSUMING COMPONENT BEFORE DATE-PICKER APPEARS
|
||||||
|
const todayDate = config.todayOverrideDateString
|
||||||
|
? new Date(config.todayOverrideDateString)
|
||||||
|
: new Date();
|
||||||
|
|
||||||
|
let todayMonthIndex = todayDate.getMonth() + 1;
|
||||||
|
let todayYearNum = todayDate.getFullYear();
|
||||||
|
let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
|
||||||
|
let currentMonthEnd = new Date(todayYearNum, todayMonthIndex, 0);
|
||||||
|
|
||||||
|
let calendarViewDirection = "none";
|
||||||
|
if (config.selectableDatesSetting === "past") calendarViewDirection = "past";
|
||||||
|
if (config.selectableDatesSetting === "custom") calendarViewDirection = "future";
|
||||||
|
|
||||||
|
const initialViewWeeks = this.getInitialViewWeeks(
|
||||||
|
todayDate,
|
||||||
|
config.initialViewRowsToShow
|
||||||
|
);
|
||||||
|
|
||||||
|
let initialViewStartDate = todayDate;
|
||||||
|
let initialViewEndDate = initialViewWeeks[initialViewWeeks.length - 1].weekEndDate;
|
||||||
|
let saturday1month = initialViewWeeks[0].weekEndDate.getMonth();
|
||||||
|
let sunday5month =
|
||||||
|
initialViewWeeks[initialViewWeeks.length - 1].weekStartDate.getMonth();
|
||||||
|
|
||||||
|
let hideSomeDaysForInitialView = false;
|
||||||
|
let hideSecondMonth = false;
|
||||||
|
|
||||||
|
// TODO - NEED TO CREATE OPPOSITE LOGIC FOR "past" DIRECTION LIKE BELOW vvvvv
|
||||||
|
if (calendarViewDirection === "future") {
|
||||||
|
if (saturday1month !== sunday5month) {
|
||||||
|
hideSomeDaysForInitialView = true;
|
||||||
|
}
|
||||||
|
if (initialViewStartDate.getMonth() === sunday5month) {
|
||||||
|
hideSecondMonth = true;
|
||||||
|
if (currentMonthEnd > initialViewEndDate) {
|
||||||
|
// should part of 1st month be hidden?
|
||||||
|
hideSomeDaysForInitialView = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let myPromise = new Promise((resolve, reject) => {
|
||||||
|
const response = config.customSelectableDatesCallback(
|
||||||
|
initialViewStartDate,
|
||||||
|
initialViewEndDate
|
||||||
|
);
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
return myPromise.then((response) => {
|
||||||
|
const initialData = {
|
||||||
|
todayDate: todayDate,
|
||||||
|
initialViewStartDate: initialViewStartDate,
|
||||||
|
initialViewEndDate: initialViewEndDate,
|
||||||
|
calendarViewDirection: calendarViewDirection,
|
||||||
|
initialShopTimeSlotsResponse: response,
|
||||||
|
hideSomeDaysForInitialView: hideSomeDaysForInitialView,
|
||||||
|
hideSecondMonth: hideSecondMonth,
|
||||||
|
};
|
||||||
|
return initialData;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initializeComponent(initialData) {
|
||||||
|
this.setCalendarData(initialData);
|
||||||
|
},
|
||||||
scrollToElement(elementId, speed, easing) {
|
scrollToElement(elementId, speed, easing) {
|
||||||
|
// TODO - needs to be cleaned up & refactored
|
||||||
function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") {
|
function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") {
|
||||||
const initY = wrapper.scrollTop;
|
const initY = wrapper.scrollTop;
|
||||||
const wrapperRect = wrapper.getBoundingClientRect();
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
|
|
@ -190,280 +297,258 @@ export default {
|
||||||
|
|
||||||
scrollTopSmooth(wrapper, targetMonth, 800, "ease-in-out");
|
scrollTopSmooth(wrapper, targetMonth, 800, "ease-in-out");
|
||||||
},
|
},
|
||||||
goForward() {
|
|
||||||
const fieldset = document.querySelector("#date-picker-fieldset");
|
|
||||||
|
|
||||||
if (this.hasPartialMonthInitialView) {
|
async setCalendarData(config = {}) {
|
||||||
const monthToScrollTo = document.querySelector(
|
this.today = config.todayDate;
|
||||||
".partial-month-initial-view:not(.current-month):not(.month-hidden)"
|
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
|
||||||
);
|
let hideSecondMonth = config.hideSecondMonth;
|
||||||
this.scrollToElement(monthToScrollTo.id);
|
const direction = config.calendarViewDirection;
|
||||||
this.hasPartialMonthInitialView = false; // removes hidden styling on days
|
|
||||||
} else {
|
|
||||||
this.showAnotherMonth();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async setCalendarData() {
|
|
||||||
// CAN I SET TODAY AND THE "END DATE OF THE INITIAL VIEW" BEFORE I RUN THROUGH THE MONTHS?
|
|
||||||
const todayDateNum = this.todayDate.getDate();
|
|
||||||
const todayDayIndex = this.todayDate.getDay(); // get day of week index of today (0-6)
|
|
||||||
const todayYearNum = this.todayDate.getFullYear();
|
|
||||||
const todayMonthIndex = this.todayDate.getMonth() + 1;
|
|
||||||
const todayMonthEndDateNum = new Date(todayYearNum, todayMonthIndex, 0).getDate();
|
|
||||||
|
|
||||||
this.lastApiCallStartDate =
|
const monthsAfterToLoadOffset = 12; // TO BE MADE "CONSTANTS"
|
||||||
todayYearNum.toString() +
|
const monthsBeforeToLoadOffset = 36; // TO BE MADE "CONSTANTS"
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(todayMonthIndex) +
|
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(todayDateNum);
|
|
||||||
|
|
||||||
const week1endDateNum = todayDateNum + (6 - todayDayIndex);
|
config.initialShopTimeSlotsResponse.forEach((selectableDate) => {
|
||||||
const weeksLeft =
|
this.selectableDatesData.push(selectableDate);
|
||||||
this.initialViewRowsToShow -
|
});
|
||||||
1 -
|
|
||||||
Math.ceil((todayMonthEndDateNum - week1endDateNum) / 7);
|
|
||||||
const daysLeftover = 7 - ((todayMonthEndDateNum - week1endDateNum) % 7);
|
|
||||||
const initialViewEndDate = 7 * weeksLeft + daysLeftover;
|
|
||||||
|
|
||||||
if (weeksLeft < 1) this.hasPartialMonthInitialViewNEW = false;
|
|
||||||
|
|
||||||
const initialViewEndDateYear =
|
|
||||||
this.hasPartialMonthInitialViewNEW && todayMonthIndex === 12
|
|
||||||
? todayYearNum + 1
|
|
||||||
: todayYearNum;
|
|
||||||
const initialViewEndDateMonth = this.hasPartialMonthInitialViewNEW
|
|
||||||
? todayMonthIndex + 1
|
|
||||||
: todayMonthIndex;
|
|
||||||
const initialViewEndDateDate = this.hasPartialMonthInitialViewNEW
|
|
||||||
? initialViewEndDate
|
|
||||||
: todayMonthEndDateNum;
|
|
||||||
|
|
||||||
this.lastApiCallEndDate =
|
|
||||||
initialViewEndDateYear.toString() +
|
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(initialViewEndDateMonth) +
|
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(initialViewEndDateDate);
|
|
||||||
|
|
||||||
// make new API call with this month's start and end dates
|
|
||||||
await this.updateSelectableDates(this.lastApiCallStartDate, this.lastApiCallEndDate);
|
|
||||||
|
|
||||||
// GENERATE MONTHS AND PUSH THEM INTO ARRAY
|
// GENERATE MONTHS AND PUSH THEM INTO ARRAY
|
||||||
const months = [];
|
const months = [];
|
||||||
if (this.calendarViewDirection === "future") {
|
const options = {
|
||||||
|
calendarViewDirection: direction,
|
||||||
|
monthsBeforeToLoadOffset: monthsBeforeToLoadOffset,
|
||||||
|
monthsAfterToLoadOffset: monthsAfterToLoadOffset,
|
||||||
|
initialViewStartDate: config.initialViewStartDate,
|
||||||
|
initialViewEndDate: config.initialViewEndDate,
|
||||||
|
hideSecondMonth: hideSecondMonth,
|
||||||
|
};
|
||||||
|
if (direction === "future") {
|
||||||
// first 0, then 1
|
// first 0, then 1
|
||||||
for (let i = 0; i <= this.monthsAfterToLoadOffset; i++) {
|
for (let i = 0; i <= monthsAfterToLoadOffset; i++) {
|
||||||
months.push(await this.getMonthData(i));
|
months.push(await this.getMonthData(i, options));
|
||||||
}
|
}
|
||||||
} else if (this.calendarViewDirection === "past") {
|
} else if (direction === "past") {
|
||||||
// first 0, then -1
|
// first 0, then -1
|
||||||
for (let i = 0; i >= 0 - this.monthsAfterToLoadOffset; i--) {
|
for (let i = 0; i >= 0 - monthsBeforeToLoadOffset; i--) {
|
||||||
months.unshift(this.getMonthData(i));
|
months.unshift(this.getMonthData(i, options));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TK - IF A CALENDAR WITH BOTH PAST AND FUTURE WAS EVER NEEDED
|
// TODO - IF A CALENDAR WITH BOTH PAST AND FUTURE WAS EVER NEEDED
|
||||||
// for (let i = this.monthsAfterToLoadOffset; i >= this.monthsBeforeToLoadOffset; i--) {
|
// for (let i = monthsAfterToLoadOffset; i >= monthsBeforeToLoadOffset; i--) {
|
||||||
// months.push(this.getMonthDataPAST(i));
|
// months.push(this.getMonthDataPAST(i));
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
this.months = months;
|
this.months = months;
|
||||||
|
this.isLoading = false;
|
||||||
},
|
},
|
||||||
async getMonthData(offset = requiredParameter()) {
|
|
||||||
const direction = this.calendarViewDirection; // "past" or "future"
|
async getMonthData(offset = requiredParameter(), options) {
|
||||||
let yearNum;
|
/* options will contain:
|
||||||
let monthIndex;
|
calendarViewDirection (string)
|
||||||
|
initialViewStartDateNum (number)
|
||||||
|
initialViewEndDateNum (number)
|
||||||
|
monthsBeforeToLoadOffset (number),
|
||||||
|
monthsAfterToLoadOffset (number),
|
||||||
|
hideSecondMonth (boolean),
|
||||||
|
|
||||||
|
data used:
|
||||||
|
todayDate (date object)
|
||||||
|
this.hideSomeDaysForInitialView (string)
|
||||||
|
*/
|
||||||
|
|
||||||
|
let monthIndex = this.todayMonthIndex + offset; // mutable
|
||||||
|
let yearNum = this.todayYearNum; // mutable
|
||||||
|
const calendarViewDirection = options.calendarViewDirection;
|
||||||
|
const initialViewStartDate = options.initialViewStartDate;
|
||||||
|
const initialViewEndDate = options.initialViewEndDate;
|
||||||
|
const hideSecondMonth = options.hideSecondMonth; // <<<<<<<<<<<<<
|
||||||
|
const dates = [];
|
||||||
let monthClass = "";
|
let monthClass = "";
|
||||||
let initialViewEndDate; // only used for setCalendarData FUTURE
|
let isMonthThatHidesSomeDaysForInitialView;
|
||||||
let initialViewStartDate; // only used for setCalendarData PAST
|
|
||||||
let currentWeekEndDateNum; // only used for setCalendarData PAST
|
|
||||||
let currentWeekStartDateNum; // only used for setCalendarData FUTURE
|
|
||||||
let firstMonthDayTally; // only used for setCalendarData on 1st month generated
|
|
||||||
const datesArray = [];
|
|
||||||
const todayDateNum = this.todayDate.getDate(); // TODO - DEDUPE?; SAME SET IN setCalendarData
|
|
||||||
|
|
||||||
yearNum = this.todayDate.getFullYear(); // TODO - DEDUPE?; SAME SET IN setCalendarData
|
if (calendarViewDirection === "future" && offset > 0) {
|
||||||
monthIndex = this.todayDate.getMonth() + offset; // TODO - DEDUPE?; SAME SET IN setCalendarData
|
while (monthIndex > 12) {
|
||||||
|
|
||||||
if (direction === "future" && offset > 0) {
|
|
||||||
while (monthIndex > 11) {
|
|
||||||
monthIndex = monthIndex - 12;
|
monthIndex = monthIndex - 12;
|
||||||
yearNum++;
|
yearNum++;
|
||||||
}
|
}
|
||||||
} else if (direction === "past" && offset < 0) {
|
} else if (calendarViewDirection === "past" && offset < 0) {
|
||||||
while (monthIndex < 0) {
|
while (monthIndex < 1) {
|
||||||
monthIndex = 12 + monthIndex;
|
monthIndex = 12 + monthIndex;
|
||||||
yearNum--;
|
yearNum--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const todayDayIndex = this.todayDate.getDay(); // get day of week index of today (0-6) // TODO - DEDUPE?; SAME SET IN setCalendarData
|
const monthEndDate = new Date(yearNum, monthIndex, 0); // BOTH
|
||||||
currentWeekStartDateNum =
|
|
||||||
todayDayIndex >= todayDateNum ? 1 : todayDateNum - todayDayIndex; // FUTURE
|
|
||||||
const monthEndDate = new Date(yearNum, monthIndex + 1, 0); // BOTH // TODO - DEDUPE?; SAME SET IN setCalendarData
|
|
||||||
let monthEndDateNum = monthEndDate.getDate(); // BOTH
|
let monthEndDateNum = monthEndDate.getDate(); // BOTH
|
||||||
currentWeekEndDateNum = todayDateNum + 6 - todayDayIndex; // PAST, aka Sat. (ok if it's larger than the month end?)
|
|
||||||
if (offset === 0 && direction === "past" && monthEndDateNum > currentWeekEndDateNum) {
|
if (
|
||||||
monthEndDateNum = currentWeekEndDateNum; // PAST
|
offset === 0 &&
|
||||||
|
calendarViewDirection === "past" &&
|
||||||
|
monthEndDateNum > this.currentWeekEndDateNum
|
||||||
|
) {
|
||||||
|
monthEndDateNum = this.currentWeekEndDateNum; // PAST
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthStartDateNum =
|
const monthStartDateNum =
|
||||||
offset === 0 && direction === "future" ? currentWeekStartDateNum : 1; // FUTURE
|
offset === 0 && calendarViewDirection === "future"
|
||||||
const monthStartDate = new Date(yearNum, monthIndex, monthStartDateNum); // BOTH
|
? this.currentWeekStartDateNum
|
||||||
|
: 1; // FUTURE
|
||||||
|
const monthStartDate = new Date(yearNum, monthIndex - 1, monthStartDateNum); // BOTH
|
||||||
|
|
||||||
const startDateDayIndex = monthStartDate.getDay(); // FUTURE
|
const startDateDayIndex = monthStartDate.getDay(); // FUTURE
|
||||||
const endDateDayIndex = monthEndDate.getDay(); // PAST
|
const endDateDayIndex = monthEndDate.getDay(); // PAST
|
||||||
|
|
||||||
if (offset === 0 && direction === "future") {
|
if (Math.abs(offset) === 1 && hideSecondMonth) {
|
||||||
firstMonthDayTally = monthStartDateNum - startDateDayIndex; // FUTURE (starts low, counts up)
|
monthClass = monthClass + " month-hidden";
|
||||||
monthClass = monthClass + " current-month";
|
} else if (Math.abs(offset) > 1) {
|
||||||
}
|
|
||||||
if (offset === 0 && direction === "past") {
|
|
||||||
firstMonthDayTally = currentWeekEndDateNum; // PAST (starts high, counts down)
|
|
||||||
monthClass = monthClass + " current-month";
|
|
||||||
}
|
|
||||||
|
|
||||||
while (
|
|
||||||
direction === "future" &&
|
|
||||||
offset === 0 &&
|
|
||||||
firstMonthDayTally <= monthEndDateNum
|
|
||||||
) {
|
|
||||||
// FUTURE
|
|
||||||
firstMonthDayTally = firstMonthDayTally + 7;
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (
|
|
||||||
direction === "past" &&
|
|
||||||
offset === 0 &&
|
|
||||||
firstMonthDayTally >= monthStartDateNum
|
|
||||||
) {
|
|
||||||
// PAST
|
|
||||||
firstMonthDayTally = firstMonthDayTally - 7;
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(offset) === 1) {
|
|
||||||
if (this.initialViewRowsTally < this.initialViewRowsToShow) {
|
|
||||||
// INDICATES A SPLIT-MONTH INITIAL VIEW
|
|
||||||
initialViewEndDate = 6 - startDateDayIndex + monthStartDateNum; // FUTURE
|
|
||||||
initialViewStartDate = monthEndDateNum - endDateDayIndex; // PAST
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
} else {
|
|
||||||
// INDICATES AN INITIAL VIEW SHOWING ONLY ONE MONTH
|
|
||||||
// monthClass = monthClass + " initial-month-hidden";
|
|
||||||
monthClass = monthClass + " month-hidden";
|
|
||||||
this.hasPartialMonthInitialView = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.initialViewRowsTally < this.initialViewRowsToShow) {
|
|
||||||
initialViewEndDate = initialViewEndDate + 7;
|
|
||||||
initialViewStartDate = initialViewStartDate - 7;
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(offset) > 1) {
|
|
||||||
monthClass = monthClass + " month-hidden";
|
monthClass = monthClass + " month-hidden";
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
Math.abs(offset) === options.monthsAfterToLoadOffset &&
|
||||||
|
calendarViewDirection === "future"
|
||||||
|
) {
|
||||||
|
monthClass = monthClass + " last-available-month";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Math.abs(offset) === options.monthsBeforeToLoadOffset &&
|
||||||
|
calendarViewDirection === "past"
|
||||||
|
) {
|
||||||
|
// TODO - re-check this logic if past direction
|
||||||
|
monthClass = monthClass + " last-available-month";
|
||||||
|
}
|
||||||
|
|
||||||
// populate datesArray
|
// populate dates array
|
||||||
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
|
for (let i = monthStartDateNum; i <= monthEndDateNum; i++) {
|
||||||
let dayClasses = "";
|
let dayClasses = "";
|
||||||
|
let dateString =
|
||||||
|
yearNum.toString() +
|
||||||
|
"-" +
|
||||||
|
forceTwoDigitString(monthIndex) +
|
||||||
|
"-" +
|
||||||
|
forceTwoDigitString(i);
|
||||||
|
|
||||||
const thisDate = {
|
const thisDate = {
|
||||||
// NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
|
||||||
year: yearNum,
|
year: yearNum,
|
||||||
month: monthIndex + 1,
|
month: monthIndex,
|
||||||
date: i,
|
date: i,
|
||||||
dateString:
|
dateString: dateString,
|
||||||
yearNum.toString() +
|
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(monthIndex + 1) +
|
|
||||||
"-" +
|
|
||||||
forceTwoDigitString(i),
|
|
||||||
};
|
};
|
||||||
if (offset === 0 && i === todayDateNum) {
|
if (offset === 0 && i === this.todayDateNum) {
|
||||||
dayClasses += "current-day";
|
dayClasses += "current-day";
|
||||||
}
|
}
|
||||||
if (offset === 0 && i < todayDateNum && direction === "future") {
|
if (offset === 0 && i < this.todayDateNum && calendarViewDirection === "future") {
|
||||||
dayClasses += "unavailable-day";
|
dayClasses += "unavailable-day";
|
||||||
}
|
}
|
||||||
if (offset === 0 && i > todayDateNum && direction === "past") {
|
if (offset === 0 && i > this.todayDateNum && calendarViewDirection === "past") {
|
||||||
dayClasses += "unavailable-day";
|
dayClasses += "unavailable-day";
|
||||||
}
|
}
|
||||||
if (Math.abs(offset) === 1 && direction === "future" && i > initialViewEndDate) {
|
if (
|
||||||
|
this.hideSomeDaysForInitialView &&
|
||||||
|
initialViewEndDate.getMonth() + 1 === monthIndex &&
|
||||||
|
initialViewEndDate.getDate() < i
|
||||||
|
) {
|
||||||
dayClasses += "day-hidden";
|
dayClasses += "day-hidden";
|
||||||
|
isMonthThatHidesSomeDaysForInitialView = true;
|
||||||
}
|
}
|
||||||
if (Math.abs(offset) === 1 && direction === "past" && i < initialViewStartDate) {
|
|
||||||
dayClasses += "day-hidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateObject = {
|
const dateObject = {
|
||||||
dateNum: i,
|
dateNum: i,
|
||||||
dayClasses: dayClasses,
|
dayClasses: dayClasses,
|
||||||
inputValue: thisDate,
|
inputValue: thisDate,
|
||||||
|
isSelectable:
|
||||||
|
this.selectableDatesData.findIndex(
|
||||||
|
(date) => date.dateString === dateString
|
||||||
|
) > -1
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
};
|
};
|
||||||
datesArray.push(dateObject);
|
dates.push(dateObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthToAdd = {
|
const monthToAdd = {
|
||||||
monthLabel: monthsOfYear[monthIndex],
|
monthLabel: MONTHS_OF_YEAR[monthIndex - 1],
|
||||||
monthIndex: monthIndex,
|
monthIndex: monthIndex,
|
||||||
monthString: monthsOfYear[monthIndex] + "-" + yearNum?.toString(),
|
monthString: MONTHS_OF_YEAR[monthIndex - 1] + "-" + yearNum?.toString(),
|
||||||
yearNum: yearNum,
|
yearNum: yearNum,
|
||||||
dates: datesArray,
|
dates: dates,
|
||||||
startDateDayIndex: startDateDayIndex,
|
startDateDayIndex: startDateDayIndex,
|
||||||
monthClass: monthClass,
|
monthClass: monthClass,
|
||||||
|
isMonthThatHidesSomeDaysForInitialView: isMonthThatHidesSomeDaysForInitialView,
|
||||||
};
|
};
|
||||||
// TODO: CHANGE NAME OF MONTH AND DATE's "___String" to "_____Id"; it's better
|
|
||||||
return monthToAdd;
|
return monthToAdd;
|
||||||
},
|
},
|
||||||
async showAnotherMonth() {
|
async showAnotherMonth() {
|
||||||
|
this.isLoading = true;
|
||||||
let monthToShow;
|
let monthToShow;
|
||||||
if (this.calendarViewDirection === "future") {
|
let monthStartDateNum = 0;
|
||||||
monthToShow = this.months.find((month) =>
|
|
||||||
month.monthClass.includes("month-hidden")
|
if (this.hideSomeDaysForInitialView) {
|
||||||
);
|
monthToShow = this.months.find(
|
||||||
}
|
({ isMonthThatHidesSomeDaysForInitialView }) =>
|
||||||
if (this.calendarViewDirection === "past") {
|
isMonthThatHidesSomeDaysForInitialView
|
||||||
// TODO: UPDATE THIS WITH CORRECT PAST LOOKING LOGIC
|
|
||||||
monthToShow = this.months.find((month) =>
|
|
||||||
month.monthClass.includes("month-hidden")
|
|
||||||
);
|
);
|
||||||
|
// find the first day-hidden to become the next api call start date
|
||||||
|
monthStartDateNum =
|
||||||
|
monthToShow.dates.find(({ dayClasses }) => dayClasses.includes("day-hidden"))
|
||||||
|
.dateNum - 1; // TRY 2
|
||||||
|
} else {
|
||||||
|
if (this.calendarViewDirection === "future") {
|
||||||
|
monthToShow = this.months.find((month) =>
|
||||||
|
month.monthClass.includes("month-hidden")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.calendarViewDirection === "past") {
|
||||||
|
// TODO: UPDATE THIS WITH CORRECT PAST LOOKING LOGIC
|
||||||
|
monthToShow = this.months.find((month) =>
|
||||||
|
month.monthClass.includes("month-hidden")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (monthToShow) {
|
if (monthToShow) {
|
||||||
// make new API call with this month's start and end dates
|
// make new API call with this month's start and end dates
|
||||||
await this.updateSelectableDates(
|
await this.updateSelectableDates(
|
||||||
monthToShow.dates[0].inputValue.dateString,
|
monthToShow.dates[monthStartDateNum].inputValue.dateString,
|
||||||
monthToShow.dates[monthToShow.dates.length - 1].inputValue.dateString
|
monthToShow.dates[monthToShow.dates.length - 1].inputValue.dateString
|
||||||
);
|
);
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hideSomeDaysForInitialView = false; // if hid days on initial partial view, this removes hidden styling on days
|
||||||
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
monthToShow.monthClass = monthToShow.monthClass.replace(" month-hidden", "");
|
||||||
this.scrollToElement(monthToShow.monthString);
|
this.scrollToElement(monthToShow.monthString);
|
||||||
|
|
||||||
|
if (monthToShow.monthClass.includes("last-available-month"))
|
||||||
|
this.disableViewMoreDatesButton = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelectableDate(thisDate) {
|
|
||||||
const testForDate = (dateInArray) => {
|
|
||||||
return dateInArray.dateString === thisDate.dateString;
|
|
||||||
};
|
|
||||||
const isSelectable =
|
|
||||||
this.selectableDatesData.findIndex(testForDate) > -1 ? true : false;
|
|
||||||
|
|
||||||
return isSelectable;
|
|
||||||
},
|
|
||||||
async updateSelectableDates(monthStart, monthEnd) {
|
async updateSelectableDates(monthStart, monthEnd) {
|
||||||
const responseData = await this.customSelectableDatesCallback(monthStart, monthEnd);
|
const moreSelectableDates = await this.customSelectableDatesCallback(
|
||||||
|
monthStart,
|
||||||
responseData?.forEach((newObj) => {
|
monthEnd
|
||||||
|
);
|
||||||
|
moreSelectableDates.forEach((selectableDate) => {
|
||||||
const index = this.selectableDatesData.findIndex(
|
const index = this.selectableDatesData.findIndex(
|
||||||
(obj) => obj.dateString === newObj.dateString
|
(obj) => obj.dateString === selectableDate.dateString
|
||||||
);
|
);
|
||||||
if (index === -1) this.selectableDatesData.push(newObj);
|
if (index === -1) this.selectableDatesData.push(selectableDate);
|
||||||
|
this.months.forEach((month) => {
|
||||||
|
// TODO: avoid checking all calendar date; maybe only ones between monthStart and monthEnd as defined above?
|
||||||
|
month.dates.forEach((date) => {
|
||||||
|
if (date.inputValue.dateString === selectableDate.dateString) {
|
||||||
|
date["isSelectable"] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
loader,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.date-picker-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
.date-picker {
|
.date-picker {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -472,6 +557,17 @@ export default {
|
||||||
fieldset {
|
fieldset {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 88%;
|
height: 88%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
position: absolute;
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.calendar-grid-container {
|
.calendar-grid-container {
|
||||||
margin: 0 auto 2rem auto;
|
margin: 0 auto 2rem auto;
|
||||||
|
|
@ -547,7 +643,7 @@ export default {
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 1.35rem;
|
height: 1.35rem;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: ease all 250ms;
|
transition: height ease 250ms, opacity ease 250ms;
|
||||||
|
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
position: absolute; //override bootstrap
|
position: absolute; //override bootstrap
|
||||||
|
|
@ -685,11 +781,16 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
max-height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.month-hidden {
|
&.month-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&.last-available-month:not(&.month-hidden) {
|
||||||
|
margin-bottom: 14rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#bottom-spacer {
|
#bottom-spacer {
|
||||||
|
|
|
||||||
30
src/digital-components/date-picker/mixins/constants.js
Normal file
30
src/digital-components/date-picker/mixins/constants.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const TIMINGFUNC_MAP = {
|
||||||
|
linear: (t) => t,
|
||||||
|
"ease-in": (t) => t * t,
|
||||||
|
"ease-out": (t) => t * (2 - t),
|
||||||
|
"ease-in-out": (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
||||||
|
};
|
||||||
|
const BUFFER_OFFSET = 10;
|
||||||
|
|
||||||
|
const MONTHS_OF_YEAR = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
|
||||||
|
<<<<<<< HEAD:src/constants/scheduling.js
|
||||||
|
const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||||
|
|
||||||
|
export { monthsOfYear, daysOfWeek };
|
||||||
|
=======
|
||||||
|
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR };
|
||||||
|
>>>>>>> CSR-886:src/digital-components/date-picker/mixins/constants.js
|
||||||
15
src/digital-components/date-picker/mixins/helpers.js
Normal file
15
src/digital-components/date-picker/mixins/helpers.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
const selectableDaysOptions = Object.freeze({
|
||||||
|
CUSTOM: "custom",
|
||||||
|
PAST: "past",
|
||||||
|
});
|
||||||
|
|
||||||
|
const requiredParameter = () => {
|
||||||
|
throw new Error("parameter is required");
|
||||||
|
};
|
||||||
|
|
||||||
|
const forceTwoDigitString = (monthNum) => {
|
||||||
|
const newString = monthNum.toString();
|
||||||
|
return newString.length === 1 ? "0" + newString : newString;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { selectableDaysOptions, requiredParameter, forceTwoDigitString };
|
||||||
|
|
@ -170,6 +170,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.show .modal-dialog {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="text-block w-100 mt-2"
|
class="text-block w-100"
|
||||||
:class="[justifyText, typeStyle, fontWeight]"
|
:class="[justifyText, typeStyle, fontWeight, margin]"
|
||||||
v-html="this.TextBlockCopy"></div>
|
v-html="this.TextBlockCopy"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -11,6 +11,11 @@ export default {
|
||||||
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, // left, right, center
|
||||||
|
margin: {
|
||||||
|
// bootstrap margin to apply to the block.
|
||||||
|
type: String,
|
||||||
|
default: "mt-2",
|
||||||
|
},
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@
|
||||||
<script>
|
<script>
|
||||||
import textLink from "@/ux-components/text-link/text-link";
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
import { Modal } from "bootstrap";
|
import { Modal } from "bootstrap";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "menuModal",
|
name: "menuModal",
|
||||||
|
|
@ -92,10 +93,7 @@ export default {
|
||||||
show() {
|
show() {
|
||||||
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
|
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
document.querySelector(".page-container-grouped-styles").scrollTo({
|
this.scrollToPageTop();
|
||||||
top: 0,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,10 @@ export default {
|
||||||
data: payload,
|
data: payload,
|
||||||
}).then(
|
}).then(
|
||||||
(response) => {
|
(response) => {
|
||||||
resolve(response);
|
// simulate a delayed response
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(response);
|
||||||
|
}, 2000);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
return reject(error.response);
|
return reject(error.response);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { damageLocationsSelected as glassLocations } from "@/constants/damage-locations-selected";
|
||||||
|
|
||||||
export function getDamageString() {
|
export function getDamageString() {
|
||||||
// If it's a repair it's always a windshield.
|
// If it's a repair it's always a windshield.
|
||||||
|
|
@ -45,6 +46,14 @@ export function getIsWindshieldOnly() {
|
||||||
return returnString;
|
return returnString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function includesWindshieldReplacement() {
|
||||||
|
const windshieldMatches =
|
||||||
|
store.getters.order.damage.glassToReplace?.filter(
|
||||||
|
(glassToReplace) => glassToReplace.glassLocation === glassLocations.WINDSHIELD
|
||||||
|
) ?? [];
|
||||||
|
return windshieldMatches.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function isGlassAvailableForCarId(carId) {
|
export async function isGlassAvailableForCarId(carId) {
|
||||||
const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
|
const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.GET_DAMAGE_OPTIONS,
|
storeActions.GET_DAMAGE_OPTIONS,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { storeActions } from "@/constants/store-actions.js";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import experimentMixin from "@/mixins/experiment-mixin";
|
import experimentMixin from "@/mixins/experiment-mixin";
|
||||||
import { experimentSettings } from "@/constants/experiments";
|
import { experimentSettings } from "@/constants/experiments";
|
||||||
|
import { includesWindshieldReplacement } from "@/helpers/damage-helper";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
|
||||||
|
|
@ -73,6 +74,7 @@ export async function skipVinLookup() {
|
||||||
return (
|
return (
|
||||||
store.getters.damage.isRepair ||
|
store.getters.damage.isRepair ||
|
||||||
isVinOptionalVehicle ||
|
isVinOptionalVehicle ||
|
||||||
|
!includesWindshieldReplacement() ||
|
||||||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +87,7 @@ export async function skipVinLookupNotRepair() {
|
||||||
return (
|
return (
|
||||||
!store.getters.damage.isRepair &&
|
!store.getters.damage.isRepair &&
|
||||||
(isVinOptionalVehicle ||
|
(isVinOptionalVehicle ||
|
||||||
|
!includesWindshieldReplacement() ||
|
||||||
experimentMixin.methods.hasSettingEqualTo(
|
experimentMixin.methods.hasSettingEqualTo(
|
||||||
experimentSettings.SUPPRESS_VIN_CAPTURE,
|
experimentSettings.SUPPRESS_VIN_CAPTURE,
|
||||||
"true"
|
"true"
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
||||||
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
|
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user has YMMS and no vehicle questions > should return vin-lookup", async () => {
|
test("user has YMMS and no vehicle questions > should return estimate", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const toRoute = {
|
const toRoute = {
|
||||||
query: {},
|
query: {},
|
||||||
|
|
@ -172,7 +172,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
||||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(result).toBe(fmgPageValues.VIN_LOOKUP);
|
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user has YMMS but no questions or carId > should return estimate", async () => {
|
test("user has YMMS but no questions or carId > should return estimate", async () => {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
<div class="row mb-4" v-show="showAddressFields" aria-live="polite">
|
<div class="row mb-4" v-show="showAddressFields" aria-live="polite">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<dropdownQuestion
|
<dropdownQuestion
|
||||||
customInputId="state"
|
customDropdownId="state"
|
||||||
cmsWidgetName="StateQuestionWidget"
|
cmsWidgetName="StateQuestionWidget"
|
||||||
v-model="addressModel.state"
|
v-model="addressModel.state"
|
||||||
ref="state"
|
ref="state"
|
||||||
|
|
@ -115,6 +115,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
preserveCityAndStateOnReset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -390,8 +394,10 @@ export default {
|
||||||
this.displayNoMatchWarning = true;
|
this.displayNoMatchWarning = true;
|
||||||
|
|
||||||
this.addressModel.city = "";
|
this.addressModel.city = "";
|
||||||
this.addressModel.state = "";
|
if (!this.preserveCityAndStateOnReset) {
|
||||||
this.addressModel.zipCode = "";
|
this.addressModel.state = "";
|
||||||
|
this.addressModel.zipCode = "";
|
||||||
|
}
|
||||||
this.showAddressFields = true;
|
this.showAddressFields = true;
|
||||||
this.displayVerificationWarning = false;
|
this.displayVerificationWarning = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ describe("estimate.vue", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
delete window.location;
|
||||||
|
window.location = { search: "?fmgPage=estimate&zipcode=43015" };
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
estimate.beforeRouteEnter.call(
|
estimate.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
buttonTypeString="listButton"
|
buttonTypeString="listButton"
|
||||||
v-model="selectedVinLookupMethod"
|
v-model="selectedVinLookupMethod"
|
||||||
isRequired
|
isRequired
|
||||||
validationRules="option-required" />
|
validationRules="option-required"
|
||||||
|
:logDisplayedValuesEvent="true" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<alert
|
<alert
|
||||||
|
|
@ -147,11 +148,17 @@ export default {
|
||||||
|
|
||||||
const queryString = window.location.search;
|
const queryString = window.location.search;
|
||||||
const urlParams = new URLSearchParams(queryString);
|
const urlParams = new URLSearchParams(queryString);
|
||||||
const hasZip = urlParams.has(queryStrings.ZIP_CODE);
|
const lowerCaseParams = new URLSearchParams();
|
||||||
const zip = urlParams.get(queryStrings.ZIP_CODE);
|
for (const [name, value] of urlParams) {
|
||||||
|
lowerCaseParams.append(name.toLowerCase(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE)
|
||||||
|
? lowerCaseParams.get(queryStrings.ZIP_CODE)
|
||||||
|
: store.getters.order.serviceLocation.zipCode;
|
||||||
|
|
||||||
var vinByAddressPromise;
|
var vinByAddressPromise;
|
||||||
if (hasZip) {
|
if (zip) {
|
||||||
vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
|
vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
|
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
|
||||||
zip,
|
zip,
|
||||||
|
|
@ -191,7 +198,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zip && resultMap.vinByAddress === false) {
|
if (!zip || resultMap.vinByAddress === false) {
|
||||||
var indexToRemove = resultMap.cmsContent.VinLookupMethod.Answers.findIndex(
|
var indexToRemove = resultMap.cmsContent.VinLookupMethod.Answers.findIndex(
|
||||||
(answer) => answer.Name === "HomeAddress"
|
(answer) => answer.Name === "HomeAddress"
|
||||||
);
|
);
|
||||||
|
|
@ -199,6 +206,7 @@ export default {
|
||||||
resultMap.cmsContent.VinLookupMethod.Answers.splice(indexToRemove, 1);
|
resultMap.cmsContent.VinLookupMethod.Answers.splice(indexToRemove, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,6 @@ describe("license-plate-lookup.vue", () => {
|
||||||
});
|
});
|
||||||
const registrationZip = "12345";
|
const registrationZip = "12345";
|
||||||
const serviceZip = "12345";
|
const serviceZip = "12345";
|
||||||
console.log("this is the test I care about");
|
|
||||||
|
|
||||||
wrapper.vm.navigateForward = jest.fn();
|
wrapper.vm.navigateForward = jest.fn();
|
||||||
const vinLookup = { data: { vehicle: { carId: "TESTID1" } } };
|
const vinLookup = { data: { vehicle: { carId: "TESTID1" } } };
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
:displayGenericVehicleImage="false" />
|
:displayGenericVehicleImage="false" />
|
||||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
<div class="fade-on-route-transition sub-container make-tall">
|
<div class="fade-on-route-transition sub-container make-tall">
|
||||||
<div class="row my-2">
|
<div class="row mt-2 mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
validationRules="license-plate-required" />
|
validationRules="license-plate-required" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row mt-0 mb-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
validationRules="zip-required|zip-format" />
|
validationRules="zip-required|zip-format" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-0">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
cmsWidgetName="EmailAddressQuestionWidget"
|
cmsWidgetName="EmailAddressQuestionWidget"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
:buttonTypeObject="servicePackageRadio"
|
:buttonTypeObject="servicePackageRadio"
|
||||||
v-model="selectedPackageName"
|
v-model="selectedPackageName"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:isRequired="isRequired" />
|
:isRequired="isRequired"
|
||||||
|
:logDisplayedValuesEvent="true" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
3
src/layouts/review/review.spec.js
Normal file
3
src/layouts/review/review.spec.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
describe("Review Page", () => {
|
||||||
|
test.todo("Add more tests as specific functionality is added.");
|
||||||
|
});
|
||||||
102
src/layouts/review/review.vue
Normal file
102
src/layouts/review/review.vue
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||||
|
<!-- When customer-details is added: v-slot="{ meta }" -->
|
||||||
|
<div class="page-container-grouped-styles">
|
||||||
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||||
|
<vehicleBanner
|
||||||
|
cmsWidgetName="VehicleBannerWidget"
|
||||||
|
:displayGenericVehicleImage="false" />
|
||||||
|
|
||||||
|
<textBlock
|
||||||
|
:customText="subHeaderTitle"
|
||||||
|
typeStyle="h5"
|
||||||
|
justifyText="text-center"
|
||||||
|
margin="mt-1"
|
||||||
|
class="dark-header" />
|
||||||
|
<textBlock
|
||||||
|
:customText="subHeaderBody"
|
||||||
|
typeStyle="body"
|
||||||
|
justifyText="left"
|
||||||
|
margin="mt-0 mb-2" />
|
||||||
|
|
||||||
|
<buttonMain
|
||||||
|
ref="buttonMain"
|
||||||
|
isPrimary
|
||||||
|
:buttonText="forwardButtonText"
|
||||||
|
loaderColor="white"
|
||||||
|
@click-event="forwardButtonAction" />
|
||||||
|
|
||||||
|
<funnelFooter
|
||||||
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
@back-clicked="backButtonAction"
|
||||||
|
@ForwardClicked="forwardButtonAction" />
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
|
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||||
|
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||||
|
import buttonMain from "@/ux-components/button-main/button-main";
|
||||||
|
import textBlock from "@/digital-components/text-block/text-block";
|
||||||
|
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "review",
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
|
next((vm) => {
|
||||||
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
backButtonAction() {},
|
||||||
|
forwardButtonAction() {},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
subHeaderTitle() {
|
||||||
|
return this.getCmsContent("FunnelSubHeaderWidget", "HeaderText");
|
||||||
|
},
|
||||||
|
subHeaderBody() {
|
||||||
|
return this.getCmsContent("FunnelSubHeaderWidget", "BodyText");
|
||||||
|
},
|
||||||
|
forwardButtonText() {
|
||||||
|
return this.getCmsContent("FunnelFooterWidget", "ForwardButtonText");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
funnelHeader,
|
||||||
|
funnelFooter,
|
||||||
|
vehicleBanner,
|
||||||
|
buttonMain,
|
||||||
|
textBlock,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dark-header {
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
src/layouts/schedule/helpers/schedule-helper.js
Normal file
13
src/layouts/schedule/helpers/schedule-helper.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
|
||||||
|
export async function getAlertReasons(ctu) {
|
||||||
|
const alertReasons = await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.GET_ALERT_REASONS_BY_CTU,
|
||||||
|
{
|
||||||
|
ctu: ctu,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(alertReasons);
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,22 @@
|
||||||
<span v-else class="m-0 text-body" v-html="copy"></span>
|
<span v-else class="m-0 text-body" v-html="copy"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="displayWeatherAlert">
|
||||||
|
<alert
|
||||||
|
v-for="alert in weatherAlerts"
|
||||||
|
v-show="cmsHeadlineTextFound(alert.cmsWidgetName)"
|
||||||
|
:key="alert.cmsWidgetName"
|
||||||
|
:ref="alert.cmsWidgetName"
|
||||||
|
class="mt-2 mb-3"
|
||||||
|
:cmsWidgetName="alert.cmsWidgetName"
|
||||||
|
alertClass="alert-warning" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<date-picker
|
<date-picker
|
||||||
selectableDatesSetting="custom"
|
selectableDatesSetting="custom"
|
||||||
|
ref="datePicker"
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
:customSelectableDatesCallback="getAvailableDates" />
|
:customSelectableDatesCallback="getAvailableDatesMethod" />
|
||||||
<!-- todayOverrideDateString="2023-08-06T03:00:00" -->
|
<!-- todayOverrideDateString="2023-08-06T03:00:00" -->
|
||||||
<time-slot-modal-question
|
<time-slot-modal-question
|
||||||
ref="timeSlotModalQuestion"
|
ref="timeSlotModalQuestion"
|
||||||
|
|
@ -53,6 +65,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Components
|
// Components
|
||||||
|
import alert from "@/ux-components/alert/alert";
|
||||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
|
@ -67,6 +80,7 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
import { getAlertReasons } from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
import {
|
import {
|
||||||
doesCopyContainRouterLink,
|
doesCopyContainRouterLink,
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
|
@ -75,10 +89,38 @@ import {
|
||||||
} from "@/helpers/cms-content-helper";
|
} from "@/helpers/cms-content-helper";
|
||||||
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";
|
||||||
|
|
||||||
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) => {
|
||||||
|
// USING DATES PASSED, MAKE AN API CALL
|
||||||
|
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||||
|
storeActions.GET_SHOP_TIME_SLOTS,
|
||||||
|
{
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
shopAppointmentType: "mobile", // TODO - PASS THIS IN FROM PREVIOUS PAGE?
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const newShopTimeSlots = newShopTimeSlotsResponse.data;
|
||||||
|
return convertApiResponse(newShopTimeSlots.days);
|
||||||
|
};
|
||||||
|
const convertApiResponse = (responseData) => {
|
||||||
|
// DATA CONVERSION
|
||||||
|
responseData?.forEach((date) => {
|
||||||
|
const dateString = date.date;
|
||||||
|
date.dateString = dateString;
|
||||||
|
const dateStringPieces = dateString.split("-");
|
||||||
|
date.year = Number(dateStringPieces[0]);
|
||||||
|
date.month = Number(dateStringPieces[1]);
|
||||||
|
date.date = Number(dateStringPieces[2]);
|
||||||
|
});
|
||||||
|
return responseData;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "schedule",
|
name: "schedule",
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -86,6 +128,7 @@ export default {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
selectedTimeSlotId: null,
|
selectedTimeSlotId: null,
|
||||||
selectableDatesData: [],
|
selectableDatesData: [],
|
||||||
|
weatherAlerts: [],
|
||||||
mobileEarlyBirdFee: null,
|
mobileEarlyBirdFee: null,
|
||||||
TEMPORARYappointmentType: "Inshop",
|
TEMPORARYappointmentType: "Inshop",
|
||||||
|
|
||||||
|
|
@ -94,6 +137,11 @@ export default {
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
// Call APIs
|
// Call APIs
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
const datePickerInitialDataPromise = datePicker.methods.loadInitialData({
|
||||||
|
// setup config options for date-picker
|
||||||
|
selectableDatesSetting: "custom",
|
||||||
|
initialViewRowsToShow: 5,
|
||||||
|
customSelectableDatesCallback: getAvailableDates,
|
||||||
const earlyBirdPromise = baseMixin.methods.dispatchStoreAction(
|
const earlyBirdPromise = baseMixin.methods.dispatchStoreAction(
|
||||||
storeActions.GET_MOBILE_EARLY_BIRD_FEE
|
storeActions.GET_MOBILE_EARLY_BIRD_FEE
|
||||||
);
|
);
|
||||||
|
|
@ -108,12 +156,39 @@ export default {
|
||||||
// false
|
// false
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
const alertReasonsPromise = getAlertReasons(store.getters.order.serviceLocation.zipCodeCtu);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resultKey: "alertReasons",
|
||||||
|
promise: alertReasonsPromise,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultKey: "datePickerInitialData",
|
||||||
|
promise: datePickerInitialDataPromise,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
resultKey: "earlyBird",
|
resultKey: "earlyBird",
|
||||||
promise: earlyBirdPromise,
|
promise: earlyBirdPromise,
|
||||||
|
|
@ -129,6 +204,8 @@ export default {
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
vm.setData(resultMap.alertReasons);
|
||||||
|
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
|
||||||
vm.mobileEarlyBirdFee = resultMap.earlyBird;
|
vm.mobileEarlyBirdFee = resultMap.earlyBird;
|
||||||
vm.mobileEarlyBirdFee.laborAmount = 15;
|
vm.mobileEarlyBirdFee.laborAmount = 15;
|
||||||
vm.mobileEarlyBirdFee.sellingPrice = 0;
|
vm.mobileEarlyBirdFee.sellingPrice = 0;
|
||||||
|
|
@ -146,6 +223,9 @@ export default {
|
||||||
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
|
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
|
||||||
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
|
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
|
||||||
},
|
},
|
||||||
|
displayWeatherAlert() {
|
||||||
|
return this.weatherAlerts.length > 0;
|
||||||
|
},
|
||||||
appointmentType() {
|
appointmentType() {
|
||||||
return this.TEMPORARYappointmentType;
|
return this.TEMPORARYappointmentType;
|
||||||
//return this.$store.getters.order.serviceLocation.appointmentType;
|
//return this.$store.getters.order.serviceLocation.appointmentType;
|
||||||
|
|
@ -186,45 +266,52 @@ export default {
|
||||||
return true;
|
return true;
|
||||||
// NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE?
|
// NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE?
|
||||||
},
|
},
|
||||||
async getAvailableDates(startDate, endDate) {
|
async getAvailableDatesMethod(startDate, endDate) {
|
||||||
console.log('calling getAvailableDates', startDate, endDate);
|
/* TODO - DO WE NEED TO KEEP AN AGGREGATE OF ALL DATES RETURNED
|
||||||
// USING DATES PASSED, MAKE AN API CALL
|
FOR TIMESLOTS in this.selectableDatesData? IS selectableDatesData EVEN NEEDED?
|
||||||
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
|
||||||
storeActions.GET_SHOP_TIME_SLOTS,
|
|
||||||
{
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
shopAppointmentType: "mobile", // TODO - PASS THIS IN FROM PREVIOUS PAGE?
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
const newShopTimeSlots = newShopTimeSlotsResponse.data;
|
|
||||||
// console.log("newShopTimeSlots ", newShopTimeSlots)
|
|
||||||
|
|
||||||
// ADD API CALL RESULTS TO EXISTING DATE DATA
|
// // ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||||
this.selectableDatesData = this.selectableDatesData.concat(
|
// this.selectableDatesData = this.selectableDatesData.concat(
|
||||||
this.convertApiResponse(newShopTimeSlots.days)
|
// this.convertApiResponse(newShopTimeSlots.days)
|
||||||
);
|
// );
|
||||||
this.selectableDatesData.estimatedServiceMinutesMaximum =
|
// // console.log("this.selectableDatesData is now: ", this.selectableDatesData);
|
||||||
newShopTimeSlots.estimatedServiceMinutesMaximum;
|
|
||||||
this.selectableDatesData.estimatedServiceMinutesMinimum =
|
|
||||||
newShopTimeSlots.estimatedServiceMinutesMinimum;
|
|
||||||
console.log("this.selectableDatesData is now: ", this.selectableDatesData);
|
|
||||||
|
|
||||||
// RETURN AGGREGATE DATE DATA
|
// // RETURN AGGREGATE DATE DATA
|
||||||
return this.selectableDatesData;
|
// return this.selectableDatesData;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return await getAvailableDates(startDate, endDate);
|
||||||
},
|
},
|
||||||
convertApiResponse(responseData) {
|
setData(alertReasonsData) {
|
||||||
// DATA CONVERSION
|
if (alertReasonsData) {
|
||||||
responseData?.forEach((date) => {
|
this.convertReasonsToCmsAlerts(alertReasonsData);
|
||||||
const dateString = date.date;
|
}
|
||||||
date.dateString = dateString;
|
},
|
||||||
const dateStringPieces = dateString.split("-");
|
cmsHeadlineTextFound(widgetName) {
|
||||||
date.year = Number(dateStringPieces[0]);
|
return this.getCmsContent(widgetName, "HeadlineText") !== "";
|
||||||
date.month = Number(dateStringPieces[1]);
|
},
|
||||||
date.date = Number(dateStringPieces[2]);
|
convertReasonsToCmsAlerts(data) {
|
||||||
});
|
this.weatherAlerts = data.reduce((newObj, alert) => {
|
||||||
return responseData;
|
newObj.push({
|
||||||
|
cmsWidgetName: `LocationAlert-${alert}`,
|
||||||
|
alertReason: alert,
|
||||||
|
});
|
||||||
|
return newObj;
|
||||||
|
}, []);
|
||||||
|
},
|
||||||
|
async getWeatherAlertReasons(ctu) {
|
||||||
|
await getAlertReasons(ctu)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data) {
|
||||||
|
this.convertReasonsToCmsAlerts(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("error fetching alert reasons..");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getServiceZipCtuCodeFromStore() {
|
||||||
|
return store.getters.order.serviceLocation.zipCodeCtu;
|
||||||
},
|
},
|
||||||
setAppointmentType(appointmentType) {
|
setAppointmentType(appointmentType) {
|
||||||
this.TEMPORARYappointmentType = appointmentType;
|
this.TEMPORARYappointmentType = appointmentType;
|
||||||
|
|
@ -241,7 +328,19 @@ export default {
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
},
|
},
|
||||||
forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
|
//TODO: replace properties with real values once they are available
|
||||||
|
await this.dispatchStoreAction(
|
||||||
|
this.storeActions.SAVE_SCHEDULE,
|
||||||
|
{
|
||||||
|
date: "2023-07-04T00:00:00",
|
||||||
|
startTime: "2023-07-04T12:00:00",
|
||||||
|
endTime: "2023-07-04T17:00:00",
|
||||||
|
routeCode: "03341-01820-S-B*20232*11 AM",
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -253,6 +352,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
alert,
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
funnelFooter,
|
funnelFooter,
|
||||||
funnelSubHeader,
|
funnelSubHeader,
|
||||||
|
|
@ -261,7 +361,6 @@ export default {
|
||||||
datePicker,
|
datePicker,
|
||||||
timeSlotModalQuestion,
|
timeSlotModalQuestion,
|
||||||
},
|
},
|
||||||
|
mounted() {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss"></style>
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<div class="appointment-type-question" aria-live="polite">
|
<div class="appointment-type-question" aria-live="polite">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
|
ref="buttonQuestion"
|
||||||
customButtonQuestionId="appointmentTypeQuestion"
|
customButtonQuestionId="appointmentTypeQuestion"
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="answersToDisplay"
|
:answers="answersToDisplay"
|
||||||
|
|
@ -66,12 +67,22 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
answersToDisplay: {
|
||||||
|
handler(newValue) {
|
||||||
|
// If there is only one option to display and that option is 'Mobile' then select it
|
||||||
|
if (
|
||||||
|
newValue.length == 1 &&
|
||||||
|
newValue.findIndex((answer) => answer.Name == "Mobile") != -1
|
||||||
|
) {
|
||||||
|
this.selectedValues = "Mobile";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
isMobileOnly: {
|
isMobileOnly: {
|
||||||
handler(newValue) {
|
handler(newValue) {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.selectedValues = "Mobile";
|
this.selectedValues = "Mobile";
|
||||||
} else {
|
|
||||||
this.selectedValues = null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ describe("service-location-helper.js", () => {
|
||||||
it("Should return null if no service zip code is passed in", async () => {
|
it("Should return null if no service zip code is passed in", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const serviceZipCode = null;
|
const serviceZipCode = null;
|
||||||
const serviceType = "Replace";
|
const damageType = "Replace";
|
||||||
const parentAccountNumber = 167132;
|
const parentAccountNumber = 167132;
|
||||||
const billToAccountNumber = 1234;
|
const billToAccountNumber = 1234;
|
||||||
const expected = null;
|
const expected = null;
|
||||||
|
|
@ -56,7 +56,7 @@ describe("service-location-helper.js", () => {
|
||||||
// Act
|
// Act
|
||||||
const result = await getPricedMobileFeePart(
|
const result = await getPricedMobileFeePart(
|
||||||
serviceZipCode,
|
serviceZipCode,
|
||||||
serviceType,
|
damageType,
|
||||||
parentAccountNumber,
|
parentAccountNumber,
|
||||||
billToAccountNumber
|
billToAccountNumber
|
||||||
);
|
);
|
||||||
|
|
@ -68,7 +68,7 @@ describe("service-location-helper.js", () => {
|
||||||
it("Should return the priced mobile fee part", async () => {
|
it("Should return the priced mobile fee part", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const serviceZipCode = "43235";
|
const serviceZipCode = "43235";
|
||||||
const serviceType = "Replace";
|
const damageType = "Replace";
|
||||||
const parentAccountNumber = 167132;
|
const parentAccountNumber = 167132;
|
||||||
const billToAccountNumber = 1234;
|
const billToAccountNumber = 1234;
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ describe("service-location-helper.js", () => {
|
||||||
// Act
|
// Act
|
||||||
const result = await getPricedMobileFeePart(
|
const result = await getPricedMobileFeePart(
|
||||||
serviceZipCode,
|
serviceZipCode,
|
||||||
serviceType,
|
damageType,
|
||||||
parentAccountNumber,
|
parentAccountNumber,
|
||||||
billToAccountNumber
|
billToAccountNumber
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,15 @@
|
||||||
@click-event="openModal"
|
@click-event="openModal"
|
||||||
aria-label="Modal window" />
|
aria-label="Modal window" />
|
||||||
</div>
|
</div>
|
||||||
<textBlock
|
|
||||||
:customText="mobileFeeText"
|
|
||||||
cmsWidgetName="MobileFeeDisclaimerWidget"
|
|
||||||
typeStyle="caption" />
|
|
||||||
<div v-show="errorMessage" class="row my-1 form-test-error">
|
<div v-show="errorMessage" class="row my-1 form-test-error">
|
||||||
<span class="d-inline-flex small mt-0 center-error-message" role="alert">
|
<span class="d-inline-flex small mt-0 center-error-message" role="alert">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<textBlock
|
||||||
|
:customText="mobileFeeText"
|
||||||
|
cmsWidgetName="MobileFeeDisclaimerWidget"
|
||||||
|
typeStyle="caption" />
|
||||||
</div>
|
</div>
|
||||||
<modal
|
<modal
|
||||||
:ref="modalName"
|
:ref="modalName"
|
||||||
|
|
@ -37,7 +37,8 @@
|
||||||
<addressQuestions
|
<addressQuestions
|
||||||
ref="addressQuestions"
|
ref="addressQuestions"
|
||||||
v-model="internalModel.addressQuestions"
|
v-model="internalModel.addressQuestions"
|
||||||
captureApartmentNumberOrBusinessName="true" />
|
captureApartmentNumberOrBusinessName="true"
|
||||||
|
preserveCityAndStateOnReset="true" />
|
||||||
<vehicleProtectedQuestion
|
<vehicleProtectedQuestion
|
||||||
ref="vehicleProtectedQuestion"
|
ref="vehicleProtectedQuestion"
|
||||||
v-model="internalModel.isVehicleProtected"
|
v-model="internalModel.isVehicleProtected"
|
||||||
|
|
@ -136,6 +137,9 @@ export default {
|
||||||
alertInvalidZipWidgetName: String,
|
alertInvalidZipWidgetName: String,
|
||||||
customComponentId: String,
|
customComponentId: String,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
|
onZipUpdateCallback: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mobileLocationLinkPromptText() {
|
mobileLocationLinkPromptText() {
|
||||||
|
|
@ -256,6 +260,10 @@ export default {
|
||||||
// Update the page level model
|
// Update the page level model
|
||||||
this.$emit("update:modelValue", this.internalModel);
|
this.$emit("update:modelValue", this.internalModel);
|
||||||
|
|
||||||
|
if (this.onZipUpdateCallback) {
|
||||||
|
await this.onZipUpdateCallback(serviceZipCode);
|
||||||
|
}
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -186,10 +186,12 @@ beforeEach(() => {
|
||||||
|
|
||||||
describe("service-location.vue", () => {
|
describe("service-location.vue", () => {
|
||||||
describe("beforeRouteEnter", () => {
|
describe("beforeRouteEnter", () => {
|
||||||
test("on load sets the mobile fee part when an service zip code has already been provided", async () => {
|
test("on load sets the mobile fee part when a service zip code has already been provided", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
const mobileFeePart = {
|
const mobileFeePart = {
|
||||||
partNumber: "MOBILE FEE",
|
partNumber: "MOBILE FEE",
|
||||||
description: "MOBILE FEE",
|
description: "MOBILE FEE",
|
||||||
|
|
@ -585,6 +587,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -611,6 +615,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -637,6 +643,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -663,6 +671,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -689,6 +699,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -716,6 +728,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -743,6 +757,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -770,6 +786,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -797,6 +815,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -824,6 +844,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -851,6 +873,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -878,6 +902,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -905,6 +931,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -932,6 +960,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -959,6 +989,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -989,6 +1021,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1015,6 +1049,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1041,6 +1077,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1068,6 +1106,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1095,6 +1135,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1122,6 +1164,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1151,6 +1195,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1178,6 +1224,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1205,6 +1253,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1232,6 +1282,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1259,6 +1311,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1286,6 +1340,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1313,6 +1369,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
@ -1342,6 +1400,8 @@ describe("service-location.vue", () => {
|
||||||
|
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.$refs.shopQuestion.initializeComponent = jest.fn();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await serviceLocation.beforeRouteEnter.call(
|
await serviceLocation.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="page-container-grouped-styles">
|
<div class="page-container-grouped-styles">
|
||||||
<loadingModal ref="loadingModal" />
|
<loadingModal ref="loadingModal" />
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||||
<serviceZipModalQuestion
|
<serviceZipModalQuestion
|
||||||
v-model="serviceZipCodeQuestion"
|
v-model="serviceZipCodeQuestion"
|
||||||
ref="serviceZipCodeQuestion"
|
ref="serviceZipCodeQuestion"
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
@updated-serviceability="setServiceabilityDetails"
|
@updated-serviceability="setServiceabilityDetails"
|
||||||
@updated-contains-military-base="setContainsMilitaryBase"
|
@updated-contains-military-base="setContainsMilitaryBase"
|
||||||
linkWidgetName="ServiceZipLinkWidget"
|
linkWidgetName="ServiceZipLinkWidget"
|
||||||
modalWidgetName="ServiceZipModalWidget" />
|
modalWidgetName="ServiceZipModalWidget"
|
||||||
|
:onZipUpdateCallback="reloadShopData" />
|
||||||
<alert
|
<alert
|
||||||
ref="alertMilitaryBaseZip"
|
ref="alertMilitaryBaseZip"
|
||||||
class="my-5"
|
class="my-5"
|
||||||
|
|
@ -46,7 +47,7 @@
|
||||||
alertClass="alert-warning" />
|
alertClass="alert-warning" />
|
||||||
<appointmentTypeQuestion
|
<appointmentTypeQuestion
|
||||||
v-model="selectedAppointmentType"
|
v-model="selectedAppointmentType"
|
||||||
v-if="!displayNoShopsAlert"
|
v-show="!displayNoShopsAlert"
|
||||||
:isServiceableMobile="isServiceableMobile"
|
:isServiceableMobile="isServiceableMobile"
|
||||||
:isServiceableInshop="isServiceableInshop"
|
:isServiceableInshop="isServiceableInshop"
|
||||||
ref="appointmentTypeQuestion"
|
ref="appointmentTypeQuestion"
|
||||||
|
|
@ -64,7 +65,19 @@
|
||||||
validationRules="mobile-location-required"
|
validationRules="mobile-location-required"
|
||||||
ref="mobileLocationQuestions"
|
ref="mobileLocationQuestions"
|
||||||
linkWidgetName="MobileLocationLinkWidget"
|
linkWidgetName="MobileLocationLinkWidget"
|
||||||
modalWidgetName="MobileLocationModalWidget" />
|
modalWidgetName="MobileLocationModalWidget"
|
||||||
|
:onZipUpdateCallback="reloadShopData" />
|
||||||
|
<Transition name="fade" mode="out-in">
|
||||||
|
<shopQuestion
|
||||||
|
ref="shopQuestion"
|
||||||
|
v-show="isShopQuestionDisplayed"
|
||||||
|
v-model="selectedProviderNumber"
|
||||||
|
@providerSelected="onProviderSelected"
|
||||||
|
:serviceZipCode="zipCode"
|
||||||
|
:selectedAppointmentType="selectedAppointmentType"
|
||||||
|
:isDisplayed="isShopQuestionDisplayed"
|
||||||
|
cmsWidgetName="ShopQuestionWidget" />
|
||||||
|
</Transition>
|
||||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||||
<funnel-footer
|
<funnel-footer
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
|
@ -82,6 +95,8 @@ import alert from "@/ux-components/alert/alert";
|
||||||
import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question";
|
import serviceZipModalQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-modal-question";
|
||||||
import mobileLocationModalQuestions from "@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions";
|
import mobileLocationModalQuestions from "@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions";
|
||||||
import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question";
|
import appointmentTypeQuestion from "@/layouts/service-location/appointment-type-question/appointment-type-question";
|
||||||
|
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||||
|
|
||||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
|
@ -119,6 +134,16 @@ defineRule("mobile-location-required", (value) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultProvider = {
|
||||||
|
providerNumber: null,
|
||||||
|
address: {
|
||||||
|
streetAddress: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zip: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "service-location",
|
name: "service-location",
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -133,10 +158,12 @@ export default {
|
||||||
isRecalibrationServiceableInshop: null,
|
isRecalibrationServiceableInshop: null,
|
||||||
isGlassServiceableMobile: null,
|
isGlassServiceableMobile: null,
|
||||||
isRecalibrationServiceableMobile: null,
|
isRecalibrationServiceableMobile: null,
|
||||||
|
selectedAppointmentType: this.getSelectedAppointmentType(),
|
||||||
|
selectedProvider: this.getSelectedProvider(),
|
||||||
|
selectedProviderNumber: this.getSelectedProvider().providerNumber,
|
||||||
mobileFeePart: null,
|
mobileFeePart: null,
|
||||||
zipContainsMilitaryBase: false,
|
zipContainsMilitaryBase: false,
|
||||||
selectedAppointmentType: "",
|
zipCodeCtu: null,
|
||||||
providerNumber: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -150,6 +177,8 @@ export default {
|
||||||
|
|
||||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||||
|
|
||||||
|
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||||
|
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
|
|
@ -168,6 +197,10 @@ export default {
|
||||||
resultKey: "zipCodeData",
|
resultKey: "zipCodeData",
|
||||||
promise: getZipCodeData,
|
promise: getZipCodeData,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resultKey: "shopQuestionInitialData",
|
||||||
|
promise: shopQuestionInitialDataPromise,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
@ -180,6 +213,7 @@ export default {
|
||||||
resultMap.serviceabilityDetails,
|
resultMap.serviceabilityDetails,
|
||||||
resultMap.mobileFeePart
|
resultMap.mobileFeePart
|
||||||
);
|
);
|
||||||
|
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -194,7 +228,7 @@ export default {
|
||||||
if (newValue.zipCode !== this.zipCode) {
|
if (newValue.zipCode !== this.zipCode) {
|
||||||
this.resetMobileLocation();
|
this.resetMobileLocation();
|
||||||
this.selectedAppointmentType = null;
|
this.selectedAppointmentType = null;
|
||||||
this.providerNumber = null;
|
this.selectedProvider = defaultProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = newValue.state;
|
this.state = newValue.state;
|
||||||
|
|
@ -229,7 +263,7 @@ export default {
|
||||||
if (!this.selectedAppointmentType == "Mobile") {
|
if (!this.selectedAppointmentType == "Mobile") {
|
||||||
this.selectedAppointmentType = null;
|
this.selectedAppointmentType = null;
|
||||||
}
|
}
|
||||||
this.providerNumber = null;
|
this.selectedProvider = defaultProvider;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -247,6 +281,12 @@ export default {
|
||||||
return this.isGlassServiceableInshop;
|
return this.isGlassServiceableInshop;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isShopQuestionDisplayed() {
|
||||||
|
return (
|
||||||
|
this.selectedAppointmentType === "Inshop" ||
|
||||||
|
this.selectedAppointmentType === "Dropoff"
|
||||||
|
);
|
||||||
|
},
|
||||||
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
||||||
requiresInshopRecalibration() {
|
requiresInshopRecalibration() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -286,6 +326,7 @@ export default {
|
||||||
setData(zipCodeData, serviceabilityDetails, mobileFeePart) {
|
setData(zipCodeData, serviceabilityDetails, mobileFeePart) {
|
||||||
if (zipCodeData) {
|
if (zipCodeData) {
|
||||||
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
||||||
|
this.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceabilityDetails) {
|
if (serviceabilityDetails) {
|
||||||
|
|
@ -313,6 +354,12 @@ export default {
|
||||||
getServiceZipCodeFromStore() {
|
getServiceZipCodeFromStore() {
|
||||||
return store.getters.order.serviceLocation.zipCode;
|
return store.getters.order.serviceLocation.zipCode;
|
||||||
},
|
},
|
||||||
|
getSelectedAppointmentType() {
|
||||||
|
return store.getters.order.serviceLocation.appointmentType;
|
||||||
|
},
|
||||||
|
getSelectedProvider() {
|
||||||
|
return store.getters.order.serviceLocation.provider ?? defaultProvider;
|
||||||
|
},
|
||||||
setMobileFeePart(mobileFeePart) {
|
setMobileFeePart(mobileFeePart) {
|
||||||
this.mobileFeePart = mobileFeePart;
|
this.mobileFeePart = mobileFeePart;
|
||||||
},
|
},
|
||||||
|
|
@ -334,8 +381,32 @@ export default {
|
||||||
backButtonAction() {
|
backButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
},
|
},
|
||||||
forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
this.$router.navigateWithoutSaving(
|
await this.dispatchStoreAction(
|
||||||
|
this.storeActions.SAVE_SERVICE_LOCATION,
|
||||||
|
{
|
||||||
|
address: this.streetAddress,
|
||||||
|
address2: this.apartmentNumberOrBusinessName,
|
||||||
|
city: this.city,
|
||||||
|
state: this.state,
|
||||||
|
zipCode: this.zipCode,
|
||||||
|
zipCodeCtu: this.zipCodeCtu,
|
||||||
|
appointmentType: this.selectedAppointmentType,
|
||||||
|
isVehicleProtected: this.isVehicleProtected,
|
||||||
|
provider: {
|
||||||
|
providerNumber: this.selectedProvider?.providerNumber,
|
||||||
|
address: {
|
||||||
|
streetAddress: this.selectedProvider?.address?.streetAddress,
|
||||||
|
city: this.selectedProvider?.address?.city,
|
||||||
|
state: this.selectedProvider?.address?.state,
|
||||||
|
zip: this.selectedProvider?.address?.zipCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$router.navigateWithSaving(
|
||||||
this.navigationScenarios.SELECTED_LOCATION,
|
this.navigationScenarios.SELECTED_LOCATION,
|
||||||
this.$route
|
this.$route
|
||||||
);
|
);
|
||||||
|
|
@ -343,6 +414,12 @@ export default {
|
||||||
openModalAction(modalName) {
|
openModalAction(modalName) {
|
||||||
this.$refs[modalName].openModal();
|
this.$refs[modalName].openModal();
|
||||||
},
|
},
|
||||||
|
async reloadShopData() {
|
||||||
|
await this.$refs.shopQuestion.reloadShopData(this.zipCode);
|
||||||
|
},
|
||||||
|
onProviderSelected(selectedProvider) {
|
||||||
|
this.selectedProvider = selectedProvider;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
alert,
|
alert,
|
||||||
|
|
@ -355,6 +432,7 @@ export default {
|
||||||
Form,
|
Form,
|
||||||
loadingModal,
|
loadingModal,
|
||||||
contentGroupModal,
|
contentGroupModal,
|
||||||
|
shopQuestion,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ export default {
|
||||||
},
|
},
|
||||||
linkWidgetName: String,
|
linkWidgetName: String,
|
||||||
modalWidgetName: String,
|
modalWidgetName: String,
|
||||||
|
onZipUpdateCallback: {
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const textboxQuestionWidgetName = "ServiceZipQuestionWidget";
|
const textboxQuestionWidgetName = "ServiceZipQuestionWidget";
|
||||||
|
|
@ -161,6 +164,10 @@ export default {
|
||||||
// Update the page level model
|
// Update the page level model
|
||||||
this.$emit("update:modelValue", this.internalModel);
|
this.$emit("update:modelValue", this.internalModel);
|
||||||
|
|
||||||
|
if (this.onZipUpdateCallback) {
|
||||||
|
await this.onZipUpdateCallback(serviceZipCode);
|
||||||
|
}
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import shopListButton from "./shop-list-button";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
|
describe("service-package-radio.vue", () => {
|
||||||
|
it("Should include buttonLabel in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should include buttonLabelSubCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should include buttonLabelAuxillaryCopy in html", async () => {
|
||||||
|
// Arrange
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: mockProps,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const outputHtml = wrapper.html();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelAuxillaryCopy"]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockProps = {
|
||||||
|
buttonLabel: "buttonLabel test copy",
|
||||||
|
buttonLabelSubCopy: "buttonLabelSubCopy test copy",
|
||||||
|
buttonBodyCopy:
|
||||||
|
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
|
||||||
|
buttonLabelAuxillaryCopy: "buttonLabelAuxillaryCopy test copy",
|
||||||
|
value: 0,
|
||||||
|
modelValue: 0,
|
||||||
|
groupName: "mockGroup",
|
||||||
|
};
|
||||||
|
|
||||||
|
function setupMocks({ mountOptionsMockData = {} }) {
|
||||||
|
const wrapper = mount(shopListButton, {
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
<template>
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<baseInputButton
|
||||||
|
v-bind="$props"
|
||||||
|
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||||
|
v-model="selectedValue">
|
||||||
|
<div
|
||||||
|
:aria-label="buttonLabel"
|
||||||
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
|
<div class="row-one">
|
||||||
|
<span class="m-0 button-label-copy" :class="textPosition">{{
|
||||||
|
buttonLabel
|
||||||
|
}}</span>
|
||||||
|
<span class="m-0 button-label-sub-copy" :class="textPosition">{{
|
||||||
|
buttonLabelSubCopy
|
||||||
|
}}</span>
|
||||||
|
<div
|
||||||
|
class="availability-indicator"
|
||||||
|
:class="availability === 'high' ? 'green' : 'red'">
|
||||||
|
<span class="m-0 button-auxillary-copy">{{ buttonAuxillaryCopy }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="buttonBodyCopy"
|
||||||
|
class="m-0 button-label-sub-copy small"
|
||||||
|
:class="textPosition"
|
||||||
|
v-html="buttonBodyCopy"></span>
|
||||||
|
<span v-if="screenReaderOnlyText" class="sr-only">
|
||||||
|
{{ screenReaderOnlyText }}
|
||||||
|
</span>
|
||||||
|
<loader
|
||||||
|
v-if="isLoaderDisplayed && selectingInitiatesLoad"
|
||||||
|
:class="[this.loaderColor, this.loaderPosition]" />
|
||||||
|
</div>
|
||||||
|
</baseInputButton>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import loader from "@/ux-components/loader/loader";
|
||||||
|
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "shopListButton",
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
props: {
|
||||||
|
loaderColor: String,
|
||||||
|
loaderPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "right",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaderDisplayed: false,
|
||||||
|
availability: "low",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
displayLoader() {
|
||||||
|
this.isLoaderDisplayed = true;
|
||||||
|
},
|
||||||
|
preHandleAnswerChange() {
|
||||||
|
if (this.selectingInitiatesLoad) {
|
||||||
|
this.displayLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
loader,
|
||||||
|
baseInputButton,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.loader {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.list-button {
|
||||||
|
outline: none;
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
position: static; //override bootstrap
|
||||||
|
|
||||||
|
&:focus-visible + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:focus + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 500;
|
||||||
|
background: $blue-100;
|
||||||
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
}
|
||||||
|
&:checked:focus + .list-button-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content p,
|
||||||
|
&:checked + .list-button-content span {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
&:checked + .list-button-content span:nth-child(2) {
|
||||||
|
font-weight: 400;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list-button-content {
|
||||||
|
color: $gray-600;
|
||||||
|
position: relative;
|
||||||
|
background: $white;
|
||||||
|
transition: all 150ms linear;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
&.small {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $gray-550;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-content {
|
||||||
|
.row-one {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.25rem !important;
|
||||||
|
|
||||||
|
.button-label-copy {
|
||||||
|
flex-grow: 0;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-label-sub-copy {
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 1.25rem !important;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #727676;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.availability-indicator {
|
||||||
|
display: none;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.125rem 1.5rem;
|
||||||
|
gap: 0.25rem;
|
||||||
|
background: #e3f2ea;
|
||||||
|
border-radius: 4.5rem;
|
||||||
|
|
||||||
|
.button-auxillary-copy {
|
||||||
|
justify-content: right;
|
||||||
|
line-height: 1.25rem !important;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: #006a36;
|
||||||
|
background: #e3f2ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: #ac160b;
|
||||||
|
background: #e3f2ea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
592
src/layouts/service-location/shop-question/shop-question.spec.js
Normal file
592
src/layouts/service-location/shop-question/shop-question.spec.js
Normal file
|
|
@ -0,0 +1,592 @@
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import { storeActions } from "@/constants/store-actions";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import shopQuestion from "./shop-question";
|
||||||
|
|
||||||
|
jest.mock("@/mixins/base-mixin", () => ({
|
||||||
|
methods: {
|
||||||
|
dispatchStoreAction(action, items, encode) {
|
||||||
|
if (action === mockGetProvidersStoreAction) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve(mockNewShopList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollToPageBottom() {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockGetProvidersStoreAction = storeActions.GET_PROVIDERS;
|
||||||
|
const mockNewShopList = {
|
||||||
|
shopProviders: [
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "COLUMBUS",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "1670 HARMON AVE",
|
||||||
|
zipCode: "43223",
|
||||||
|
},
|
||||||
|
distanceInMiles: 15.9727889297435,
|
||||||
|
providerNumber: "006747",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "Columbus",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "4580 W Broad St",
|
||||||
|
zipCode: "43228",
|
||||||
|
},
|
||||||
|
distanceInMiles: 19.4618116611001,
|
||||||
|
providerNumber: "003342",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCmsContent = {
|
||||||
|
QuestionText: "Select a shop:",
|
||||||
|
};
|
||||||
|
|
||||||
|
const cmsWidgetName = "ShopQuestionWidget";
|
||||||
|
const shopQuestionInitialData = {
|
||||||
|
shopProviders: [
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "WESTERVILLE",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "4403 EXECUTIVE PKWY",
|
||||||
|
zipCode: "43081",
|
||||||
|
},
|
||||||
|
distanceInMiles: 5.16769294095201,
|
||||||
|
providerNumber: "003335",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "WORTHINGTON",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "760 DEARBORN PARK LN",
|
||||||
|
zipCode: "43085",
|
||||||
|
},
|
||||||
|
distanceInMiles: 10.5865432478478,
|
||||||
|
providerNumber: "001820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "COLUMBUS",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "5015 N HIGH ST",
|
||||||
|
zipCode: "43214",
|
||||||
|
},
|
||||||
|
distanceInMiles: 11.738869544543,
|
||||||
|
providerNumber: "003343",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "COLUMBUS",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "1670 HARMON AVE",
|
||||||
|
zipCode: "43223",
|
||||||
|
},
|
||||||
|
distanceInMiles: 15.9727889297435,
|
||||||
|
providerNumber: "006747",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "Columbus",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "4580 W Broad St",
|
||||||
|
zipCode: "43228",
|
||||||
|
},
|
||||||
|
distanceInMiles: 19.4618116611001,
|
||||||
|
providerNumber: "003342",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockMixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||||
|
if (widgetName === cmsWidgetName) {
|
||||||
|
return mockCmsContent[cmsFieldName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("shop-question.vue", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.scrollTo = jest.fn();
|
||||||
|
|
||||||
|
container.classList.add("page-container-grouped-styles");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display first three shops when an appointment type has already been selected", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: null,
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
isDisplayed: true,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.answers.length).toEqual(3);
|
||||||
|
expect(wrapper.vm.answers).toEqual([
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
|
||||||
|
buttonLabel: "4403 Executive Pkwy",
|
||||||
|
buttonLabelSubCopy: "5 mi",
|
||||||
|
value: "003335",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
|
||||||
|
buttonLabel: "760 Dearborn Park Ln",
|
||||||
|
buttonLabelSubCopy: "10.5 mi",
|
||||||
|
value: "001820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
|
||||||
|
buttonLabel: "5015 N High St",
|
||||||
|
buttonLabelSubCopy: "11.5 mi",
|
||||||
|
value: "003343",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
isDisplayed: true,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(showMoreShopsLink.exists()).toBe(true);
|
||||||
|
expect(showMoreShopsLink.isVisible()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
|
||||||
|
// Arrange
|
||||||
|
const alsoShopQuestionInitialData = {
|
||||||
|
shopProviders: [
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "WESTERVILLE",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "4403 EXECUTIVE PKWY",
|
||||||
|
zipCode: "43081",
|
||||||
|
},
|
||||||
|
distanceInMiles: 5.16769294095201,
|
||||||
|
providerNumber: "003335",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "WORTHINGTON",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "760 DEARBORN PARK LN",
|
||||||
|
zipCode: "43085",
|
||||||
|
},
|
||||||
|
distanceInMiles: 10.5865432478478,
|
||||||
|
providerNumber: "001820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: {
|
||||||
|
city: "COLUMBUS",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "5015 N HIGH ST",
|
||||||
|
zipCode: "43214",
|
||||||
|
},
|
||||||
|
distanceInMiles: 11.738869544543,
|
||||||
|
providerNumber: "003343",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
isDisplayed: true,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(alsoShopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(showMoreShopsLink.exists()).toBe(true);
|
||||||
|
expect(showMoreShopsLink.isVisible()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display the next three shops when the 'Show more location' link is clicked", async () => {
|
||||||
|
// Arrange
|
||||||
|
const displayedAnswers = [
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
|
||||||
|
buttonLabel: "4403 Executive Pkwy",
|
||||||
|
buttonLabelSubCopy: "5 mi",
|
||||||
|
value: "003335",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
|
||||||
|
buttonLabel: "760 Dearborn Park Ln",
|
||||||
|
buttonLabelSubCopy: "10.5 mi",
|
||||||
|
value: "001820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
|
||||||
|
buttonLabel: "5015 N High St",
|
||||||
|
buttonLabelSubCopy: "11.5 mi",
|
||||||
|
value: "003343",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
isDisplayed: true,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
wrapper.vm.answers = displayedAnswers;
|
||||||
|
wrapper.vm.shopIndex = 3;
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
showMoreShopsLink.trigger("click");
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.answers.length).toBe(6);
|
||||||
|
expect(wrapper.vm.answers).toEqual([
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
|
||||||
|
buttonLabel: "4403 Executive Pkwy",
|
||||||
|
buttonLabelSubCopy: "5 mi",
|
||||||
|
value: "003335",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
|
||||||
|
buttonLabel: "760 Dearborn Park Ln",
|
||||||
|
buttonLabelSubCopy: "10.5 mi",
|
||||||
|
value: "001820",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
|
||||||
|
buttonLabel: "5015 N High St",
|
||||||
|
buttonLabelSubCopy: "11.5 mi",
|
||||||
|
value: "003343",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "1670 Harmon Ave, Columbus, OH 43223",
|
||||||
|
buttonLabel: "1670 Harmon Ave",
|
||||||
|
buttonLabelSubCopy: "16 mi",
|
||||||
|
value: "006747",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "3938 Powell Rd, Powell, OH 43065",
|
||||||
|
buttonLabel: "3938 Powell Rd",
|
||||||
|
buttonLabelSubCopy: "16.5 mi",
|
||||||
|
value: "003341",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttonBodyCopy: "4580 W Broad St, Columbus, OH 43228",
|
||||||
|
buttonLabel: "4580 W Broad St",
|
||||||
|
buttonLabelSubCopy: "19.5 mi",
|
||||||
|
value: "003342",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should display the number of shops necessary to show a previously selected shop", async () => {
|
||||||
|
// Arrange
|
||||||
|
const selectedProvider = {
|
||||||
|
providerNumber: "003341",
|
||||||
|
};
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: selectedProvider.providerNumber,
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.answers.length).toEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should reset the answers when the selected appointment type changes", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mixins: [mockMixin],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
address: {
|
||||||
|
city: "POWELL",
|
||||||
|
country: "US",
|
||||||
|
state: "OH",
|
||||||
|
streetAddress: "3938 POWELL RD",
|
||||||
|
zipCode: "43065",
|
||||||
|
},
|
||||||
|
distanceInMiles: 16.2690495685233,
|
||||||
|
providerNumber: "003341",
|
||||||
|
},
|
||||||
|
serviceZipCode: "43081",
|
||||||
|
selectedAppointmentType: "Dropoff",
|
||||||
|
cmsWidgetName: cmsWidgetName,
|
||||||
|
},
|
||||||
|
mountOptions: {
|
||||||
|
attachTo: document.body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
selectedAppointmentType: "Inshop",
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.answers.length).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
// it("Should reload the shops when the service zip code changes", async () => {
|
||||||
|
// // Arrange
|
||||||
|
// const { wrapper } = setupMocks({
|
||||||
|
// mixins: [mockMixin],
|
||||||
|
// props: {
|
||||||
|
// modelValue: {
|
||||||
|
// address: {
|
||||||
|
// city: "POWELL",
|
||||||
|
// country: "US",
|
||||||
|
// state: "OH",
|
||||||
|
// streetAddress: "3938 POWELL RD",
|
||||||
|
// zipCode: "43065",
|
||||||
|
// },
|
||||||
|
// distanceInMiles: 16.2690495685233,
|
||||||
|
// providerNumber: "003341",
|
||||||
|
// },
|
||||||
|
// serviceZipCode: "43081",
|
||||||
|
// selectedAppointmentType: "Dropoff",
|
||||||
|
// cmsWidgetName: cmsWidgetName,
|
||||||
|
// isDisplayed: true
|
||||||
|
// },
|
||||||
|
// mountOptions: {
|
||||||
|
// attachTo: document.body,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// wrapper.vm.$options.methods.loadInitialData = jest.fn().mockImplementation(() => {
|
||||||
|
// return new Promise((resolve) => {
|
||||||
|
// resolve(mockNewShopList);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Act
|
||||||
|
// wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
// await wrapper.vm.$options.watch.serviceZipCode.handler.call(wrapper.vm, "43054");
|
||||||
|
|
||||||
|
// // Assert
|
||||||
|
// expect(wrapper.vm.shopProviders.length).toEqual(3);
|
||||||
|
// expect(wrapper.vm.shopProviders).toEqual(mockNewShopList.shopProviders);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it("Should clear the existing answers when the service zip code changes", async () => {
|
||||||
|
// // Arrange
|
||||||
|
// const { wrapper } = setupMocks({
|
||||||
|
// mixins: [mockMixin],
|
||||||
|
// props: {
|
||||||
|
// modelValue: {
|
||||||
|
// address: {
|
||||||
|
// city: "POWELL",
|
||||||
|
// country: "US",
|
||||||
|
// state: "OH",
|
||||||
|
// streetAddress: "3938 POWELL RD",
|
||||||
|
// zipCode: "43065",
|
||||||
|
// },
|
||||||
|
// distanceInMiles: 16.2690495685233,
|
||||||
|
// providerNumber: "003341",
|
||||||
|
// },
|
||||||
|
// serviceZipCode: "43081",
|
||||||
|
// selectedAppointmentType: "Dropoff",
|
||||||
|
// cmsWidgetName: cmsWidgetName,
|
||||||
|
// },
|
||||||
|
// mountOptions: {
|
||||||
|
// attachTo: document.body,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// //Act
|
||||||
|
// wrapper.vm.initializeComponent(shopQuestionInitialData);
|
||||||
|
|
||||||
|
// wrapper.setProps({
|
||||||
|
// selectedAppointmentType: "Inshop",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// // Assert
|
||||||
|
// expect(wrapper.vm.answers.length).toEqual(3);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {
|
||||||
|
const resultingMountOptions = getMountOptions({
|
||||||
|
...mountOptions,
|
||||||
|
mixins,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props) resultingMountOptions.propsData = props;
|
||||||
|
|
||||||
|
const wrapper = isShallowMount
|
||||||
|
? shallowMount(shopQuestion, resultingMountOptions)
|
||||||
|
: mount(shopQuestion, resultingMountOptions);
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
252
src/layouts/service-location/shop-question/shop-question.vue
Normal file
252
src/layouts/service-location/shop-question/shop-question.vue
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
<template>
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div v-if="isDisplayed" class="shop-question" aria-live="polite">
|
||||||
|
<alert
|
||||||
|
ref="alertDropoffInformation"
|
||||||
|
v-if="displayDropoffInformation"
|
||||||
|
class="mb-4 drop-off-alert"
|
||||||
|
cmsWidgetName="AlertDropoffInformationWidget"
|
||||||
|
alertClass="alert-info"
|
||||||
|
v-bind:isDismissible="false" />
|
||||||
|
<buttonQuestion
|
||||||
|
ref="buttonQuestion"
|
||||||
|
buttonTypeString="shopListButton"
|
||||||
|
:buttonTypeObject="shopListButton"
|
||||||
|
class="radioQuestion"
|
||||||
|
:questionText="questionText"
|
||||||
|
:answers="answers"
|
||||||
|
groupName="chooseShop"
|
||||||
|
textPosition="text-start"
|
||||||
|
v-model="selectedValue"
|
||||||
|
isRequired
|
||||||
|
validationRules="option-required" />
|
||||||
|
<textLink
|
||||||
|
v-show="displaySeeMoreLocationsLink"
|
||||||
|
ref="showMoreShopsLink"
|
||||||
|
class="show-more-shops-link"
|
||||||
|
id="showMoreShopsId"
|
||||||
|
cmsWidgetName="ShowMoreShopsLinkWidget"
|
||||||
|
linkType="text"
|
||||||
|
:text="showMoreShopsLinkText"
|
||||||
|
href="#!"
|
||||||
|
@click-event="getNextShopsFromList"
|
||||||
|
:aria-label="showMoreShopsLinkText" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import alert from "@/ux-components/alert/alert";
|
||||||
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
|
import shopListButton from "./shop-list-button/shop-list-button";
|
||||||
|
import textLink from "@/ux-components/text-link/text-link";
|
||||||
|
|
||||||
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "shop-question",
|
||||||
|
mixins: [baseMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
shopProviders: [],
|
||||||
|
shopListButton: shopListButton,
|
||||||
|
answers: [],
|
||||||
|
shopIndex: 0,
|
||||||
|
displaySeeMoreLocationsLink: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
serviceZipCode: String,
|
||||||
|
selectedAppointmentType: String,
|
||||||
|
cmsWidgetName: String,
|
||||||
|
validationRules: String,
|
||||||
|
isDisplayed: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
questionText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||||
|
},
|
||||||
|
selectedValue: {
|
||||||
|
get: function () {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
// Button Question only supports primitive values so we must get the full object to emit to the page
|
||||||
|
const provider = this.shopProviders?.find(
|
||||||
|
(provider) => provider.providerNumber == newValue
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
this.$emit("providerSelected", provider);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
displayDropoffInformation() {
|
||||||
|
return this.selectedAppointmentType == "Dropoff";
|
||||||
|
},
|
||||||
|
showMoreShopsLinkText() {
|
||||||
|
return this.getCmsContent("ShowMoreShopsLinkWidget", "Text");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadInitialData(serviceZipCode) {
|
||||||
|
return this.loadData(serviceZipCode);
|
||||||
|
},
|
||||||
|
loadData(serviceZipCode) {
|
||||||
|
return baseMixin.methods.dispatchStoreAction(storeActions.GET_PROVIDERS, {
|
||||||
|
serviceZipCode: serviceZipCode,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initializeComponent(shopQuestionInitialData) {
|
||||||
|
this.shopProviders = shopQuestionInitialData.shopProviders;
|
||||||
|
},
|
||||||
|
async getNextShopsFromList(numberToGet = 3) {
|
||||||
|
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);
|
||||||
|
const state = shopProvider.address.state;
|
||||||
|
const zipCode = shopProvider.address.zipCode;
|
||||||
|
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
buttonLabel: streetAddress,
|
||||||
|
buttonLabelSubCopy: `${distanceInMiles} mi`,
|
||||||
|
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
|
||||||
|
value: shopProvider.providerNumber,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.answers.length === 0) {
|
||||||
|
this.answers = mappedData;
|
||||||
|
} else {
|
||||||
|
mappedData.forEach((shop) => {
|
||||||
|
this.answers.push(shop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
if (this.shopIndex == this.shopProviders.length) {
|
||||||
|
this.displaySeeMoreLocationsLink = false;
|
||||||
|
} else {
|
||||||
|
this.displaySeeMoreLocationsLink = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
this.scrollToPageBottom();
|
||||||
|
},
|
||||||
|
resetAnswers() {
|
||||||
|
this.answers = [];
|
||||||
|
this.shopIndex = 0;
|
||||||
|
this.selectedValue = "";
|
||||||
|
|
||||||
|
if (this.$refs.buttonQuestion) {
|
||||||
|
this.$refs.buttonQuestion.resetField();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async reloadShopData(serviceZipCode) {
|
||||||
|
const result = await this.loadData(serviceZipCode);
|
||||||
|
this.initializeComponent(result.data);
|
||||||
|
|
||||||
|
this.resetAnswers();
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
await this.getNextShopsFromList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedAppointmentType: {
|
||||||
|
async handler(newValue) {
|
||||||
|
this.resetAnswers();
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
if (newValue !== "Mobile") {
|
||||||
|
await this.getNextShopsFromList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shopProviders: {
|
||||||
|
async handler(newValue) {
|
||||||
|
//this.resetAnswers();
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
if (this.selectedAppointmentType) {
|
||||||
|
const selectedShopIndex = newValue.findIndex(
|
||||||
|
(provider) => provider.providerNumber == this.modelValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedShopIndex >= 3) {
|
||||||
|
await this.getNextShopsFromList(selectedShopIndex + 1);
|
||||||
|
} else {
|
||||||
|
await this.getNextShopsFromList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
alert,
|
||||||
|
buttonQuestion,
|
||||||
|
textLink,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.shop-question {
|
||||||
|
margin-top: 1rem !important;
|
||||||
|
text-align: center !important;
|
||||||
|
|
||||||
|
.button-question {
|
||||||
|
.question-text {
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-off-alert {
|
||||||
|
.alert-heading {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
line-height: 1.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
padding-left: 1.5rem !important;
|
||||||
|
padding-right: 0.5rem !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vin-information">
|
<div class="vin-information">
|
||||||
<div class="vin-toggle mt-2" :class="[isActive ? 'active' : '']" @click="toggleClass()">
|
<div class="vin-toggle" :class="[isActive ? 'active' : '']" @click="toggleClass()">
|
||||||
<textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" />
|
<textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" />
|
||||||
</div>
|
</div>
|
||||||
<div class="vin-info">
|
<div class="vin-info">
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@
|
||||||
alertClass="alert-danger" />
|
alertClass="alert-danger" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<vinInformation />
|
<vinInformation />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row mb-0 mt-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
cmsWidgetName="ServiceZipQuestionWidget"
|
cmsWidgetName="ServiceZipQuestionWidget"
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
validationRules="zip-required|zip-format" />
|
validationRules="zip-required|zip-format" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-4">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
cmsWidgetName="EmailAddressQuestionWidget"
|
cmsWidgetName="EmailAddressQuestionWidget"
|
||||||
|
|
|
||||||
|
|
@ -146,18 +146,18 @@ export default {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data) {
|
if (response?.data) {
|
||||||
if (response.data.sessionKey && skey === 0) {
|
if (response?.data.sessionKey && skey === 0) {
|
||||||
setCookieProperties(
|
setCookieProperties(
|
||||||
{ [cookieNames.SESSION_KEY]: response.data.sessionKey },
|
{ [cookieNames.SESSION_KEY]: response?.data.sessionKey },
|
||||||
{
|
{
|
||||||
useDefaultFunnelCookieAttributes: false,
|
useDefaultFunnelCookieAttributes: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (response.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") {
|
if (response?.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") {
|
||||||
setCookieProperties(
|
setCookieProperties(
|
||||||
{ [cookieNames.SESSION_ID]: response.data.sessionId },
|
{ [cookieNames.SESSION_ID]: response?.data.sessionId },
|
||||||
{
|
{
|
||||||
maxAge: 60 * 30, // 30 minutes
|
maxAge: 60 * 30, // 30 minutes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ export default {
|
||||||
getTotalLineItemPrice(lineItem) {
|
getTotalLineItemPrice(lineItem) {
|
||||||
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
|
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
|
||||||
},
|
},
|
||||||
|
scrollToPageTop() {
|
||||||
|
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
|
||||||
|
container.scrollTo({ top: 0, left: 0, behavior: "smooth" });
|
||||||
|
},
|
||||||
|
scrollToPageBottom() {
|
||||||
|
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
|
||||||
|
container.scrollTo({ top: container.scrollHeight, left: 0, behavior: "smooth" });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
storeActions() {
|
storeActions() {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { applicationConfig } from "../constants/application-config";
|
||||||
// Components
|
// Components
|
||||||
import datePicker from "@/digital-components/date-picker/date-picker.vue";
|
import datePicker from "@/digital-components/date-picker/date-picker.vue";
|
||||||
import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue";
|
import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue";
|
||||||
|
import review from "@/layouts/review/review";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
|
@ -43,6 +44,11 @@ const routes = [
|
||||||
name: "date-picker",
|
name: "date-picker",
|
||||||
component: datePicker,
|
component: datePicker,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/review", // This is a temporary route for testing.
|
||||||
|
name: "review",
|
||||||
|
component: review,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "root",
|
name: "root",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,15 @@ const getDefaultState = () => {
|
||||||
zipCode: null,
|
zipCode: null,
|
||||||
zipCodeCtu: null,
|
zipCodeCtu: null,
|
||||||
appointmentType: null,
|
appointmentType: null,
|
||||||
|
provider: {
|
||||||
|
providerNumber: null,
|
||||||
|
address: {
|
||||||
|
streetAddress: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
zip: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
emailAddress: null,
|
emailAddress: null,
|
||||||
|
|
@ -70,6 +79,12 @@ const getDefaultState = () => {
|
||||||
},
|
},
|
||||||
parentAccountNumber: 0,
|
parentAccountNumber: 0,
|
||||||
},
|
},
|
||||||
|
schedule: {
|
||||||
|
date: null,
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
routeCode: null,
|
||||||
|
},
|
||||||
referralNumber: null,
|
referralNumber: null,
|
||||||
referralDate: null,
|
referralDate: null,
|
||||||
referralCorrelationId: null,
|
referralCorrelationId: null,
|
||||||
|
|
@ -226,10 +241,34 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
updateServiceLocation(state, serviceLocationInfo) {
|
updateServiceLocation(state, serviceLocationInfo) {
|
||||||
state.order.serviceLocation.address = serviceLocationInfo.address;
|
state.order.serviceLocation.address = serviceLocationInfo.address;
|
||||||
|
state.order.serviceLocation.address2 = serviceLocationInfo.address2;
|
||||||
state.order.serviceLocation.city = serviceLocationInfo.city;
|
state.order.serviceLocation.city = serviceLocationInfo.city;
|
||||||
state.order.serviceLocation.state = serviceLocationInfo.state;
|
state.order.serviceLocation.state = serviceLocationInfo.state;
|
||||||
state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode;
|
state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode;
|
||||||
state.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu;
|
state.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu;
|
||||||
|
state.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType;
|
||||||
|
state.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected;
|
||||||
|
|
||||||
|
if (serviceLocationInfo.provider) {
|
||||||
|
state.order.serviceLocation.provider.providerNumber =
|
||||||
|
serviceLocationInfo.provider?.providerNumber;
|
||||||
|
state.order.serviceLocation.provider.address.streetAddress =
|
||||||
|
serviceLocationInfo.provider?.address?.streetAddress;
|
||||||
|
state.order.serviceLocation.provider.address.city =
|
||||||
|
serviceLocationInfo.provider?.address?.city;
|
||||||
|
state.order.serviceLocation.provider.address.state =
|
||||||
|
serviceLocationInfo.provider?.address?.state;
|
||||||
|
state.order.serviceLocation.provider.address.zip =
|
||||||
|
serviceLocationInfo.provider?.address?.zip;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateSchedule(state, scheduleInfo) {
|
||||||
|
if (scheduleInfo) {
|
||||||
|
state.order.schedule.date = scheduleInfo.date;
|
||||||
|
state.order.schedule.startTime = scheduleInfo.startTime;
|
||||||
|
state.order.schedule.endTime = scheduleInfo.endTime;
|
||||||
|
state.order.schedule.routeCode = scheduleInfo.routeCode;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// applicationUser MUTATIONS
|
// applicationUser MUTATIONS
|
||||||
|
|
@ -358,15 +397,31 @@ export const mutations = {
|
||||||
state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData;
|
state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData;
|
||||||
state.order.payment.parentAccountNumber =
|
state.order.payment.parentAccountNumber =
|
||||||
sessionInformation.order.payment.parentAccountNumber;
|
sessionInformation.order.payment.parentAccountNumber;
|
||||||
state.order.providerNumber = sessionInformation.order.providerNumber;
|
|
||||||
(state.order.serviceLocation.address =
|
(state.order.serviceLocation.address =
|
||||||
sessionInformation.order.serviceLocation.streetAddress),
|
sessionInformation.order.serviceLocation.streetAddress),
|
||||||
|
(state.order.serviceLocation.address2 =
|
||||||
|
sessionInformation.order.serviceLocation.address2),
|
||||||
(state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city),
|
(state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city),
|
||||||
(state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state),
|
(state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state),
|
||||||
(state.order.serviceLocation.zipCode =
|
(state.order.serviceLocation.zipCode =
|
||||||
sessionInformation.order.serviceLocation.zipCode),
|
sessionInformation.order.serviceLocation.zipCode),
|
||||||
(state.order.serviceLocation.zipCodeCtu =
|
(state.order.serviceLocation.zipCodeCtu =
|
||||||
sessionInformation.order.serviceLocation.zipCodeCtu);
|
sessionInformation.order.serviceLocation.zipCodeCtu);
|
||||||
|
state.order.serviceLocation.appointmentType =
|
||||||
|
sessionInformation.order.serviceLocation.appointmentType;
|
||||||
|
state.order.serviceLocation.isVehicleProtected =
|
||||||
|
sessionInformation.order.serviceLocation.isVehicleProtected;
|
||||||
|
|
||||||
|
state.order.serviceLocation.provider.providerNumber =
|
||||||
|
sessionInformation.order.serviceLocation.provider?.providerNumber;
|
||||||
|
state.order.serviceLocation.provider.address.streetAddress =
|
||||||
|
sessionInformation.order.serviceLocation.provider?.address?.streetAddress;
|
||||||
|
state.order.serviceLocation.provider.address.city =
|
||||||
|
sessionInformation.order.serviceLocation.provider?.address?.city;
|
||||||
|
state.order.serviceLocation.provider.address.state =
|
||||||
|
sessionInformation.order.serviceLocation.provider?.address?.state;
|
||||||
|
state.order.serviceLocation.provider.address.zip =
|
||||||
|
sessionInformation.order.serviceLocation.provider?.address?.zip;
|
||||||
|
|
||||||
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
||||||
state.order.payment.insuranceCoverage.isVerified =
|
state.order.payment.insuranceCoverage.isVerified =
|
||||||
|
|
@ -380,6 +435,11 @@ export const mutations = {
|
||||||
state.applicationUser.crmCustomerId = sessionInformation.applicationUser.crmCustomerId;
|
state.applicationUser.crmCustomerId = sessionInformation.applicationUser.crmCustomerId;
|
||||||
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
|
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
|
||||||
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
|
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
|
||||||
|
|
||||||
|
state.order.schedule.date = sessionInformation.order.schedule?.date;
|
||||||
|
state.order.schedule.startTime = sessionInformation.order.schedule?.startTime;
|
||||||
|
state.order.schedule.endTime = sessionInformation.order.schedule?.endTime;
|
||||||
|
state.order.schedule.routeCode = sessionInformation.order.schedule?.routeCode;
|
||||||
},
|
},
|
||||||
updateExperiments(state, experiments) {
|
updateExperiments(state, experiments) {
|
||||||
state.applicationUser.experiments = experiments;
|
state.applicationUser.experiments = experiments;
|
||||||
|
|
@ -681,6 +741,15 @@ export const actions = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Location API Actions
|
||||||
|
getAlertReasonsByCtu(context, { ctu }) {
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetAlertReasons.method,
|
||||||
|
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Misc Actions
|
// Misc Actions
|
||||||
updateStoreWithSaveSessionResponse(
|
updateStoreWithSaveSessionResponse(
|
||||||
context,
|
context,
|
||||||
|
|
@ -727,12 +796,21 @@ export const actions = {
|
||||||
experimentsForUser: experimentsForUser,
|
experimentsForUser: experimentsForUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods
|
||||||
method: endpoints.LogPageView.method,
|
.callHttpClient({
|
||||||
endpoint: endpoints.LogPageView.url,
|
method: endpoints.LogPageView.method,
|
||||||
payload: payload,
|
endpoint: endpoints.LogPageView.url,
|
||||||
logApiCall: false,
|
payload: payload,
|
||||||
});
|
logApiCall: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log("Analytics Service Error: " + error.data);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
logCustomEvent(
|
logCustomEvent(
|
||||||
context,
|
context,
|
||||||
|
|
@ -763,12 +841,21 @@ export const actions = {
|
||||||
experimentsForUser: experimentsForUser,
|
experimentsForUser: experimentsForUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods
|
||||||
method: endpoints.LogCustomEvent.method,
|
.callHttpClient({
|
||||||
endpoint: endpoints.LogCustomEvent.url,
|
method: endpoints.LogCustomEvent.method,
|
||||||
payload: payload,
|
endpoint: endpoints.LogCustomEvent.url,
|
||||||
logApiCall: false,
|
payload: payload,
|
||||||
});
|
logApiCall: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log("Analytics Service Error: " + error.data);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
|
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
|
||||||
var payload = {
|
var payload = {
|
||||||
|
|
@ -782,12 +869,21 @@ export const actions = {
|
||||||
referrer: referrer,
|
referrer: referrer,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods
|
||||||
method: endpoints.InitializeSession.method,
|
.callHttpClient({
|
||||||
endpoint: endpoints.InitializeSession.url,
|
method: endpoints.InitializeSession.method,
|
||||||
payload: payload,
|
endpoint: endpoints.InitializeSession.url,
|
||||||
logApiCall: false,
|
payload: payload,
|
||||||
});
|
logApiCall: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log("Analytics Service Error: " + error.data);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Misc Actions
|
// Misc Actions
|
||||||
|
|
@ -927,13 +1023,13 @@ export const actions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getMobileFeePart(context) {
|
getMobileFeePart(context) {
|
||||||
const serviceType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||||
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||||
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetMobileFeePart.method,
|
method: endpoints.GetMobileFeePart.method,
|
||||||
endpoint: `${endpoints.GetMobileFeePart.url}/${serviceType}/${parentAccountNumber}/${billToAccountNumber}`,
|
endpoint: `${endpoints.GetMobileFeePart.url}/${damageType}/${parentAccountNumber}/${billToAccountNumber}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -943,13 +1039,34 @@ export const actions = {
|
||||||
partNumber: lineItem.partNumber,
|
partNumber: lineItem.partNumber,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const lineItemsToSend = buildQueryStringParameterFromArrayOfComplexObjects(
|
const lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
|
||||||
lineItemsWithOnlyPartNumbers,
|
lineItemsWithOnlyPartNumbers,
|
||||||
"lineItems"
|
"lineItems"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const vehicle = context.getters.vehicle;
|
||||||
|
const carId = vehicle.carId;
|
||||||
|
const damage = context.getters.damage;
|
||||||
|
const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace);
|
||||||
|
|
||||||
|
const glassPieces = buildQueryStringParameterFromArrayOfComplexObjects(
|
||||||
|
glassArray,
|
||||||
|
"glassPieces"
|
||||||
|
);
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetServiceabilityDetails.method,
|
method: endpoints.GetServiceabilityDetails.method,
|
||||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&${lineItemsToSend}`,
|
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getProviders(context, { serviceZipCode }) {
|
||||||
|
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||||
|
const shopRadiusInMiles = 100;
|
||||||
|
|
||||||
|
return globalMethods.callHttpClient({
|
||||||
|
method: endpoints.GetProviders.method,
|
||||||
|
endpoint: `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -964,7 +1081,7 @@ export const actions = {
|
||||||
endpoint: endpoints.GetSupportingItems.url,
|
endpoint: endpoints.GetSupportingItems.url,
|
||||||
payload: {
|
payload: {
|
||||||
carId: carId,
|
carId: carId,
|
||||||
serviceType: isRepair ? "Repair" : "Replace",
|
damageType: isRepair ? "Repair" : "Replace",
|
||||||
parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
parentAccountNumber: applicationConfig.CASH_PARENT_ACCOUNT_NUMBER,
|
||||||
parts: glassPartsArray,
|
parts: glassPartsArray,
|
||||||
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
||||||
|
|
@ -1109,13 +1226,31 @@ export const actions = {
|
||||||
isInsurance: order.payment.isInsurance ?? false,
|
isInsurance: order.payment.isInsurance ?? false,
|
||||||
parentAccountNumber: order.payment.parentAccountNumber,
|
parentAccountNumber: order.payment.parentAccountNumber,
|
||||||
},
|
},
|
||||||
providerNumber: "",
|
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
streetAddress: order.serviceLocation.address,
|
streetAddress: order.serviceLocation.address,
|
||||||
|
streetAddress2: order.serviceLocation.address2,
|
||||||
city: order.serviceLocation.city,
|
city: order.serviceLocation.city,
|
||||||
state: order.serviceLocation.state,
|
state: order.serviceLocation.state,
|
||||||
zipCode: order.serviceLocation.zipCode,
|
zipCode: order.serviceLocation.zipCode,
|
||||||
zipCodeCtu: order.serviceLocation.zipCodeCtu,
|
zipCodeCtu: order.serviceLocation.zipCodeCtu,
|
||||||
|
appointmentType: order.serviceLocation.appointmentType,
|
||||||
|
isVehicleProtected: order.serviceLocation.isVehicleProtected,
|
||||||
|
provider: {
|
||||||
|
providerNumber: order.serviceLocation.provider?.providerNumber,
|
||||||
|
address: {
|
||||||
|
streetAddress:
|
||||||
|
order.serviceLocation.provider?.address?.streetAddress,
|
||||||
|
city: order.serviceLocation.provider?.address?.city,
|
||||||
|
state: order.serviceLocation.provider?.address?.state,
|
||||||
|
zip: order.serviceLocation.provider?.address?.zip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
date: order.schedule?.date,
|
||||||
|
startTime: order.schedule?.startTime,
|
||||||
|
endTime: order.schedule?.endTime,
|
||||||
|
routeCode: order.schedule?.routeCode,
|
||||||
},
|
},
|
||||||
existingPromoCode: null,
|
existingPromoCode: null,
|
||||||
referralCorrelationId: order.referralCorrelationId,
|
referralCorrelationId: order.referralCorrelationId,
|
||||||
|
|
@ -1560,6 +1695,9 @@ export const actions = {
|
||||||
return availableLineItems;
|
return availableLineItems;
|
||||||
},
|
},
|
||||||
// Misc order actions
|
// Misc order actions
|
||||||
|
saveSchedule(context, scheduleInfo) {
|
||||||
|
context.commit(storeMutations.UPDATE_SCHEDULE, scheduleInfo);
|
||||||
|
},
|
||||||
saveServiceLocation(context, serviceLocationInfo) {
|
saveServiceLocation(context, serviceLocationInfo) {
|
||||||
context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo);
|
context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue