CASH-2820 | Load more shops
New textLink component for "View more shops" button Add in method which adds in new shops New data member which tracks all providers New data member which tracks if new shops are being loaded
This commit is contained in:
parent
3bd146796c
commit
1a36c38071
1 changed files with 103 additions and 9 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<interceptOverlay v-if="isLoadingDates" />
|
||||
<interceptOverlay v-if="isLoadingDates || isLoadingMoreShops" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container page-container-grouped-styles">
|
||||
<div class="row">
|
||||
|
|
@ -34,20 +34,40 @@
|
|||
:isLoading="isLoadingDates"
|
||||
@zip-code-clicked="onMobileZipCodeClicked" />
|
||||
<inshopSchedulingCard
|
||||
v-for="{ provider } in inShopProvidersAndTimeslots"
|
||||
v-for="entry in inShopProvidersAndTimeslots"
|
||||
v-show="showInshopSchedulingCards"
|
||||
:key="provider.providerNumber"
|
||||
:key="entry.provider.providerNumber"
|
||||
class="mt-4"
|
||||
v-model="selectedScheduling"
|
||||
:provider="provider"
|
||||
:provider="entry.provider"
|
||||
:timeSlots="
|
||||
getInshopTimeSlotsForSelectedDate(provider.providerNumber)
|
||||
getInshopTimeSlotsForSelectedDate(entry.provider.providerNumber)
|
||||
"
|
||||
:radioGroupName="schedulingRadioGroupName"
|
||||
:isLoading="isLoadingDates"
|
||||
@address-clicked="onInshopAddressClicked(provider)" />
|
||||
:isLoading="isLoadingDates || Boolean(entry.isLoadingTimeSlots)"
|
||||
@address-clicked="onInshopAddressClicked(entry.provider)" />
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
<textLink
|
||||
v-if="hasMoreShopsAvailable"
|
||||
id="viewMoreShopsLinkId"
|
||||
linkType="text"
|
||||
:text="viewMoreShopsText"
|
||||
href="javascript:void(0)"
|
||||
@click-event="onViewMoreShopsClick"
|
||||
>
|
||||
<template #after-text>
|
||||
<span class="spacing-gap"></span>
|
||||
<img
|
||||
class="chevron-icon"
|
||||
src="@/assets/img/icons/chevron-no-background.svg"
|
||||
alt=""
|
||||
aria-hidden="true" />
|
||||
</template>
|
||||
</textLink>
|
||||
</div>
|
||||
|
||||
<waitlistQuestion
|
||||
class="mt-5"
|
||||
v-model="isWaitlistRequested"
|
||||
|
|
@ -73,6 +93,7 @@ import mobileSchedulingCard from "@/layouts/scheduling/mobile-scheduling-card/mo
|
|||
import inshopSchedulingCard from "@/layouts/scheduling/inshop-scheduling-card/inshop-scheduling-card";
|
||||
import interceptOverlay from "@/ux-components/intercept-overlay/intercept-overlay";
|
||||
import waitlistQuestion from "@/layouts/scheduling/waitlist-question/waitlist-question";
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import store from "@/store";
|
||||
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/schedule-constants";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
|
|
@ -100,6 +121,9 @@ function toDateString(offsetDays, base = new Date()) {
|
|||
|
||||
const SCHEDULE_FETCH_DAYS = 15;
|
||||
const SCHEDULING_RADIO_GROUP_NAME = "schedulingTimeSlot";
|
||||
const INITIAL_INSHOP_PROVIDER_COUNT = 3;
|
||||
const MAX_INSHOP_PROVIDERS = 9;
|
||||
const VIEW_MORE_SHOPS_BATCH_SIZE = 3;
|
||||
|
||||
/**
|
||||
* Appends days from newSlots into entry.timeSlots, initializing it if absent.
|
||||
|
|
@ -200,11 +224,12 @@ export default {
|
|||
},
|
||||
];
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
// Get the first 3 providers from the shopProviderData
|
||||
const providers = resultMap.providers?.shopProviders?.slice(0, 3) ?? [];
|
||||
const allShopProviders = resultMap.providers?.shopProviders ?? [];
|
||||
const providers = allShopProviders.slice(0, INITIAL_INSHOP_PROVIDER_COUNT);
|
||||
const mobileProviderNumber = resultMap.providers?.mobileProviderNumber ?? null;
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.allShopProviders = allShopProviders;
|
||||
vm.inShopProvidersAndTimeslots = providers.map((provider) => ({
|
||||
provider,
|
||||
timeSlots: null,
|
||||
|
|
@ -269,6 +294,9 @@ export default {
|
|||
serviceLocationText() {
|
||||
return this.getCmsContent("ServiceLocationText", "Text");
|
||||
},
|
||||
viewMoreShopsText() {
|
||||
return this.getCmsContent("ViewMoreShopsWidget", "Text");
|
||||
},
|
||||
serviceZipCode() {
|
||||
return store.getters.order.serviceLocation.zipCode;
|
||||
},
|
||||
|
|
@ -308,6 +336,10 @@ export default {
|
|||
);
|
||||
return [...new Set([...inshopDates, ...mobileDates])].sort();
|
||||
},
|
||||
hasMoreShopsAvailable() {
|
||||
const maxVisible = Math.min(MAX_INSHOP_PROVIDERS, this.allShopProviders.length);
|
||||
return this.inShopProvidersAndTimeslots.length < maxVisible;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -317,10 +349,12 @@ export default {
|
|||
datePickerStartDate: toDateString(0),
|
||||
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
|
||||
inShopProvidersAndTimeslots: [],
|
||||
allShopProviders: [],
|
||||
mobileProviderAndTimeSlot: null,
|
||||
mobilePremiumAppointmentFee: null,
|
||||
selectedScheduling: null,
|
||||
isWaitlistRequested: false,
|
||||
isLoadingMoreShops: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -341,6 +375,50 @@ export default {
|
|||
onMobileZipCodeClicked() {
|
||||
// TODO: open service zip modal when zip edit is implemented for scheduling page
|
||||
},
|
||||
async onViewMoreShopsClick() {
|
||||
if (this.isLoadingDates || this.isLoadingMoreShops) return;
|
||||
|
||||
const maxVisible = Math.min(MAX_INSHOP_PROVIDERS, this.allShopProviders.length);
|
||||
const remainingCapacity = maxVisible - this.inShopProvidersAndTimeslots.length;
|
||||
if (remainingCapacity <= 0) return;
|
||||
|
||||
const displayedProviderNumbers = new Set(
|
||||
this.inShopProvidersAndTimeslots.map(({ provider }) => provider.providerNumber)
|
||||
);
|
||||
const newProviders = this.allShopProviders
|
||||
.filter((provider) => !displayedProviderNumbers.has(provider.providerNumber))
|
||||
.slice(0, Math.min(VIEW_MORE_SHOPS_BATCH_SIZE, remainingCapacity));
|
||||
|
||||
if (!newProviders.length) return;
|
||||
|
||||
const newEntries = newProviders.map((provider) => ({
|
||||
provider,
|
||||
timeSlots: null,
|
||||
isLoadingTimeSlots: true,
|
||||
}));
|
||||
this.inShopProvidersAndTimeslots = [...this.inShopProvidersAndTimeslots, ...newEntries];
|
||||
this.isLoadingMoreShops = true;
|
||||
|
||||
try {
|
||||
const resultMap = await settleAllPromises([
|
||||
{
|
||||
resultKey: "inshopTimeSlots",
|
||||
promise: fetchInshopTimeSlots({
|
||||
startDate: this.datePickerStartDate,
|
||||
endDate: this.datePickerEndDate,
|
||||
providerNumbers: newProviders.map((provider) => provider.providerNumber),
|
||||
pageNameToLog: this.pageName,
|
||||
}),
|
||||
},
|
||||
]);
|
||||
assignInshopTimeSlotsFromV2Response(newEntries, resultMap.inshopTimeSlots);
|
||||
} finally {
|
||||
newEntries.forEach((entry) => {
|
||||
entry.isLoadingTimeSlots = false;
|
||||
});
|
||||
this.isLoadingMoreShops = false;
|
||||
}
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
const order = store.getters.order;
|
||||
const logQueue = [];
|
||||
|
|
@ -449,6 +527,7 @@ export default {
|
|||
inshopSchedulingCard,
|
||||
interceptOverlay,
|
||||
waitlistQuestion,
|
||||
textLink,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -476,4 +555,19 @@ h5 {
|
|||
.card-slide-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.chevron-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
transform: rotate(180deg);
|
||||
transition: transform 150ms linear;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.spacing-gap {
|
||||
margin-right: 6.5px;
|
||||
}
|
||||
|
||||
:deep(.text-link) {
|
||||
font-weight:600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue