Draft implementation.

This commit is contained in:
Chloe Herd 2026-07-29 14:28:16 -04:00
parent 9ce6b3b412
commit beb3350957
11 changed files with 561 additions and 71 deletions

View file

@ -0,0 +1,107 @@
import {
getAvailableAppointmentDatesToShow,
getAvailableDatesFromProviderDays,
} from "./schedule-helper";
describe("getAvailableDatesFromProviderDays", () => {
test("returns sorted dates that have at least one time slot", () => {
const days = [
{ date: "2026-06-16", timeSlots: [{ id: "a" }] },
{ date: "2026-06-11", timeSlots: [] },
{ date: "2026-06-14", timeSlots: [{ id: "b" }, { id: "c" }] },
];
expect(getAvailableDatesFromProviderDays(days)).toEqual(["2026-06-14", "2026-06-16"]);
});
test("returns an empty array when days is null or undefined", () => {
expect(getAvailableDatesFromProviderDays(null)).toEqual([]);
expect(getAvailableDatesFromProviderDays(undefined)).toEqual([]);
});
});
describe("getAvailableAppointmentDatesToShow", () => {
const selectedDate = "2026-06-15";
test("AC1: happy path", () => {
const available = [
"2026-06-11",
"2026-06-12",
"2026-06-13",
"2026-06-14",
"2026-06-16",
"2026-06-17",
"2026-06-18",
"2026-06-19",
"2026-06-20",
];
expect(getAvailableAppointmentDatesToShow(available, selectedDate)).toEqual([
"2026-06-11",
"2026-06-14",
"2026-06-16",
]);
});
test("AC2: no previous dates available", () => {
const available = ["2026-06-16", "2026-06-17", "2026-06-18", "2026-06-19", "2026-06-20"];
expect(getAvailableAppointmentDatesToShow(available, selectedDate)).toEqual([
"2026-06-16",
"2026-06-17",
"2026-06-18",
]);
});
test("AC3: no next dates available", () => {
const available = [
"2026-06-09",
"2026-06-10",
"2026-06-11",
"2026-06-12",
"2026-06-13",
"2026-06-14",
];
expect(getAvailableAppointmentDatesToShow(available, selectedDate)).toEqual([
"2026-06-09",
"2026-06-10",
"2026-06-14",
]);
});
test("AC4: only one previous date available and it matches earliest", () => {
const available = [
"2026-06-11",
"2026-06-16",
"2026-06-17",
"2026-06-18",
"2026-06-19",
"2026-06-20",
];
expect(getAvailableAppointmentDatesToShow(available, selectedDate)).toEqual([
"2026-06-11",
"2026-06-16",
"2026-06-17",
]);
});
test("AC5: no dates available", () => {
expect(getAvailableAppointmentDatesToShow([], selectedDate)).toEqual([]);
expect(getAvailableAppointmentDatesToShow([""], selectedDate)).toEqual([]);
expect(getAvailableAppointmentDatesToShow([], null)).toEqual([]);
});
test("AC6: only one date available", () => {
expect(getAvailableAppointmentDatesToShow(["2026-06-13"], selectedDate)).toEqual([
"2026-06-13",
]);
});
test("AC7: only two dates available", () => {
expect(
getAvailableAppointmentDatesToShow(["2026-06-11", "2026-06-12"], selectedDate)
).toEqual(["2026-06-11", "2026-06-12"]);
});
});

View file

@ -257,3 +257,104 @@ export function getInitialViewWeeks(todayString, initialViewRowsToShow, preSelec
}
return weeks;
}
export const SCHEDULING_DAY_ABBRS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
export const SCHEDULING_MONTH_ABBRS = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const MAX_AVAILABLE_APPOINTMENT_DATES_TO_SHOW = 3;
/**
* Returns sorted YYYY-MM-DD strings for days that have at least one time slot.
* @param {Array<{ date: string, timeSlots?: Array<unknown> }> | null | undefined} days
*/
export function getAvailableDatesFromProviderDays(days) {
return (days ?? [])
.filter((day) => day.timeSlots?.length > 0)
.map((day) => day.date)
.sort();
}
/**
* Selects up to three available appointment dates to display when the selected day has no slots.
* Priority order: earliest, previous closest to selected, next closest to selected, then chronological.
* @param {string[]} availableDates - Sorted or unsorted YYYY-MM-DD strings with availability.
* @param {string} selectedDate - Currently selected YYYY-MM-DD date from the date picker.
* @returns {string[]}
*/
export function getAvailableAppointmentDatesToShow(availableDates, selectedDate) {
const sorted = [...new Set((availableDates ?? []).filter(Boolean))].sort();
if (sorted.length === 0 || !selectedDate) {
return [];
}
const result = [];
const used = new Set();
const add = (date) => {
if (date && sorted.includes(date) && !used.has(date)) {
used.add(date);
result.push(date);
}
};
const earliest = sorted[0];
const previousClosest = sorted.filter((date) => date < selectedDate).at(-1) ?? null;
const nextClosest = sorted.find((date) => date > selectedDate) ?? null;
const priorityCandidates = [earliest];
if (previousClosest && previousClosest !== earliest) {
priorityCandidates.push(previousClosest);
} else if (nextClosest && nextClosest !== earliest) {
priorityCandidates.push(nextClosest);
}
if (nextClosest) {
priorityCandidates.push(nextClosest);
}
for (const date of priorityCandidates) {
if (result.length >= MAX_AVAILABLE_APPOINTMENT_DATES_TO_SHOW) {
break;
}
add(date);
}
for (const date of sorted) {
if (result.length >= MAX_AVAILABLE_APPOINTMENT_DATES_TO_SHOW) {
break;
}
add(date);
}
return result.sort();
}
/**
* @param {string} dateString - YYYY-MM-DD
*/
export function formatSchedulingDateChip(dateString) {
const date = convertDateStringToDate(dateString);
const dayAbbr = SCHEDULING_DAY_ABBRS[date.getDay()];
const monthAbbr = SCHEDULING_MONTH_ABBRS[date.getMonth()];
const day = date.getDate();
const dayLabel = `${dayAbbr.charAt(0) + dayAbbr.slice(1).toLowerCase()}.`;
return {
value: dateString,
label: `${dayLabel} ${monthAbbr} ${day}`,
};
}

View file

@ -0,0 +1,32 @@
import { shallowMount } from "@vue/test-utils";
import availableAppointmentDateChips from "./available-appointment-date-chips.vue";
describe("available-appointment-date-chips.vue", () => {
test("renders a button for each date", () => {
const wrapper = shallowMount(availableAppointmentDateChips, {
props: {
dates: ["2026-06-11", "2026-06-16"],
label: "Available appointments:",
},
});
expect(wrapper.text()).toContain("Available appointments:");
expect(wrapper.findAll(".available-appointment-date-chips__day-card").length).toBe(2);
expect(wrapper.text()).toContain("Thu. Jun 11");
expect(wrapper.text()).toContain("Tue. Jun 16");
expect(wrapper.findAll(".available-appointment-date-chips__day-label").length).toBe(2);
wrapper.unmount();
});
test("emits date-selected when a chip is clicked", async () => {
const wrapper = shallowMount(availableAppointmentDateChips, {
props: {
dates: ["2026-06-11"],
},
});
await wrapper.find(".available-appointment-date-chips__day-card").trigger("click");
expect(wrapper.emitted("date-selected")[0]).toEqual(["2026-06-11"]);
wrapper.unmount();
});
});

View file

@ -0,0 +1,83 @@
<template>
<div class="available-appointment-date-chips">
<p v-if="label" class="available-appointment-date-chips__label">{{ label }}</p>
<div class="available-appointment-date-chips__track">
<button
v-for="date in formattedDates"
:key="date.value"
type="button"
class="available-appointment-date-chips__day-card d-flex align-items-center justify-content-center"
:aria-label="date.label"
@click="$emit('date-selected', date.value)">
<span class="available-appointment-date-chips__day-label">{{ date.label }}</span>
</button>
</div>
</div>
</template>
<script>
import { formatSchedulingDateChip } from "@/layouts/schedule/helpers/schedule-helper";
export default {
name: "availableAppointmentDateChips",
emits: ["date-selected"],
props: {
dates: {
type: Array,
default: () => [],
},
label: {
type: String,
default: "",
},
},
computed: {
formattedDates() {
return this.dates.map(formatSchedulingDateChip);
},
},
};
</script>
<style lang="scss" scoped>
.available-appointment-date-chips {
&__label {
margin: 0 0 8px;
font-size: 1rem;
font-weight: 600;
line-height: 1.5rem;
color: $black;
}
&__track {
display: flex;
gap: 0.25rem;
}
&__day-card {
flex: 1;
min-width: 0;
border: 1px solid $blue;
border-radius: $border-radius-lg;
background: $white;
padding: 0.375rem 0.5rem;
cursor: pointer;
transition:
background 150ms ease,
border-color 150ms ease;
&:hover {
background: $blue-100;
}
}
&__day-label {
font-family: $font-family-sans-serif-bold;
font-size: $font-size-14;
line-height: 1.25rem;
letter-spacing: 0.03em;
color: $blue;
white-space: nowrap;
}
}
</style>

View file

@ -154,6 +154,30 @@ describe("date-picker.vue", () => {
expect(wrapper.find(".date-picker__day-card--selected").exists()).toBe(false);
wrapper.unmount();
});
test("scrolls visible window and updates selection when modelValue changes externally", async () => {
const wrapper = mountDesktop({
availableDates: [
"2026-01-21",
"2026-01-22",
"2026-01-23",
"2026-01-25",
"2026-01-28",
"2026-01-30",
],
startDate: "2026-01-21",
endDate: "2026-01-30",
modelValue: "2026-01-21",
});
expect(wrapper.vm.windowStart).toBe(0);
await wrapper.setProps({ modelValue: "2026-01-28" });
expect(wrapper.vm.windowStart).toBe(5);
expect(wrapper.find(".date-picker__day-card--selected").exists()).toBe(true);
wrapper.unmount();
});
});
describe("navigation", () => {

View file

@ -171,6 +171,10 @@ export default {
},
},
watch: {
modelValue(newValue, oldValue) {
if (newValue === oldValue) return;
this.syncWindowToSelectedDate();
},
allDates(newDates) {
if (!newDates.length) return;
@ -182,7 +186,9 @@ export default {
if (this.pendingAutoSelect) {
this.pendingAutoSelect = false;
this.advanceWindowAndAutoSelect();
return;
}
this.syncWindowToSelectedDate();
},
},
mounted() {
@ -192,8 +198,7 @@ export default {
// If a model value is set, we need to set the window start to the index of the model value.
// Otherwise, we need to initialize the window.
if (this.modelValue) {
const index = this.allDates.findIndex((d) => d.value === this.modelValue);
if (index !== -1) this.windowStart = this.getWindowStartFromIndex(index);
this.syncWindowToSelectedDate();
} else {
this.initialize();
}
@ -214,6 +219,14 @@ export default {
getWindowStartFromIndex(index) {
return Math.floor(index / this.windowSize) * this.windowSize;
},
syncWindowToSelectedDate() {
if (!this.modelValue || !this.allDates.length) return;
const index = this.allDates.findIndex((date) => date.value === this.modelValue);
if (index !== -1) {
this.windowStart = this.getWindowStartFromIndex(index);
}
},
goBack() {
this.windowStart = Math.max(0, this.windowStart - this.windowSize);
this.autoSelectFirstAvailable();

View file

@ -134,6 +134,31 @@ describe("inshop-scheduling-card.vue", () => {
wrapper.unmount();
});
test("shows available date chips when selected day has no slots and chips are provided", async () => {
const wrapper = mountComponent({
timeSlots: [],
availableDateChips: ["2026-06-11", "2026-06-14", "2026-06-16"],
});
await wrapper.find(".inshop-scheduling-card__header").trigger("click");
expect(wrapper.text()).toContain("Available appointments:");
expect(wrapper.find("available-appointment-date-chips-stub").exists()).toBe(true);
expect(wrapper.find(".inshop-scheduling-card__time-slots").exists()).toBe(false);
wrapper.unmount();
});
test("emits date-selected when an available date chip is selected", async () => {
const wrapper = mountComponent({
timeSlots: [],
availableDateChips: ["2026-06-11"],
});
await wrapper.find(".inshop-scheduling-card__header").trigger("click");
await wrapper
.find("available-appointment-date-chips-stub")
.vm.$emit("date-selected", "2026-06-11");
expect(wrapper.emitted("date-selected")[0]).toEqual(["2026-06-11"]);
wrapper.unmount();
});
test("shows drop off SubText only when the option is selected", async () => {
const wrapper = mountComponent();
expect(wrapper.text()).not.toContain("Park in any open spot");

View file

@ -37,73 +37,82 @@
{{ formattedAddress }}
</button>
<div v-show="isExpanded" class="inshop-scheduling-card__body">
<div v-if="dropOffSection" class="inshop-scheduling-card__section">
<p class="inshop-scheduling-card__section-label">{{ dropOffSection.label }}</p>
<fieldset class="inshop-scheduling-card__time-slots">
<legend class="sr-only">{{ dropOffSection.label }}</legend>
<label
v-for="slot in dropOffSection.slots"
:key="slot.value"
class="inshop-scheduling-card__slot inshop-scheduling-card__slot--full-width"
:class="{
'inshop-scheduling-card__slot--selected': isSlotSelected(slot),
}">
<input
type="radio"
class="inshop-scheduling-card__slot-input"
:name="radioGroupName"
:value="slot.value"
:checked="isSlotSelected(slot)"
@change="onTimeSlotSelected(slot)" />
<span class="inshop-scheduling-card__slot-label">
<span class="inshop-scheduling-card__slot-label-primary">
{{ slot.label }}
<availableAppointmentDateChips
v-if="showAvailableDateChips"
:dates="availableDateChips"
:label="availableDatesLabel"
@date-selected="$emit('date-selected', $event)" />
<template v-else>
<div v-if="dropOffSection" class="inshop-scheduling-card__section">
<p class="inshop-scheduling-card__section-label">
{{ dropOffSection.label }}
</p>
<fieldset class="inshop-scheduling-card__time-slots">
<legend class="sr-only">{{ dropOffSection.label }}</legend>
<label
v-for="slot in dropOffSection.slots"
:key="slot.value"
class="inshop-scheduling-card__slot inshop-scheduling-card__slot--full-width"
:class="{
'inshop-scheduling-card__slot--selected': isSlotSelected(slot),
}">
<input
type="radio"
class="inshop-scheduling-card__slot-input"
:name="radioGroupName"
:value="slot.value"
:checked="isSlotSelected(slot)"
@change="onTimeSlotSelected(slot)" />
<span class="inshop-scheduling-card__slot-label">
<span class="inshop-scheduling-card__slot-label-primary">
{{ slot.label }}
</span>
<span
v-if="slot.subText && isSlotSelected(slot)"
class="inshop-scheduling-card__slot-label-secondary"
v-html="slot.subText" />
</span>
<span
v-if="slot.subText && isSlotSelected(slot)"
class="inshop-scheduling-card__slot-label-secondary"
v-html="slot.subText" />
</span>
</label>
</fieldset>
</div>
<div
v-for="section in displayTimeSlotSections"
:key="section.key"
class="inshop-scheduling-card__section">
<p class="inshop-scheduling-card__section-label">{{ section.label }}</p>
<fieldset
class="inshop-scheduling-card__time-slots inshop-scheduling-card__time-slots--grid">
<legend class="sr-only">{{ section.label }}</legend>
<label
v-for="slot in section.visibleSlots"
:key="slot.value"
class="inshop-scheduling-card__slot"
:class="{
'inshop-scheduling-card__slot--selected': isSlotSelected(slot),
}">
<input
type="radio"
class="inshop-scheduling-card__slot-input"
:name="radioGroupName"
:value="slot.value"
:checked="isSlotSelected(slot)"
@change="onTimeSlotSelected(slot)" />
<span class="inshop-scheduling-card__slot-label">
<span class="inshop-scheduling-card__slot-label-primary">
{{ slot.label }}
</label>
</fieldset>
</div>
<div
v-for="section in displayTimeSlotSections"
:key="section.key"
class="inshop-scheduling-card__section">
<p class="inshop-scheduling-card__section-label">{{ section.label }}</p>
<fieldset
class="inshop-scheduling-card__time-slots inshop-scheduling-card__time-slots--grid">
<legend class="sr-only">{{ section.label }}</legend>
<label
v-for="slot in section.visibleSlots"
:key="slot.value"
class="inshop-scheduling-card__slot"
:class="{
'inshop-scheduling-card__slot--selected': isSlotSelected(slot),
}">
<input
type="radio"
class="inshop-scheduling-card__slot-input"
:name="radioGroupName"
:value="slot.value"
:checked="isSlotSelected(slot)"
@change="onTimeSlotSelected(slot)" />
<span class="inshop-scheduling-card__slot-label">
<span class="inshop-scheduling-card__slot-label-primary">
{{ slot.label }}
</span>
</span>
</span>
</label>
</fieldset>
<button
v-if="section.showViewMoreLink"
type="button"
class="inshop-scheduling-card__view-more-link"
@click="onViewMoreTimesClick(section.key)">
{{ viewMoreTimesText }}
</button>
</div>
</label>
</fieldset>
<button
v-if="section.showViewMoreLink"
type="button"
class="inshop-scheduling-card__view-more-link"
@click="onViewMoreTimesClick(section.key)">
{{ viewMoreTimesText }}
</button>
</div>
</template>
</div>
</div>
</div>
@ -117,6 +126,7 @@ import {
mapInshopTimeSlotToDisplaySlot,
} from "@/layouts/schedule/helpers/schedule-helper";
import schedulingCardLoader from "@/layouts/scheduling/scheduling-card-loader/scheduling-card-loader";
import availableAppointmentDateChips from "@/layouts/scheduling/available-appointment-date-chips/available-appointment-date-chips.vue";
function toTitleCase(str) {
if (!str) return "";
@ -133,7 +143,7 @@ const DROPOFF_CMS_NAME_TO_ROUTE_FLAG = {
export default {
name: "inshop-scheduling-card",
emits: ["update:modelValue", "address-clicked"],
emits: ["update:modelValue", "address-clicked", "date-selected"],
props: {
modelValue: {
type: Object,
@ -163,6 +173,10 @@ export default {
type: Boolean,
default: false,
},
availableDateChips: {
type: Array,
default: () => [],
},
},
data() {
return {
@ -272,6 +286,15 @@ export default {
appointmentCountLabel() {
return `${this.appointmentCount} appt${this.appointmentCount === 1 ? "" : "s"}`;
},
showAvailableDateChips() {
return this.availableDateChips.length > 0 && this.appointmentCount === 0;
},
availableDatesLabel() {
return (
this.getCmsContent(this.cmsWidgetName, "AvailableDatesLabel") ||
"Available appointments:"
);
},
selectedRouteCodeValue() {
if (
this.modelValue?.appointmentType !== AppointmentTypeStrings.IN_SHOP ||
@ -327,6 +350,7 @@ export default {
},
components: {
schedulingCardLoader,
availableAppointmentDateChips,
},
};
</script>

View file

@ -83,6 +83,29 @@ describe("mobile-scheduling-card.vue", () => {
wrapper.unmount();
});
test("shows available date chips when selected day has no slots and chips are provided", () => {
const wrapper = mountComponent({
timeSlots: [],
availableDateChips: ["2026-06-11", "2026-06-14", "2026-06-16"],
});
expect(wrapper.text()).toContain("Available appointments:");
expect(wrapper.find("available-appointment-date-chips-stub").exists()).toBe(true);
expect(wrapper.find(".mobile-scheduling-card__time-slots").exists()).toBe(false);
wrapper.unmount();
});
test("emits date-selected when an available date chip is selected", async () => {
const wrapper = mountComponent({
timeSlots: [],
availableDateChips: ["2026-06-11"],
});
await wrapper
.find("available-appointment-date-chips-stub")
.vm.$emit("date-selected", "2026-06-11");
expect(wrapper.emitted("date-selected")[0]).toEqual(["2026-06-11"]);
wrapper.unmount();
});
test("shows free flag when showFreeFlag is true", () => {
const wrapper = mountComponent({ showFreeFlag: true });
expect(wrapper.text()).toContain("We'll come to you for free!");

View file

@ -51,7 +51,12 @@
<span class="mobile-scheduling-card__address-hint">{{ addressHintText }}</span>
</div>
<div v-show="isExpanded" class="mobile-scheduling-card__body">
<fieldset class="mobile-scheduling-card__time-slots">
<availableAppointmentDateChips
v-if="showAvailableDateChips"
:dates="availableDateChips"
:label="availableDatesLabel"
@date-selected="$emit('date-selected', $event)" />
<fieldset v-else class="mobile-scheduling-card__time-slots">
<legend class="sr-only">{{ headerTitle }}</legend>
<label
v-for="slot in displayTimeSlots"
@ -91,10 +96,11 @@
import { PREMIUM_TIME_SLOT_ID_FLAG, AppointmentTypeStrings } from "@/constants/schedule-constants";
import { militaryToTwelveHourTime } from "@/layouts/schedule/helpers/schedule-helper";
import schedulingCardLoader from "@/layouts/scheduling/scheduling-card-loader/scheduling-card-loader.vue";
import availableAppointmentDateChips from "@/layouts/scheduling/available-appointment-date-chips/available-appointment-date-chips.vue";
export default {
name: "mobile-scheduling-card",
emits: ["update:modelValue", "zip-code-clicked"],
emits: ["update:modelValue", "zip-code-clicked", "date-selected"],
props: {
modelValue: {
type: Object,
@ -132,6 +138,10 @@ export default {
type: Boolean,
default: false,
},
availableDateChips: {
type: Array,
default: () => [],
},
},
data() {
return {
@ -160,6 +170,15 @@ export default {
appointmentCountLabel() {
return `${this.appointmentCount} appt${this.appointmentCount === 1 ? "" : "s"}`;
},
showAvailableDateChips() {
return this.availableDateChips.length > 0 && this.displayTimeSlots.length === 0;
},
availableDatesLabel() {
return (
this.getCmsContent(this.cmsWidgetName, "AvailableDatesLabel") ||
"Available appointments:"
);
},
hasPremiumTimeSlot() {
return this.timeSlots?.[0]?.offerPremium === true && this.premiumTimeSlotPrice != null;
},
@ -239,6 +258,7 @@ export default {
},
components: {
schedulingCardLoader,
availableAppointmentDateChips,
},
};
</script>

View file

@ -27,12 +27,14 @@
v-model="selectedScheduling"
:providerNumber="mobileProviderAndTimeSlot.providerNumber"
:timeSlots="mobileTimeSlotsForSelectedDate"
:availableDateChips="mobileAvailableDateChips"
:premiumTimeSlotPrice="premiumTimeSlotPrice"
:zipCode="serviceZipCode"
:showFreeFlag="showMobileFreeFlag"
:radioGroupName="schedulingRadioGroupName"
:isLoading="isLoadingDates"
@zip-code-clicked="onMobileZipCodeClicked" />
@zip-code-clicked="onMobileZipCodeClicked"
@date-selected="onSchedulingDateSelected" />
<inshopSchedulingCard
v-for="entry in inshopProvidersAndTimeSlots"
v-show="showInshopSchedulingCards"
@ -43,9 +45,13 @@
:timeSlots="
getInshopTimeSlotsForSelectedDate(entry.provider.providerNumber)
"
:availableDateChips="
getInshopAvailableDateChips(provider.providerNumber)
"
:radioGroupName="schedulingRadioGroupName"
:isLoading="isLoadingDates || Boolean(entry.isLoadingTimeSlots)"
@address-clicked="onInshopAddressClicked(entry.provider)" />
@date-selected="onSchedulingDateSelected" />
</div>
</Transition>
<div class="d-flex justify-content-center mt-4">
@ -104,6 +110,11 @@ import {
hasGlassPartsOrRepairInfo,
hasInsuranceInfo,
} from "@/helpers/page-prerequisites-helper.js";
import { debugLog } from "@/helpers/debug-log-helper";
import {
getAvailableAppointmentDatesToShow,
getAvailableDatesFromProviderDays,
} from "@/layouts/schedule/helpers/schedule-helper";
/**
* Returns a YYYY-MM-DD date string offset by the given number of days from a base date.
@ -345,6 +356,15 @@ export default {
);
return day?.timeSlots ?? [];
},
mobileAvailableDateChips() {
if (!this.selectedDate || this.mobileTimeSlotsForSelectedDate.length > 0) {
return [];
}
const availableDates = getAvailableDatesFromProviderDays(
this.mobileProviderAndTimeSlot?.timeSlots?.days
);
return getAvailableAppointmentDatesToShow(availableDates, this.selectedDate);
},
premiumTimeSlotPrice() {
if (this.mobilePremiumAppointmentFee?.partType !== PREMIUM_FEE_PART_TYPE) {
return null;
@ -396,6 +416,24 @@ export default {
const day = providerEntry?.timeSlots?.days?.find((d) => d.date === this.selectedDate);
return day?.timeSlots ?? [];
},
getInshopAvailableDateChips(providerNumber) {
if (
!this.selectedDate ||
this.getInshopTimeSlotsForSelectedDate(providerNumber).length
) {
return [];
}
const providerEntry = this.inShopProvidersAndTimeslots.find(
({ provider }) => provider.providerNumber === providerNumber
);
const availableDates = getAvailableDatesFromProviderDays(
providerEntry?.timeSlots?.days
);
return getAvailableAppointmentDatesToShow(availableDates, this.selectedDate);
},
onSchedulingDateSelected(date) {
this.selectedDate = date;
},
onInshopAddressClicked(provider) {
// TODO: open Google Maps when address link functionality is implemented
console.log("onInshopAddressClicked", provider?.providerNumber);