All checks were successful
Build Docker Image / build (push) Successful in 3m23s
- Add useCatalogSearch composable for managing unified search state - Add UnifiedSearchBar component with token chips for filters - Add CatalogHero component for empty/landing state - Create grid components for each display mode: - CatalogGridProducts, CatalogGridSuppliers, CatalogGridHubs - CatalogGridHubsForProduct, CatalogGridProductsFromSupplier - CatalogGridProductsInHub, CatalogGridOffers - Add unified catalog page at /catalog with query params - Remove SubNavigation from catalog section (kept for other sections) - Update all links to use new unified catalog paths - Delete old nested catalog pages (offers/suppliers/hubs flows) - Add i18n translations for catalog section
125 lines
3.2 KiB
Vue
125 lines
3.2 KiB
Vue
<template>
|
|
<div>
|
|
<Text size="lg" weight="semibold" class="mb-4">
|
|
{{ t('catalog.headers.offers') }}
|
|
<span v-if="!isLoading" class="text-base-content/50 font-normal">
|
|
({{ filteredItems.length }})
|
|
</span>
|
|
</Text>
|
|
|
|
<div v-if="isLoading" class="flex justify-center py-12">
|
|
<span class="loading loading-spinner loading-lg"></span>
|
|
</div>
|
|
|
|
<div v-else-if="filteredItems.length === 0" class="text-center py-12">
|
|
<Text tone="muted">{{ t('catalog.empty.noOffers') }}</Text>
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<OfferCard
|
|
v-for="offer in filteredItems"
|
|
:key="offer.uuid"
|
|
:offer="offer"
|
|
linkable
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
GetOffersDocument,
|
|
GetSupplierOffersDocument
|
|
} from '~/composables/graphql/public/exchange-generated'
|
|
|
|
const props = defineProps<{
|
|
productId?: string | null
|
|
supplierId?: string | null
|
|
hubId?: string | null
|
|
searchQuery: string
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const { execute } = useGraphQL()
|
|
|
|
const items = ref<any[]>([])
|
|
const isLoading = ref(true)
|
|
|
|
const fetchData = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
let offers: any[] = []
|
|
|
|
if (props.productId && props.hubId) {
|
|
// Product + Hub = specific offers
|
|
const data = await execute(
|
|
GetOffersDocument,
|
|
{
|
|
productUuid: props.productId,
|
|
locationUuid: props.hubId
|
|
},
|
|
'public',
|
|
'exchange'
|
|
)
|
|
offers = data?.getOffers || []
|
|
} else if (props.supplierId && props.productId) {
|
|
// Supplier + Product = offers from supplier for product
|
|
const data = await execute(
|
|
GetSupplierOffersDocument,
|
|
{ teamUuid: props.supplierId },
|
|
'public',
|
|
'exchange'
|
|
)
|
|
offers = (data?.getOffers || []).filter(
|
|
(o: any) => o.productUuid === props.productId
|
|
)
|
|
} else if (props.supplierId) {
|
|
// Just supplier = all offers from supplier
|
|
const data = await execute(
|
|
GetSupplierOffersDocument,
|
|
{ teamUuid: props.supplierId },
|
|
'public',
|
|
'exchange'
|
|
)
|
|
offers = data?.getOffers || []
|
|
} else if (props.hubId) {
|
|
// Just hub = all offers in hub
|
|
const data = await execute(
|
|
GetOffersDocument,
|
|
{ locationUuid: props.hubId },
|
|
'public',
|
|
'exchange'
|
|
)
|
|
offers = data?.getOffers || []
|
|
} else if (props.productId) {
|
|
// Just product = all offers for product
|
|
const data = await execute(
|
|
GetOffersDocument,
|
|
{ productUuid: props.productId },
|
|
'public',
|
|
'exchange'
|
|
)
|
|
offers = data?.getOffers || []
|
|
}
|
|
|
|
items.value = offers
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
await fetchData()
|
|
|
|
watch([() => props.productId, () => props.supplierId, () => props.hubId], fetchData)
|
|
|
|
const filteredItems = computed(() => {
|
|
if (!props.searchQuery.trim()) return items.value
|
|
const q = props.searchQuery.toLowerCase()
|
|
return items.value.filter((item: any) =>
|
|
item.productName?.toLowerCase().includes(q) ||
|
|
item.teamName?.toLowerCase().includes(q) ||
|
|
item.locationName?.toLowerCase().includes(q)
|
|
)
|
|
})
|
|
</script>
|