CSR-886: new refactoring changes to use beforeRouteEnter on Schedule page to do heavy lifting

This commit is contained in:
Adam Caouette 2023-05-17 19:19:48 -04:00
parent 53166fc0ea
commit d7f406b2ba
7 changed files with 477 additions and 288 deletions

View file

@ -1,16 +0,0 @@
const monthsOfYear = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
export { monthsOfYear };

View file

@ -1 +0,0 @@
test.todo("some test to be written in the future");

View file

@ -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,55 @@
<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, months: null,
disableViewMoreDatesButton: false,
selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based) selectableDatesData: [], // NOTE: uses monthNum (1-based), NOT monthIndex (0-based)
hasPartialMonthInitialView: true, today: null,
hasPartialMonthInitialViewNEW: true, hideSomeDaysForInitialView: null,
// TO BE MADE "CONSTANTS"
monthsBeforeToLoadOffset: 36,
monthsAfterToLoadOffset: 12,
initialViewRowsToShow: 5, initialViewRowsToShow: 5,
// TBD OR REMOVED
hasPartialMonthInitialView: true,
hasSplitInitialView: true,
initialViewIncludesFullSecondMonth: false,
initialViewRowsTally: 0, initialViewRowsTally: 0,
isAddingNewMonth: false, isAddingNewMonth: false,
currentScrollTop: 0, currentScrollTop: 0,
months: null,
lastApiCallStartDate: null, lastApiCallStartDate: null,
lastApiCallEndDate: null, lastApiCallEndDate: null,
newToday: {
dateObj: null,
dateNum: null,
dayIndex: null,
yearNum: null,
monthIndex: null,
monthEndDateNum: null,
},
initialViewStartDateNum: null,
initialViewEndDateNum: null,
daysLeftOver: 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 +136,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,12 +166,134 @@ 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 todayDayIndex = todayDate.getDay();
let todayDateNum = todayDate.getDate();
let currentMonthStart = new Date(todayYearNum, todayMonthIndex - 1, 1);
let currentMonthEnd = new Date(todayYearNum, todayMonthIndex, 0);
let currentWeekStartDate = this.getWeekStartDate(todayDate);
let currentWeekEndDateNum = this.getWeekEndDate(todayDate);
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 secondMonthEnd;
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) {
function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") { function scrollTopSmooth(wrapper, target, duration = 300, timingName = "linear") {
const initY = wrapper.scrollTop; const initY = wrapper.scrollTop;
@ -190,280 +329,259 @@ 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 = this.monthsAfterToLoadOffset; i >= this.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.hasSplitInitialView = false; // if was partial view, this removes hidden styling on days
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 +590,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 +676,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 +814,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 {

View 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 };

View 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 };

View file

@ -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);

View file

@ -31,9 +31,9 @@
<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" -->
<funnel-footer <funnel-footer
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
ref="funnelFooter" ref="funnelFooter"
@ -73,6 +73,33 @@ 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() {
@ -85,6 +112,28 @@ 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,
/* 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);
@ -98,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);
@ -106,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: {
@ -129,40 +183,21 @@ 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) {
// USING DATES PASSED, MAKE AN API CALL /* TODO - DO WE NEED TO KEEP AN AGGREGATE OF ALL DATES RETURNED
const newShopTimeSlotsResponse = await baseMixin.methods.dispatchStoreAction( FOR TIMESLOTS in this.selectableDatesData? IS selectableDatesData EVEN NEEDED?
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)
); // );
console.log("this.selectableDatesData is now: ", this.selectableDatesData); // // console.log("this.selectableDatesData is now: ", this.selectableDatesData);
// RETURN AGGREGATE DATE DATA // // RETURN AGGREGATE DATE DATA
return this.selectableDatesData; // return this.selectableDatesData;
}, */
convertApiResponse(responseData) {
// DATA CONVERSION return await getAvailableDates(startDate, endDate);
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;
}, },
setData(alertReasonsData) { setData(alertReasonsData) {
if (alertReasonsData) { if (alertReasonsData) {
@ -192,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;
}, },
@ -236,5 +268,3 @@ export default {
mounted() {}, mounted() {},
}; };
</script> </script>
<style lang="scss"></style>