commit
25607e8f19
13 changed files with 724 additions and 305 deletions
|
|
@ -15,7 +15,7 @@ module.exports = {
|
||||||
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
||||||
"!src/layouts/reveal/**/*.vue",
|
"!src/layouts/reveal/**/*.vue",
|
||||||
"!src/ux-components/text-link/**/*.vue",
|
"!src/ux-components/text-link/**/*.vue",
|
||||||
"!src/common-components/date-picker/**/*.vue", // Temp until unit tests completed
|
"!src/digital-components/date-picker/**/*.vue", // Temp until unit tests completed
|
||||||
"!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing
|
"!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing
|
||||||
"!src/common-components/funnel-header/menu-modal/**/*.vue",
|
"!src/common-components/funnel-header/menu-modal/**/*.vue",
|
||||||
"!src/layouts/schedule/*.vue", // Temp test exclusion while in development
|
"!src/layouts/schedule/*.vue", // Temp test exclusion while in development
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,11 @@ const endpoints = {
|
||||||
url: "/parts/api/v1/parts/part-from-capability-answer",
|
url: "/parts/api/v1/parts/part-from-capability-answer",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
GetShopTimeSlots: {
|
||||||
|
url: "/schedule/api/v1/schedule/shop-time-slots",
|
||||||
|
mockUrl: "https://mockey.qa.sagaws.net/service/shop-time-slots", // TODO: REMOVE MOCKURL
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
SaveSession: {
|
SaveSession: {
|
||||||
url: "/order/api/v1/order/save-session",
|
url: "/order/api/v1/order/save-session",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
const monthsOfYear = [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
];
|
|
||||||
|
|
||||||
export { monthsOfYear };
|
|
||||||
|
|
@ -32,6 +32,7 @@ const storeActions = {
|
||||||
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
||||||
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
GET_MOBILE_FEE_PART: "getMobileFeePart",
|
||||||
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
|
||||||
|
GET_SHOP_TIME_SLOTS: "getShopTimeSlots",
|
||||||
GET_PROVIDERS: "getProviders",
|
GET_PROVIDERS: "getProviders",
|
||||||
SAVE_SESSION: "saveSession",
|
SAVE_SESSION: "saveSession",
|
||||||
LOAD_SESSION: "loadSession",
|
LOAD_SESSION: "loadSession",
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
test.todo("some test to be written in the future");
|
|
||||||
|
|
@ -6,15 +6,55 @@ import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
|
||||||
describe("date-picker.vue", () => {
|
describe("date-picker.vue", () => {
|
||||||
describe("initial setup", () => {
|
describe("initial setup", () => {
|
||||||
test("Creates a date object from today", () => {
|
test("Creates a date object from today", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = await setupMocks({
|
||||||
|
propsData: {
|
||||||
|
selectableDatesSetting: "custom"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
wrapper.vm.loadInitialData = jest.fn().mockImplementation(
|
||||||
const testResult = wrapper.vm.todayDate instanceof Date;
|
() =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
resolve({
|
||||||
|
data: [responseValue],
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.loadInitialData).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// console.log("wrapper.vm.today: ", wrapper.vm.today)
|
||||||
|
|
||||||
|
// // Act
|
||||||
|
// const testResult = wrapper.vm.today instanceof Date;
|
||||||
|
// console.log("testResult: ", testResult)
|
||||||
|
|
||||||
// Assert
|
// // Assert
|
||||||
expect(testResult).toEqual(true);
|
// expect(testResult).toEqual(true);
|
||||||
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
@ -1,58 +1,68 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="date-picker text-center" :class="calendarViewDirection">
|
<div class="date-picker text-center" :class="calendarViewDirection">
|
||||||
<fieldset v-if="months">
|
<fieldset id="date-picker-fieldset" ref="datePickerFieldset">
|
||||||
<legend class="sr-only">Date Picker</legend>
|
<legend class="sr-only">Select a day and time</legend>
|
||||||
<div
|
<div
|
||||||
v-for="month in months"
|
v-for="month in months"
|
||||||
:key="`${month.monthLabel}-${month.yearNum.toString()}-${months.length}`">
|
:key="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||||
|
:id="`${month.monthLabel}-${month.yearNum?.toString()}`"
|
||||||
|
class="calendar-grid-container position-relative"
|
||||||
|
:class="[
|
||||||
|
hideSomeDaysForInitialView ? 'partial-month-initial-view' : '',
|
||||||
|
month.monthClass,
|
||||||
|
]">
|
||||||
|
<div class="month-year body-small d-flex align-items-center small">
|
||||||
|
{{ month.monthLabel }} {{ month.yearNum?.toString() }}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="calendar-grid-container"
|
v-if="calendarViewDirection === 'future'"
|
||||||
:class="[isInitialView === true ? 'initial-view' : '', month.monthClass]">
|
class="legend caption d-flex align-items-center justify-content-end">
|
||||||
<div class="month-year body-small d-flex align-items-center ps-3 small">
|
<span class="legend-circle me-1"></span> = Available
|
||||||
{{ month.monthLabel }} {{ month.yearNum.toString() }}
|
</div>
|
||||||
</div>
|
<div class="separator-line"></div>
|
||||||
<div
|
<div class="nav-back ps-3"><button></button></div>
|
||||||
v-if="calendarViewDirection === 'future'"
|
<div class="nav-forward pe-3"><button></button></div>
|
||||||
class="legend caption d-flex align-items-center justify-content-end">
|
<!-- TODO Accessibility: Do the days of the week need to be read? -->
|
||||||
<span class="legend-circle me-1"></span> = Available
|
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
|
||||||
</div>
|
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
|
||||||
<div class="nav-back ps-3"><button></button></div>
|
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
|
||||||
<div class="nav-forward pe-3"><button></button></div>
|
<div class="grid-item caption"><span class="sr-only">Wednesday</span>W</div>
|
||||||
<!-- Do the days of the weeek need to be read? -->
|
<div class="grid-item caption"><span class="sr-only">Thursday</span>T</div>
|
||||||
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
|
<div class="grid-item caption"><span class="sr-only">Friday</span>F</div>
|
||||||
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
|
<div class="grid-item caption"><span class="sr-only">Saturday</span>S</div>
|
||||||
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
|
<div
|
||||||
<div class="grid-item caption"><span class="sr-only">Wednesday</span>W</div>
|
v-for="date in month.dates"
|
||||||
<div class="grid-item caption"><span class="sr-only">Thursday</span>T</div>
|
:key="date.inputValue.dateString"
|
||||||
<div class="grid-item caption"><span class="sr-only">Friday</span>F</div>
|
:id="date.inputValue.dateString"
|
||||||
<div class="grid-item caption"><span class="sr-only">Saturday</span>S</div>
|
class="grid-item radio-wrapper"
|
||||||
<div
|
:class="[
|
||||||
v-for="date in month.dates"
|
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
|
||||||
:key="`${month.monthLabel}-${date.dateNum.toString()}-${months.length}`"
|
date.dayClasses,
|
||||||
class="grid-item radio-wrapper"
|
date.isSelectable ? 'selectable-day' : '',
|
||||||
:class="[
|
]">
|
||||||
date.dateNum === 1 ? 'first-day-' + month.startDateDayIndex : '',
|
<input
|
||||||
date.dayClasses,
|
:disabled="!date.isSelectable"
|
||||||
]">
|
type="radio"
|
||||||
<input
|
name="day-of-month"
|
||||||
:disabled="!isSelectableDate(date.inputValue)"
|
v-model="selectedDate"
|
||||||
type="radio"
|
:value="date.inputValue"
|
||||||
name="day-of-month"
|
:id="`${month.monthLabel}-${date.dateNum.toString()}`" />
|
||||||
v-model="selectedDate"
|
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
|
||||||
:value="date.inputValue"
|
<span>{{ date.dateNum.toString() }}</span>
|
||||||
:id="`${month.monthLabel}-${date.dateNum.toString()}`" />
|
</label>
|
||||||
<label :for="`${month.monthLabel}-${date.dateNum.toString()}`">
|
|
||||||
<span>{{ date.dateNum.toString() }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</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>
|
||||||
|
|
@ -60,39 +70,35 @@
|
||||||
|
|
||||||
<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",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "datePicker",
|
name: "datePicker",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
calendarData: [],
|
isLoading: true,
|
||||||
monthsBeforeToLoadOffset: 0,
|
months: null,
|
||||||
monthsAfterToLoadOffset: 1,
|
disableViewMoreDatesButton: false,
|
||||||
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
|
||||||
isInitialView: true,
|
today: null,
|
||||||
initialViewRowsToShow: 5,
|
hideSomeDaysForInitialView: null,
|
||||||
initialViewRowsTally: 0,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selectableDates: {
|
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,
|
||||||
},
|
},
|
||||||
todayOverrideDateString: {
|
todayOverrideDateString: {
|
||||||
// only used for unit tests to override today's date
|
// keep for use in unit tests to override today's date
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
@ -104,20 +110,29 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
todayDate() {
|
todayMonthIndex() {
|
||||||
return this.todayOverrideDateString
|
return this.today.getMonth() + 1;
|
||||||
? new Date(this.todayOverrideDateString)
|
|
||||||
: new Date();
|
|
||||||
},
|
},
|
||||||
months() {
|
todayYearNum() {
|
||||||
if (this.calendarData?.length < 1) {
|
return this.today.getFullYear();
|
||||||
this.setCalendarData();
|
},
|
||||||
}
|
todayDayIndex() {
|
||||||
return this.calendarData;
|
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.selectableDates === "past") return "past";
|
if (this.selectableDatesSetting === "past") return "past";
|
||||||
if (this.selectableDates === "custom") return "future";
|
if (this.selectableDatesSetting === "custom") return "future";
|
||||||
return "none";
|
return "none";
|
||||||
},
|
},
|
||||||
selectedDate: {
|
selectedDate: {
|
||||||
|
|
@ -130,260 +145,444 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goForward() {
|
getWeekStartDate(date) {
|
||||||
if (this.isInitialView) {
|
// Get the day of the week for date
|
||||||
this.isInitialView = false; // 1st click removes hidden styling
|
let dayOfWeek = date.getDay();
|
||||||
} else {
|
|
||||||
this.addMonthData(); // 2nd click adds 1 month data
|
// 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;
|
||||||
},
|
},
|
||||||
setCalendarData() {
|
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) {
|
||||||
|
// TODO - needs to be cleaned up & refactored
|
||||||
|
function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") {
|
||||||
|
const initY = wrapper.scrollTop;
|
||||||
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
|
const targetRect = target.getBoundingClientRect();
|
||||||
|
const targetY = targetRect.top - wrapperRect.top - BUFFER_OFFSET;
|
||||||
|
const timingFunc = TIMINGFUNC_MAP[timingName];
|
||||||
|
|
||||||
|
let start = null;
|
||||||
|
|
||||||
|
const step = (timestamp) => {
|
||||||
|
start = start || timestamp;
|
||||||
|
const progress = timestamp - start,
|
||||||
|
// Growing from 0 to 1
|
||||||
|
time = Math.min(1, (timestamp - start) / duration);
|
||||||
|
|
||||||
|
let percentageNew = timingFunc(time);
|
||||||
|
let distanceToGo = targetY;
|
||||||
|
let thisDistance = percentageNew * distanceToGo;
|
||||||
|
|
||||||
|
wrapper.scrollTo(0, initY + thisDistance);
|
||||||
|
|
||||||
|
if (percentageNew < 1) {
|
||||||
|
window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = this.$refs.datePickerFieldset;
|
||||||
|
const targetMonth = document.getElementById(elementId);
|
||||||
|
|
||||||
|
scrollTopSmooth(wrapper, targetMonth, 800, "ease-in-out");
|
||||||
|
},
|
||||||
|
|
||||||
|
async setCalendarData(config = {}) {
|
||||||
|
this.today = config.todayDate;
|
||||||
|
this.hideSomeDaysForInitialView = config.hideSomeDaysForInitialView;
|
||||||
|
let hideSecondMonth = config.hideSecondMonth;
|
||||||
|
const direction = config.calendarViewDirection;
|
||||||
|
|
||||||
|
const monthsAfterToLoadOffset = 12; // TO BE MADE "CONSTANTS"
|
||||||
|
const monthsBeforeToLoadOffset = 36; // TO BE MADE "CONSTANTS"
|
||||||
|
|
||||||
|
config.initialShopTimeSlotsResponse.forEach((selectableDate) => {
|
||||||
|
this.selectableDatesData.push(selectableDate);
|
||||||
|
});
|
||||||
|
|
||||||
// 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(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.calendarData = months;
|
this.months = months;
|
||||||
|
this.isLoading = false;
|
||||||
},
|
},
|
||||||
getMonthData(offset) {
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (typeof offset !== "undefined") {
|
if (calendarViewDirection === "future" && offset > 0) {
|
||||||
// offset only passed during initial setup with setCalendarData
|
while (monthIndex > 12) {
|
||||||
yearNum = this.todayDate.getFullYear();
|
monthIndex = monthIndex - 12;
|
||||||
monthIndex = this.todayDate.getMonth() + offset;
|
yearNum++;
|
||||||
|
|
||||||
if (direction === "future" && offset > 0) {
|
|
||||||
while (monthIndex > 11) {
|
|
||||||
monthIndex = monthIndex - 12;
|
|
||||||
yearNum++;
|
|
||||||
}
|
|
||||||
} else if (direction === "past" && offset < 0) {
|
|
||||||
while (monthIndex < 0) {
|
|
||||||
monthIndex = 12 + monthIndex;
|
|
||||||
yearNum--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else if (calendarViewDirection === "past" && offset < 0) {
|
||||||
// used only when adding an additional month
|
while (monthIndex < 1) {
|
||||||
|
monthIndex = 12 + monthIndex;
|
||||||
if (direction === "future") {
|
yearNum--;
|
||||||
// get last day of current calendar data
|
|
||||||
const lastMonthInCalendarData = this.calendarData[this.calendarData.length - 1];
|
|
||||||
|
|
||||||
if (lastMonthInCalendarData.monthIndex === 11) {
|
|
||||||
monthIndex = 0;
|
|
||||||
yearNum = lastMonthInCalendarData.yearNum + 1;
|
|
||||||
} else {
|
|
||||||
monthIndex = lastMonthInCalendarData.monthIndex + 1;
|
|
||||||
yearNum = lastMonthInCalendarData.yearNum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (direction === "past") {
|
|
||||||
// get last day of current calendar data
|
|
||||||
const firstMonthInCalendarData = this.calendarData[0];
|
|
||||||
|
|
||||||
if (firstMonthInCalendarData.monthIndex === 0) {
|
|
||||||
monthIndex = 11;
|
|
||||||
yearNum = firstMonthInCalendarData.yearNum - 1;
|
|
||||||
} else {
|
|
||||||
monthIndex = firstMonthInCalendarData.monthIndex - 1;
|
|
||||||
yearNum = firstMonthInCalendarData.yearNum;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const todayDayIndex = this.todayDate.getDay(); // get day of week index of today (0-6)
|
const monthEndDate = new Date(yearNum, monthIndex, 0); // BOTH
|
||||||
currentWeekStartDateNum =
|
|
||||||
todayDayIndex > todayDateNum ? 1 : todayDateNum - todayDayIndex; // FUTURE
|
|
||||||
const monthEndDate = new Date(yearNum, monthIndex + 1, 0); // BOTH
|
|
||||||
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 (typeof offset !== "undefined") {
|
if (Math.abs(offset) === 1 && hideSecondMonth) {
|
||||||
if (offset === 0 && direction === "future")
|
monthClass = monthClass + " month-hidden";
|
||||||
firstMonthDayTally = monthStartDateNum - startDateDayIndex; // FUTURE (starts low, counts up)
|
} else if (Math.abs(offset) > 1) {
|
||||||
if (offset === 0 && direction === "past")
|
monthClass = monthClass + " month-hidden";
|
||||||
firstMonthDayTally = currentWeekEndDateNum; // PAST (starts high, counts down)
|
}
|
||||||
|
if (
|
||||||
while (
|
Math.abs(offset) === options.monthsAfterToLoadOffset &&
|
||||||
direction === "future" &&
|
calendarViewDirection === "future"
|
||||||
offset === 0 &&
|
) {
|
||||||
firstMonthDayTally < monthEndDateNum
|
monthClass = monthClass + " last-available-month";
|
||||||
) {
|
}
|
||||||
// FUTURE
|
if (
|
||||||
firstMonthDayTally = firstMonthDayTally + 7;
|
Math.abs(offset) === options.monthsBeforeToLoadOffset &&
|
||||||
this.initialViewRowsTally++;
|
calendarViewDirection === "past"
|
||||||
}
|
) {
|
||||||
|
// TODO - re-check this logic if past direction
|
||||||
while (
|
monthClass = monthClass + " last-available-month";
|
||||||
direction === "past" &&
|
|
||||||
offset === 0 &&
|
|
||||||
firstMonthDayTally > monthStartDateNum
|
|
||||||
) {
|
|
||||||
// PAST
|
|
||||||
firstMonthDayTally = firstMonthDayTally - 7;
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(offset) === 1) {
|
|
||||||
if (this.initialViewRowsTally < this.initialViewRowsToShow) {
|
|
||||||
initialViewEndDate = 6 - startDateDayIndex + monthStartDateNum; // FUTURE
|
|
||||||
initialViewStartDate = monthEndDateNum - endDateDayIndex; // PAST
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
} else {
|
|
||||||
monthClass = monthClass + " month-hidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.initialViewRowsTally < this.initialViewRowsToShow) {
|
|
||||||
initialViewEndDate = initialViewEndDate + 7;
|
|
||||||
initialViewStartDate = initialViewStartDate - 7;
|
|
||||||
this.initialViewRowsTally++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectableDates === "custom") {
|
// populate dates array
|
||||||
const monthStart = {
|
|
||||||
year: yearNum,
|
|
||||||
month: monthIndex + 1,
|
|
||||||
date: offset === 0 ? todayDateNum : monthStartDateNum,
|
|
||||||
};
|
|
||||||
const monthEnd = {
|
|
||||||
year: monthEndDate.getFullYear(),
|
|
||||||
month: monthEndDate.getMonth() + 1,
|
|
||||||
date: monthEndDateNum,
|
|
||||||
};
|
|
||||||
// get available dates
|
|
||||||
this.updateSelectableDates(monthStart, monthEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate datesArray
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
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";
|
|
||||||
}
|
|
||||||
if (this.selectableDates === "custom" && this.isSelectableDate(thisDate)) {
|
|
||||||
dayClasses += " selectable-day";
|
|
||||||
}
|
}
|
||||||
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: 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
return monthToAdd;
|
return monthToAdd;
|
||||||
},
|
},
|
||||||
addMonthData() {
|
async showAnotherMonth() {
|
||||||
const monthToAdd = this.getMonthData();
|
this.isLoading = true;
|
||||||
if (this.calendarViewDirection === "future") {
|
let monthToShow;
|
||||||
this.calendarData.push(monthToAdd);
|
let monthStartDateNum = 0;
|
||||||
}
|
|
||||||
if (this.calendarViewDirection === "past") {
|
if (this.hideSomeDaysForInitialView) {
|
||||||
this.calendarData.unshift(monthToAdd);
|
monthToShow = this.months.find(
|
||||||
}
|
({ isMonthThatHidesSomeDaysForInitialView }) =>
|
||||||
},
|
isMonthThatHidesSomeDaysForInitialView
|
||||||
isSelectableDate(thisDate) {
|
|
||||||
const testForDate = (dateInArray) => {
|
|
||||||
return (
|
|
||||||
dateInArray.date === thisDate.date &&
|
|
||||||
dateInArray.month === thisDate.month &&
|
|
||||||
dateInArray.year === thisDate.year
|
|
||||||
);
|
);
|
||||||
};
|
// find the first day-hidden to become the next api call start date
|
||||||
const isSelectable =
|
monthStartDateNum =
|
||||||
this.selectableDatesData.findIndex(testForDate) > -1 ? true : false;
|
monthToShow.dates.find(({ dayClasses }) => dayClasses.includes("day-hidden"))
|
||||||
return isSelectable;
|
.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) {
|
||||||
|
// make new API call with this month's start and end dates
|
||||||
|
await this.updateSelectableDates(
|
||||||
|
monthToShow.dates[monthStartDateNum].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", "");
|
||||||
|
this.scrollToElement(monthToShow.monthString);
|
||||||
|
|
||||||
|
if (monthToShow.monthClass.includes("last-available-month"))
|
||||||
|
this.disableViewMoreDatesButton = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateSelectableDates(monthStart, monthEnd) {
|
async updateSelectableDates(monthStart, monthEnd) {
|
||||||
this.customSelectableDatesCallback(monthStart, monthEnd)?.forEach((newObj) => {
|
const moreSelectableDates = await this.customSelectableDatesCallback(
|
||||||
|
monthStart,
|
||||||
|
monthEnd
|
||||||
|
);
|
||||||
|
moreSelectableDates.forEach((selectableDate) => {
|
||||||
const index = this.selectableDatesData.findIndex(
|
const index = this.selectableDatesData.findIndex(
|
||||||
(obj) =>
|
(obj) => obj.dateString === selectableDate.dateString
|
||||||
obj.year === newObj.year &&
|
|
||||||
obj.month === newObj.month &&
|
|
||||||
obj.date === newObj.date
|
|
||||||
);
|
);
|
||||||
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;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
overflow-y: auto;
|
||||||
|
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;
|
margin: 0 auto 2rem auto;
|
||||||
max-width: 414px;
|
max-width: 414px;
|
||||||
|
transition: height ease 2s, opacity ease 2s;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, 1fr);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px;
|
padding: 0 0.75rem;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
.grid-item {
|
.grid-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 10%;
|
margin: 10px 3px;
|
||||||
|
|
||||||
&.first-day-,
|
&.first-day-,
|
||||||
&.first-day-0 {
|
&.first-day-0 {
|
||||||
|
|
@ -409,6 +608,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.separator-line {
|
||||||
|
grid-area: 2/1/2/8;
|
||||||
|
border-top: 1px solid $gray-500;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
.month-year {
|
.month-year {
|
||||||
grid-area: 1 / 1 / 2 / 5;
|
grid-area: 1 / 1 / 2 / 5;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
@ -436,6 +641,9 @@ export default {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
height: 1.35rem;
|
||||||
|
opacity: 1;
|
||||||
|
transition: height ease 250ms, opacity ease 250ms;
|
||||||
|
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
position: absolute; //override bootstrap
|
position: absolute; //override bootstrap
|
||||||
|
|
@ -495,9 +703,9 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
min-width: 40px;
|
min-width: 36px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
@ -567,19 +775,37 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.initial-view {
|
&.partial-month-initial-view {
|
||||||
&.month-hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.day-hidden {
|
.day-hidden {
|
||||||
display: none;
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
max-height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.month-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&.last-available-month:not(&.month-hidden) {
|
||||||
|
margin-bottom: 14rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#bottom-spacer {
|
||||||
|
height: 20rem;
|
||||||
|
background: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-link {
|
.btn-link {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
|
box-shadow: none !important; // TODO: FIX THIS
|
||||||
|
position: absolute;
|
||||||
|
top: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.past {
|
.past {
|
||||||
.calendar-grid-container {
|
.calendar-grid-container {
|
||||||
.month-year {
|
.month-year {
|
||||||
|
|
@ -637,4 +863,16 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.btn-link {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
height: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
text-underline-offset: 4px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
24
src/digital-components/date-picker/mixins/constants.js
Normal file
24
src/digital-components/date-picker/mixins/constants.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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",
|
||||||
|
];
|
||||||
|
|
||||||
|
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR };
|
||||||
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 };
|
||||||
|
|
@ -60,17 +60,24 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
callMockHttpClient({ method, endpoint }) {
|
callMockHttpClient({ method, endpoint, payload }) {
|
||||||
// For Mock use only!
|
// For Mock use only!
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios({
|
axios({
|
||||||
method: method,
|
method: method,
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
responseType: {},
|
responseType: {},
|
||||||
|
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,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container-grouped-styles">
|
<div class="page-container-grouped-styles">
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||||
<div class="select-car">
|
<div class="">
|
||||||
<div class="select-car-form rounded text-center">
|
<div class="rounded text-center">
|
||||||
<div class="fade-on-route-transition">
|
<div class="fade-on-route-transition">
|
||||||
<date-picker
|
<date-picker
|
||||||
selectableDates="custom"
|
selectableDates="custom"
|
||||||
|
|
@ -50,6 +50,7 @@ export default {
|
||||||
{ year: 2023, month: 3, date: 5 },
|
{ year: 2023, month: 3, date: 5 },
|
||||||
{ year: 2023, month: 3, date: 22 },
|
{ year: 2023, month: 3, date: 22 },
|
||||||
{ year: 2023, month: 4, date: 13 },
|
{ year: 2023, month: 4, date: 13 },
|
||||||
|
{ year: 2023, month: 4, date: 7 },
|
||||||
{ year: 2023, month: 4, date: 14 },
|
{ year: 2023, month: 4, date: 14 },
|
||||||
{ year: 2023, month: 4, date: 26 },
|
{ year: 2023, month: 4, date: 26 },
|
||||||
{ year: 2023, month: 5, date: 21 },
|
{ year: 2023, month: 5, date: 21 },
|
||||||
|
|
@ -81,3 +82,9 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
// .page-container-grouped-styles {
|
||||||
|
// padding: 0 .5rem !important;
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<date-picker
|
<date-picker
|
||||||
selectableDates="custom"
|
selectableDatesSetting="custom"
|
||||||
|
ref="datePicker"
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
:customSelectableDatesCallback="getAvailableDates" />
|
:customSelectableDatesCallback="getAvailableDatesMethod" />
|
||||||
<funnel-footer
|
<funnel-footer
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="funnelFooter"
|
ref="funnelFooter"
|
||||||
|
|
@ -55,6 +56,8 @@ import datePicker from "@/digital-components/date-picker/date-picker";
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
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 { getAlertReasons } from "@/layouts/schedule/helpers/schedule-helper";
|
||||||
|
|
@ -70,29 +73,67 @@ import store from "@/store";
|
||||||
|
|
||||||
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
defineRule("date-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() {
|
||||||
return {
|
return {
|
||||||
selectedDate: null,
|
selectedDate: null,
|
||||||
|
selectableDatesData: [],
|
||||||
weatherAlerts: [],
|
weatherAlerts: [],
|
||||||
mockSelectableDatesData: [
|
|
||||||
{ year: 2023, month: 4, date: 13 },
|
|
||||||
{ year: 2023, month: 4, date: 14 },
|
|
||||||
{ year: 2023, month: 4, date: 26 },
|
|
||||||
{ year: 2023, month: 5, date: 4 },
|
|
||||||
{ year: 2023, month: 5, date: 5 },
|
|
||||||
{ year: 2023, month: 5, date: 14 },
|
|
||||||
{ year: 2023, month: 5, date: 21 },
|
|
||||||
{ year: 2023, month: 5, date: 23 },
|
|
||||||
{ year: 2023, month: 5, date: 25 },
|
|
||||||
{ year: 2023, month: 6, date: 11 },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
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,
|
||||||
|
|
||||||
|
/* 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);
|
const alertReasonsPromise = getAlertReasons(store.getters.order.serviceLocation.zipCodeCtu);
|
||||||
|
|
||||||
|
|
@ -106,6 +147,10 @@ export default {
|
||||||
resultKey: "alertReasons",
|
resultKey: "alertReasons",
|
||||||
promise: alertReasonsPromise,
|
promise: alertReasonsPromise,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resultKey: "datePickerInitialData",
|
||||||
|
promise: datePickerInitialDataPromise,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
@ -114,6 +159,7 @@ export default {
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
vm.setData(resultMap.alertReasons);
|
vm.setData(resultMap.alertReasons);
|
||||||
|
vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -137,6 +183,22 @@ 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 getAvailableDatesMethod(startDate, endDate) {
|
||||||
|
/* TODO - DO WE NEED TO KEEP AN AGGREGATE OF ALL DATES RETURNED
|
||||||
|
FOR TIMESLOTS in this.selectableDatesData? IS selectableDatesData EVEN NEEDED?
|
||||||
|
|
||||||
|
// // ADD API CALL RESULTS TO EXISTING DATE DATA
|
||||||
|
// this.selectableDatesData = this.selectableDatesData.concat(
|
||||||
|
// this.convertApiResponse(newShopTimeSlots.days)
|
||||||
|
// );
|
||||||
|
// // console.log("this.selectableDatesData is now: ", this.selectableDatesData);
|
||||||
|
|
||||||
|
// // RETURN AGGREGATE DATE DATA
|
||||||
|
// return this.selectableDatesData;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return await getAvailableDates(startDate, endDate);
|
||||||
|
},
|
||||||
setData(alertReasonsData) {
|
setData(alertReasonsData) {
|
||||||
if (alertReasonsData) {
|
if (alertReasonsData) {
|
||||||
this.convertReasonsToCmsAlerts(alertReasonsData);
|
this.convertReasonsToCmsAlerts(alertReasonsData);
|
||||||
|
|
@ -165,9 +227,6 @@ export default {
|
||||||
console.log("error fetching alert reasons..");
|
console.log("error fetching alert reasons..");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getAvailableDates(startDate, endDate) {
|
|
||||||
return this.mockSelectableDatesData;
|
|
||||||
},
|
|
||||||
getServiceZipCtuCodeFromStore() {
|
getServiceZipCtuCodeFromStore() {
|
||||||
return store.getters.order.serviceLocation.zipCodeCtu;
|
return store.getters.order.serviceLocation.zipCodeCtu;
|
||||||
},
|
},
|
||||||
|
|
@ -190,6 +249,13 @@ export default {
|
||||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
selectedDate(date) {
|
||||||
|
/* EXAMPLE RETURNED date OBJECT:
|
||||||
|
{ "year": 2023, "month": 5, "date": 25, "dateString": "2023-05-25" }
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
alert,
|
alert,
|
||||||
funnelHeader,
|
funnelHeader,
|
||||||
|
|
@ -202,5 +268,3 @@ export default {
|
||||||
mounted() {},
|
mounted() {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss"></style>
|
|
||||||
|
|
|
||||||
|
|
@ -1116,6 +1116,41 @@ export const actions = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getShopTimeSlots(
|
||||||
|
context,
|
||||||
|
{ startDate = "2023-01-01", endDate = "2023-05-01", shopAppointmentType = "" }
|
||||||
|
) {
|
||||||
|
const order = context.state.order;
|
||||||
|
const mockArray = [];
|
||||||
|
|
||||||
|
var payload = {
|
||||||
|
providerNumber: order.providerNumber,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
shopAppointmentType: shopAppointmentType,
|
||||||
|
applicationName: applicationConfig.APPLICATION_NAME,
|
||||||
|
parentAccountNumber: context.getters.payment.parentAccountNumber,
|
||||||
|
carId: context.getters.vehicle.carId,
|
||||||
|
partNumbers: mockArray, // TODO: WHERE DO I GET THIS?
|
||||||
|
eon: order.eon,
|
||||||
|
provisionalReasons: mockArray, // TODO: WHERE DO I GET THIS?
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: REMOVE MOCK CALL & USE REAL CALL BELOW
|
||||||
|
return globalMethods.callMockHttpClient({
|
||||||
|
method: endpoints.GetShopTimeSlots.method,
|
||||||
|
endpoint: endpoints.GetShopTimeSlots.mockUrl,
|
||||||
|
payload: payload,
|
||||||
|
});
|
||||||
|
// TODO: RESTORE THIS
|
||||||
|
// return globalMethods.callHttpClient({
|
||||||
|
// method: endpoints.GetShopTimeSlots.method,
|
||||||
|
// endpoint: endpoints.GetShopTimeSlots.url,
|
||||||
|
// payload: payload,
|
||||||
|
// logApiCall: false,
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
// Session API Actions
|
// Session API Actions
|
||||||
saveSession(context) {
|
saveSession(context) {
|
||||||
const vehicle = context.getters.vehicle;
|
const vehicle = context.getters.vehicle;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue