diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 162d90bf..8429f3ad 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -15,6 +15,18 @@ const endpoints = Object.freeze({ url: '/location/api/v1/location/alert-reasons', method: 'GET' }, + GetShopTimeSlots: { + url: '/schedule/api/v1/schedule/shop-time-slots', + method: 'POST' + }, + GetMobileTimeSlots: { + url: '/schedule/api/v1/schedule/mobile-time-slots', + method: 'POST' + }, + GetMobilePremiumFee: { + url: '/parts/api/v1/parts/mobile-premium-fee', + method: 'GET' + }, GetVehicleYears: { url: '/vehicle/api/v1/vehicle/years', method: 'GET' diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index a728f1d4..35232c04 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -11,4 +11,14 @@ const RouteCodeFlags = { OVERNIGHT_DROP_OFF: 'OVERNIGHT DROP OFF' }; -export { AppointmentTypeStrings, PREMIUM_TIME_SLOT_ID_FLAG, PREMIUM_FEE_PART_TYPE, RouteCodeFlags }; +const GET_SHOP_TIME_SLOTS = 'getShopTimeSlots'; +const GET_MOBILE_TIME_SLOTS = 'getMobileTimeSlots'; + +export { + AppointmentTypeStrings, + PREMIUM_TIME_SLOT_ID_FLAG, + PREMIUM_FEE_PART_TYPE, + RouteCodeFlags, + GET_SHOP_TIME_SLOTS, + GET_MOBILE_TIME_SLOTS +}; diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue new file mode 100644 index 00000000..398b92b3 --- /dev/null +++ b/src/digital-components/date-picker/date-picker.vue @@ -0,0 +1,1064 @@ + + + + + diff --git a/src/digital-components/date-picker/mixins/constants.js b/src/digital-components/date-picker/mixins/constants.js new file mode 100644 index 00000000..70d4d383 --- /dev/null +++ b/src/digital-components/date-picker/mixins/constants.js @@ -0,0 +1,26 @@ +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' +]; + +const DAYS_OF_WEEK = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + +export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK }; diff --git a/src/digital-components/date-picker/mixins/helpers.js b/src/digital-components/date-picker/mixins/helpers.js new file mode 100644 index 00000000..77c61fc9 --- /dev/null +++ b/src/digital-components/date-picker/mixins/helpers.js @@ -0,0 +1,10 @@ +const selectableDaysOptions = Object.freeze({ + CUSTOM: 'custom', + PAST: 'past' +}); + +const requiredParameter = () => { + throw new Error('parameter is required'); +}; + +export { selectableDaysOptions, requiredParameter }; diff --git a/src/helpers/date-helper.js b/src/helpers/date-helper.js new file mode 100644 index 00000000..ed943174 --- /dev/null +++ b/src/helpers/date-helper.js @@ -0,0 +1,12 @@ +const getDateDifferenceInDays = (startDate, endDate) => { + const date1 = new Date(endDate); + date1.setHours(0, 0, 0, 0); + const date2 = new Date(startDate); + date2.setHours(0, 0, 0, 0); + // To calculate the time difference of two dates + const DifferenceInTime = date1.getTime() - date2.getTime(); + // To calculate the no. of days between two dates + return DifferenceInTime / (1000 * 3600 * 24); +}; + +export default getDateDifferenceInDays; diff --git a/src/helpers/service-location-helper.js b/src/helpers/service-location-helper.js index a8ac5503..63f4c711 100644 --- a/src/helpers/service-location-helper.js +++ b/src/helpers/service-location-helper.js @@ -46,15 +46,7 @@ export async function getAvailabilityRating( providerNumber ) { // For a given shop provider number and date range, get the appointment time slots available - const shopTimeSlots = await useMainStore().getShopTimeSlots( - { - providerNumber, - startDate, - endDate, - shopAppointmentType - }, - false - ); + const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber); const numberOfDaysToEvaluate = 2; const isGoodAvailability = diff --git a/src/layouts/schedule-page/helpers/schedule-helper.js b/src/layouts/schedule-page/helpers/schedule-helper.js index 7347bfca..6f41d306 100644 --- a/src/layouts/schedule-page/helpers/schedule-helper.js +++ b/src/layouts/schedule-page/helpers/schedule-helper.js @@ -27,4 +27,68 @@ export async function mockGetAlertReasons(ctu) { return Promise.resolve(retList); } +export function calcDaysBetweenDates(dateString1, dateString2) { + const date1 = new Date(dateString1); + const date2 = new Date(dateString2); + const timeDifference = Math.abs(date2 - date1); // Calculate the time difference in milliseconds + return Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); // Convert milliseconds to days +} + +export function convertDateToDateString(date) { + // returns YYYY-MM-DD format + if (date instanceof Date !== true) return null; + return ( + `${date.getFullYear() + }-${ + (`0${date.getMonth() + 1}`).slice(-2) + }-${ + (`0${date.getDate()}`).slice(-2)}` + ); +} + +export function convertDateStringToDate(dateString) { + // dateString must be YYYY-MM-DD format + if (typeof dateString !== 'string') return null; + const dateParts = dateString.split('-'); + return new Date(dateParts[0], parseInt(dateParts[1], 10) - 1, dateParts[2]); +} + +export function sumDateString(dateString, daysToAdd) { + // dateString must be YYYY-MM-DD format + if (typeof dateString !== 'string') return null; + const date = convertDateStringToDate(dateString); + date.setDate(date.getDate() + daysToAdd); + return convertDateToDateString(date); +} + +export function militaryToTwelveHourTime(timeString) { + // Expected input: "HH:MM" + if (typeof timeString !== 'string') return null; + let hours = parseInt(timeString.split(':')[0], 10); + const minutes = timeString.split(':')[1]; + const meridianNotation = hours > 11 ? 'PM' : 'AM'; + + if (hours > 12) { + hours -= 12; + } + + return `${hours}:${minutes} ${meridianNotation}`; +} + +export function getDisplayTextForDurationLength(durationMinimum, durationMaximum) { + const isLongAppointment = durationMaximum >= 120; + const isDurationRange = durationMinimum !== durationMaximum; + + const adjustedMinimum = isLongAppointment ? durationMinimum / 60 : durationMinimum; + const adjustedMaximum = isLongAppointment ? durationMaximum / 60 : durationMaximum; + + const durationText = isDurationRange + ? `${adjustedMinimum} - ${adjustedMaximum}` + : adjustedMinimum; + + const unitText = isLongAppointment ? 'hours' : 'minutes'; + + return `${durationText} ${unitText}`; +} + export default getAlertReasons; diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index 8ba01a3e..624acf75 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -15,14 +15,23 @@ cmsWidgetName="ChangeShopLink" justifyText="center" class="mb-3 text-link-small" - marginTopSizeOverride="1" /> + :marginTopSizeOverride="1" />
+ { + if (value?.timeSlot?.routeCode == null) { + return errorMessages.DATE_REQUIRED; + } + return true; +}); + +// Define constants +const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work) + +const getAvailableDates = async ( + startDateString, + endDateString, + appointmentType, + providerNumber +) => { + const apiEndDateLimit = sumDateString(startDateString, TIME_SLOTS_CALL_DAYS_LIMIT); + const difference = calcDaysBetweenDates(startDateString, endDateString); + const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT); + const storeActionConfigs = []; + const timeSlotsData = {}; + timeSlotsData.days = []; + let apiStartDate = startDateString; + let apiEndDate = endDateString; + + for (let i = 1; i <= apiCallsCount; i++) { + let storeActionConfig; + + if (i > 1) { + apiStartDate = sumDateString(apiEndDate, 1); + apiEndDate = sumDateString(apiStartDate, TIME_SLOTS_CALL_DAYS_LIMIT); + + if (i === apiCallsCount) { + apiEndDate = endDateString; + } + } else if (apiEndDate > apiEndDateLimit) { + apiEndDate = apiEndDateLimit; + } + + if (appointmentType === AppointmentTypeStrings.MOBILE) { + storeActionConfig = { + storeAction: GET_MOBILE_TIME_SLOTS, + payload: { + startDate: apiStartDate, + endDate: apiEndDate + } + }; + } else { + storeActionConfig = { + storeAction: GET_SHOP_TIME_SLOTS, + payload: { + startDate: apiStartDate, + endDate: apiEndDate, + shopAppointmentType: appointmentType, + providerNumber + } + }; + } + if (apiStartDate < apiEndDate) storeActionConfigs.push(storeActionConfig); + } + + const timeSlotsResponsesData = { + days: [] + }; + + function compareDayStrings(a, b) { + if (a.date < b.date) return -1; + if (a.date > b.date) return 1; + return 0; + } + + const makeParallelCalls = async () => { + await Promise.all(storeActionConfigs.map(async (storeAction) => { + let timeSlotsResponse = null; + if (storeAction.storeAction === GET_SHOP_TIME_SLOTS) { + timeSlotsResponse = await useMainStore().getShopTimeSlots( + storeAction.payload.startDate, + storeAction.payload.endDate, + storeAction.payload.shopAppointmentType, + storeAction.payload.providerNumber + ); + } else { + timeSlotsResponse = await useMainStore().getMobileTimeSlots(storeAction.payload.startDate, storeAction.payload.endDate); + } + + timeSlotsResponsesData.estimatedServiceMinutesMinimum = timeSlotsResponse.data.estimatedServiceMinutesMinimum; + timeSlotsResponsesData.estimatedServiceMinutesMaximum = timeSlotsResponse.data.estimatedServiceMinutesMaximum; + timeSlotsResponsesData.days = [ + ...timeSlotsResponsesData.days, + ...timeSlotsResponse.data.days + ]; + })); + }; + + return makeParallelCalls().then(() => { + // sort days chronologically + timeSlotsResponsesData.days.sort(compareDayStrings); + return timeSlotsResponsesData; + }); +}; + export default { name: 'schedule-page', components: { siteHeader, siteSubHeader, locationAlerts, + datePicker, siteFooter, textBlock, // eslint-disable-next-line vue/no-reserved-component-names @@ -62,8 +188,20 @@ export default { mixins: [BaseFormMixin], async beforeRouteEnter(to, from, next) { // Call APIs + let preSelectedDate = await useMainStore().order.schedule.date; + if (!preSelectedDate || preSelectedDate.startTime === null) { + preSelectedDate = null; + } + const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); + const datePickerInitialDataPromise = await datePicker.methods.loadInitialData({ + selectableDatesSetting: 'custom', + initialViewRowsToShow: 5, + customSelectableDatesCallback: getAvailableDates, + preSelectedDate + }); + const alertReasonsPromise = locationAlerts.methods.loadInitialData( useMainStore().order.serviceLocation.zipCodeCtu, useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu @@ -78,6 +216,10 @@ export default { { resultKey: 'alertReasons', promise: alertReasonsPromise + }, + { + resultKey: 'datePickerInitialData', + promise: datePickerInitialDataPromise } ]; @@ -85,7 +227,10 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); next((vm) => { vm.setCmsContent(resultMap.cmsContent); + vm.$refs.datePicker.initializeComponent(resultMap.datePickerInitialData); vm.$refs.locationAlerts.initializeComponent(resultMap.alertReasons); + vm.setData(resultMap.datePickerInitialData.initialShopTimeSlotsResponse); + vm.updateFooterButtonText(vm.selectedTimeSlotInfo); }); }, setup() { @@ -100,8 +245,38 @@ export default { return this.getCmsContent('ChangeShopLink', 'Text'); }, ChangeShopLink() { - // Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText); + }, + appointmentType() { + return useMainStore().order.serviceLocation.appointmentType; + }, + timeSlotsForSelectedDate() { + if (!this.selectedDate) { + return null; + } + + return this.selectableDatesData.days?.find((selectableDate) => selectableDate.date === this.selectedDate); + } + }, + watch: { + selectedDate(newValue, oldValue) { + // Clear time slot selection if date selected changes + if (newValue !== oldValue) { + this.selectedTimeSlotInfo = { + timeSlot: { + date: null, + routeCode: null, + startTime: null, + endTime: null, + jobMaxMinutes: null, + jobMinMinutes: null + }, + isPremiumAppointment: null + }; + } + }, + selectedTimeSlotInfo(newValue) { + this.updateFooterButtonText(newValue); } }, methods: { @@ -121,20 +296,146 @@ export default { && useMainStore().order.lineItems.glassParts.length > 0); return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo; }, + setData(initialShopTimeSlotsResponse) { + this.selectableDatesData = initialShopTimeSlotsResponse; + }, + async getAvailableDatesMethod(startDate, endDate) { + const newShopTimeSlots = await getAvailableDates( + startDate, + endDate, + this.appointmentType, + this.mainStore.order.serviceLocation.provider.providerNumber + ); + // ADD API CALL RESULTS TO EXISTING DATE DATA + this.selectableDatesData.days = this.selectableDatesData.days.concat(newShopTimeSlots.days); + return newShopTimeSlots; + }, + getAvailableDates, + getServiceZipCtuCodeFromStore() { + return this.mainStore.order.serviceLocation.zipCodeCtu; + }, + openInshopTimeSlotsModal() { + this.$refs.timeSlotModalQuestion.openModal(); + }, + getSelectedDate() { + return this.mainStore.order.schedule.date; + }, + getSelectedTimeSlotInfo() { + const supportingItems = this.getSupportingItems(); + const isPremiumAppointment = + !!supportingItems.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE) + .length > 0; + + const selectedTimeSlotInfo = { + timeSlot: this.mainStore.order.schedule, + isPremiumAppointment + }; + + return selectedTimeSlotInfo; + }, + getSupportingItems() { + return this.mainStore.lineItems.supportingItems; + }, + timeSlotModalClosed() { + // Clear the selectedDate if no timeSlot has been selected + if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) { + this.selectedDate = null; + } + }, + updateFooterButtonText(timeSlotInfo) { + let navbarButtonText; + if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) { + navbarButtonText = 'Continue'; + } else { + navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(timeSlotInfo.timeSlot.date)}`; + if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) { + navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(timeSlotInfo.timeSlot.startTime)}`; + } else if ( + this.appointmentType === AppointmentTypeStrings.MOBILE + && !timeSlotInfo.isPremiumAppointment + ) { + navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime( + timeSlotInfo.timeSlot.startTime, + true + )} - ${this.getDisplayTextForMilitaryTime( + timeSlotInfo.timeSlot.endTime, + true + )}`; + } + } + this.$refs.navbar.updateButtonText(navbarButtonText); + }, + convertSelectedDateToShortMonthAndDay(selectedDate) { + // This conversion ensures we don't get get GMT induced date changes + const dateObject = convertDateStringToDate(selectedDate); + return dateObject.toLocaleDateString('en-us', { month: 'short', day: 'numeric' }); + }, + getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) { + // Expected input: "HH:MM" + let hours = parseInt(militaryTimeInput.split(':')[0], 10); + const minutes = militaryTimeInput.split(':')[1]; + const meridianNotation = hours > 11 ? 'PM' : 'AM'; + + if (hours > 12) { + hours -= 12; + } + + if (shouldTrimMinutesIfEmpty && minutes === '00') { + return `${hours} ${meridianNotation}`; + } + return `${hours}:${minutes} ${meridianNotation}`; + }, + updateSupportingItems() { + const supportingItems = this.getSupportingItems(); + + // if we have a premium fee(early bird), then save/update supporting items + if ( + this.appointmentType === AppointmentTypeStrings.MOBILE + && this.selectedTimeSlotInfo?.isPremiumAppointment + ) { + const earlyBirdIndex = supportingItems.findIndex((item) => item.partType === PREMIUM_FEE_PART_TYPE); + + if (earlyBirdIndex >= 0) { + supportingItems[earlyBirdIndex].laborAmount = + this.mobilePremiumAppointmentFee.laborAmount; + supportingItems[earlyBirdIndex].selingPrice = + this.mobilePremiumAppointmentFee.selingPrice; + supportingItems[earlyBirdIndex].kitPrice = + this.mobilePremiumAppointmentFee.kitPrice; + } else { + supportingItems.push(this.mobilePremiumAppointmentFee); + } + + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + supportingItems, + false + ); + } else { + // if it's not a mobile and/or premium early bird, then make sure we remove any that may have been added + const removeEarlyBirdIndex = supportingItems.findIndex((item) => item.partType === PREMIUM_FEE_PART_TYPE); + + if (removeEarlyBirdIndex >= 0) { + supportingItems.splice(removeEarlyBirdIndex, 1); + this.dispatchStoreAction( + this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING, + supportingItems, + false + ); + } + } + }, backButtonAction() { this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route); }, - forwardButtonAction() { // validate and save here this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route); - }, - - navigateForward() { } } }; + diff --git a/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue b/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue new file mode 100644 index 00000000..4fe1774a --- /dev/null +++ b/src/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue @@ -0,0 +1,520 @@ + + + + + diff --git a/src/store/index.js b/src/store/index.js index 20b48012..1e8e95de 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -12,9 +12,23 @@ import issPageValues from '@/router/router-constants/issPage-values'; import damageLocationsSelected from '@/constants/damage-locations-selected'; import coverageStatuses from '@/constants/coverage-statuses'; import { PREMIUM_FEE_PART_TYPE } from '@/constants/schedule-constants'; +import getDateDifferenceInDays from '@/helpers/date-helper'; const storeId = 'main'; +function getTimeSlotsAdditionalEventData( + provisionalTriggers, + zipCode, + firstAvailableAppointmentDateString, + shopAppointmentType +) { + let numberOfDays = null; + if (firstAvailableAppointmentDateString) numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString); + + if (shopAppointmentType) return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(',')}`; + return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(',')}`; +} + const getDefaultState = () => ({ order: { vehicle: { @@ -600,6 +614,65 @@ export const useMainStore = defineStore({ }); }, + getMobileTimeSlots(startDate, endDate) { + const { order } = this; + const { vehicle } = this.order; + let lineItems = [ + ...(order.lineItems.supportingItems ?? []), + ...(order.lineItems.vaps ?? []), + ...getFlattenedArrayOfLineItemsWithChildParts(order.lineItems.glassParts) + ]; + lineItems = lineItems.map((lineItem) => ({ + partNumber: lineItem.partNumber, + partType: lineItem.partType + })); + const glassPieces = order.damage.glassToReplace + ? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace) + : []; + const payload = { + startDate, + endDate, + applicationName: applicationConfig.APPLICATION_NAME, + parentAccountNumber: this.payment.parentAccountNumber, + carId: vehicle.carId, + lineItems, + glassPieces, + eon: order.eon, + coverage: { + status: '', + deductible: 0, + additionalAuthFlag: '' + }, + partSelection: { + hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length, + hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length, + hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length, + hasManuallySelectedParts: + !!this.applicationUser.pageData['vehicle-parts']?.partsOrQuestions + .length + }, + vehicle: { + year: vehicle.year, + make: vehicle.make, + model: vehicle.model, + style: vehicle.style, + vin: vehicle.vin ?? '' + }, + zipCode: order.serviceLocation.zipCode + }; + return globalMethods.callHttpClient({ + method: endpoints.GetMobileTimeSlots.method, + endpoint: endpoints.GetMobileTimeSlots.url, + payload, + logApiCall: true, + additionalSuccessEventDataHandler: (response) => + getTimeSlotsAdditionalEventData( + response.data.provisionalTriggers, + order.serviceLocation.zipCode, + response.data.days?.[0]?.date + ) + }); + }, getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber) { const { order } = this; const { vehicle } = this.order; @@ -622,7 +695,7 @@ export const useMainStore = defineStore({ endDate, shopAppointmentType, applicationName: applicationConfig.APPLICATION_NAME, - parentAccountNumber: this.payment.parentAccountNumber, + parentAccountNumber: 167132, // this.payment.parentAccountNumber, carId: vehicle.carId, lineItems, glassPieces, @@ -636,9 +709,7 @@ export const useMainStore = defineStore({ hasAnsweredPartQuestions: !!order.damage.partQuestionAnswers?.length, hasAnsweredMoldingQuestions: !!order.damage.moldingQuestionAnswers?.length, hasAnsweredCapabilityQuestions: !!order.damage.capabilityQuestionAnswers?.length, - hasManuallySelectedParts: - !!this.applicationUser.pageData['vehicle-parts']?.partsOrQuestions - .length + hasManuallySelectedParts: !!this.applicationUser.pageData['vehicle-parts']?.partsOrQuestions.length }, vehicle: { year: vehicle.year, diff --git a/src/styles/ux-variables-svg-strings.scss b/src/styles/ux-variables-svg-strings.scss index 4261e7d0..03a80935 100644 --- a/src/styles/ux-variables-svg-strings.scss +++ b/src/styles/ux-variables-svg-strings.scss @@ -1 +1,3 @@ +$svg-date-picker-nav-back-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.331685 6.00121C0.330445 5.82446 0.399256 5.65442 0.523053 5.52832L5.84499 0.198256C5.97188 0.0713149 6.14397 -8.63821e-08 6.32341 -6.72174e-08C6.50285 -4.80527e-08 6.67495 0.071315 6.80183 0.198256C6.92872 0.325198 7 0.497368 7 0.67689C7 0.856412 6.92872 1.02858 6.80183 1.15552L1.94874 6.00121L6.80183 10.8526C6.92745 10.9796 6.99751 11.1512 6.99662 11.3299C6.99572 11.5085 6.92393 11.6794 6.79705 11.8051C6.67016 11.9308 6.49857 12.0009 6.32003 12C6.14148 11.9991 5.97061 11.9273 5.84499 11.8003L0.526881 6.47601C0.401568 6.34983 0.331375 6.17908 0.331685 6.00121Z' fill='%231574A1'/%3E%3C/svg%3E%0A"; +$svg-date-picker-forward-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66831 5.99879C6.66955 6.17554 6.60074 6.34558 6.47695 6.47168L1.15501 11.8017C1.02812 11.9287 0.85603 12 0.676587 12C0.497145 12 0.325053 11.9287 0.198168 11.8017C0.0712831 11.6748 7.268e-09 11.5026 8.07183e-09 11.3231C8.87567e-09 11.1436 0.0712831 10.9714 0.198168 10.8445L5.05126 5.99879L0.198168 1.14736C0.0725521 1.02042 0.00248585 0.848755 0.00338306 0.670131C0.00428028 0.491506 0.0760674 0.320554 0.202952 0.194881C0.329837 0.0692091 0.501426 -0.000888818 0.679971 9.54485e-06C0.858515 0.000906955 1.02939 0.0727263 1.15501 0.199668L6.47312 5.52399C6.59843 5.65017 6.66862 5.82092 6.66831 5.99879Z' fill='%231574A1'/%3E%3C/svg%3E%0A"; $svg-drop-off-alert: "data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"; \ No newline at end of file