Use offer result cards in catalog and compute routes for supplier offers
All checks were successful
Build Docker Image / build (push) Successful in 5m50s

This commit is contained in:
Ruslan Bakiev
2026-02-05 19:02:26 +07:00
parent f1eb7bc746
commit beb02bd3fc
5 changed files with 61 additions and 43 deletions

View File

@@ -37,13 +37,14 @@
<!-- Offers Tab --> <!-- Offers Tab -->
<template v-if="activeTab === 'offers'"> <template v-if="activeTab === 'offers'">
<OfferCard <OfferResultCard
v-for="(offer, index) in offers" v-for="(offer, index) in offers"
:key="offer.uuid ?? index" :key="offer.uuid ?? index"
:offer="offer" :location-name="offer.locationName"
selectable :product-name="offer.title || undefined"
compact :stages="[]"
:is-selected="selectedId === offer.uuid" :start-name="offer.locationName || undefined"
:end-name="undefined"
@select="selectOffer(offer)" @select="selectOffer(offer)"
/> />
<Text v-if="offers.length === 0" tone="muted" size="sm" class="text-center py-4"> <Text v-if="offers.length === 0" tone="muted" size="sm" class="text-center py-4">

View File

@@ -13,10 +13,12 @@
</Stack> </Stack>
<Grid :cols="1" :md="2" :lg="3" :gap="4"> <Grid :cols="1" :md="2" :lg="3" :gap="4">
<OfferCard <OfferResultCard
v-for="(offer, index) in offers" v-for="(offer, index) in offers"
:key="offer.uuid ?? index" :key="offer.uuid ?? index"
:offer="offer" :location-name="offer.locationName"
:product-name="offer.title || undefined"
:stages="[]"
/> />
</Grid> </Grid>

View File

@@ -141,12 +141,15 @@
{{ $t('catalog.empty.noOffers') }} {{ $t('catalog.empty.noOffers') }}
</div> </div>
<div v-else-if="!loadingOffers" class="flex flex-col gap-2"> <div v-else-if="!loadingOffers" class="flex flex-col gap-2">
<OfferCard <OfferResultCard
v-for="(offer, index) in relatedOffers" v-for="(offer, index) in relatedOffers"
:key="offer.uuid ?? index" :key="offer.uuid ?? index"
:offer="offer" :location-name="offer.locationName || offer.locationCountry || offer.locationName"
compact :product-name="offer.productName"
selectable :price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null"
:currency="offer.currency"
:unit="offer.unit"
:stages="getOfferStages(offer)"
@select="onOfferSelect(offer)" @select="onOfferSelect(offer)"
/> />
</div> </div>
@@ -217,6 +220,7 @@ import type {
InfoSupplierItem, InfoSupplierItem,
InfoOfferItem InfoOfferItem
} from '~/composables/useCatalogInfo' } from '~/composables/useCatalogInfo'
import type { RouteStageType } from '~/composables/graphql/public/geo-generated'
const props = defineProps<{ const props = defineProps<{
entityType: InfoEntityType entityType: InfoEntityType
@@ -344,4 +348,15 @@ const onSupplierSelect = (supplier: InfoSupplierItem) => {
emit('open-info', 'supplier', supplier.uuid) emit('open-info', 'supplier', supplier.uuid)
} }
} }
const getOfferStages = (offer: InfoOfferItem) => {
const route = offer.routes?.[0]
if (!route?.stages) return []
return route.stages
.filter((stage): stage is NonNullable<RouteStageType> => stage !== null)
.map(stage => ({
transportType: stage.transportType,
distanceKm: stage.distanceKm
}))
}
</script> </script>

View File

@@ -26,7 +26,14 @@
class="cursor-pointer" class="cursor-pointer"
@click="emit('select-offer', offer)" @click="emit('select-offer', offer)"
> >
<OfferCard :offer="offer" compact selectable /> <OfferResultCard
:location-name="offer.locationName || offer.locationCountry"
:product-name="offer.productName"
:price-per-unit="offer.pricePerUnit ? Number(offer.pricePerUnit) : null"
:currency="offer.currency"
:unit="offer.unit"
:stages="[]"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -431,6 +431,29 @@ export function useCatalogInfo() {
isLoadingHubs.value = true isLoadingHubs.value = true
try { try {
let hubUuid: string | null = relatedHubs.value?.[0]?.uuid ?? null
if (!hubUuid && supplier.uuid) {
const hubsData = await execute(
NearestHubsDocument,
{
lat: supplier.latitude,
lon: supplier.longitude,
radius: 1000,
sourceUuid: supplier.uuid,
limit: 1
},
'public',
'geo'
)
const hub = (hubsData?.nearestHubs || []).find((h): h is HubItem => h !== null)
if (hub?.uuid) {
hubUuid = hub.uuid
if (!relatedHubs.value.length) {
relatedHubs.value = [hub]
}
}
}
// Find offers near supplier for this product // Find offers near supplier for this product
const offersData = await execute( const offersData = await execute(
NearestOffersDocument, NearestOffersDocument,
@@ -438,6 +461,7 @@ export function useCatalogInfo() {
lat: supplier.latitude, lat: supplier.latitude,
lon: supplier.longitude, lon: supplier.longitude,
productUuid, productUuid,
...(hubUuid ? { hubUuid } : {}),
radius: 500, radius: 500,
limit: 12 limit: 12
}, },
@@ -447,37 +471,6 @@ export function useCatalogInfo() {
relatedOffers.value = (offersData?.nearestOffers || []).filter((o): o is OfferItem => o !== null) relatedOffers.value = (offersData?.nearestOffers || []).filter((o): o is OfferItem => o !== null)
isLoadingOffers.value = false isLoadingOffers.value = false
// Load hubs near each offer and aggregate (limit to 12)
const allHubs = new Map<string, HubItem>()
for (const offer of relatedOffers.value.slice(0, 3)) {
// Check first 3 offers
if (!offer.latitude || !offer.longitude) continue
try {
const hubsData = await execute(
NearestHubsDocument,
{
lat: offer.latitude,
lon: offer.longitude,
radius: 1000,
limit: 5
},
'public',
'geo'
)
hubsData?.nearestHubs?.forEach(hub => {
if (hub && hub.uuid && !allHubs.has(hub.uuid)) {
allHubs.set(hub.uuid, hub)
}
})
} catch (e) {
console.warn('Error loading hubs for offer:', offer.uuid, e)
}
if (allHubs.size >= 12) break
}
relatedHubs.value = Array.from(allHubs.values()).slice(0, 12)
} finally { } finally {
isLoadingOffers.value = false isLoadingOffers.value = false
isLoadingHubs.value = false isLoadingHubs.value = false