diff --git a/app/components/catalog/InfoPanel.vue b/app/components/catalog/InfoPanel.vue
index 86df751..34ecd69 100644
--- a/app/components/catalog/InfoPanel.vue
+++ b/app/components/catalog/InfoPanel.vue
@@ -41,11 +41,17 @@
:key="tab.id"
role="tab"
class="tab text-white/70"
- :class="{ 'tab-active !text-white !bg-white/20': activeTab === tab.id }"
- @click="onTabClick(tab.id)"
+ :class="{
+ 'tab-active !text-white !bg-white/20': activeTab === tab.id,
+ 'pointer-events-none opacity-50': tab.loading
+ }"
+ @click="!tab.loading && onTabClick(tab.id)"
>
{{ tab.label }}
- ({{ tab.count }})
+
+
+
+ ({{ tab.count }})
@@ -185,6 +191,10 @@ const props = defineProps<{
selectedProduct?: string | null
currentTab?: string
loading?: boolean
+ loadingProducts?: boolean
+ loadingHubs?: boolean
+ loadingSuppliers?: boolean
+ loadingOffers?: boolean
}>()
const emit = defineEmits<{
@@ -226,33 +236,45 @@ const entityIcon = computed(() => {
// Available tabs based on entity type and data
const availableTabs = computed(() => {
- const tabs: Array<{ id: string; label: string; count?: number }> = []
+ const tabs: Array<{ id: string; label: string; count?: number; loading?: boolean }> = []
if (props.entityType === 'hub') {
+ // For hub: offers tab shows products first, then offers after product selection
+ const offersLoading = props.selectedProduct ? props.loadingOffers : props.loadingProducts
+ const offersCount = props.selectedProduct
+ ? props.relatedOffers?.length
+ : props.relatedProducts?.length
+
tabs.push({
id: 'offers',
label: t('catalog.tabs.offers'),
- count: props.selectedProduct
- ? props.relatedOffers?.length || 0
- : props.relatedProducts?.length || 0
+ count: offersLoading ? undefined : (offersCount || 0),
+ loading: offersLoading
})
tabs.push({
id: 'suppliers',
label: t('catalog.tabs.suppliers'),
- count: props.relatedSuppliers?.length || 0
+ count: props.loadingSuppliers ? undefined : (props.relatedSuppliers?.length || 0),
+ loading: props.loadingSuppliers
})
} else if (props.entityType === 'supplier') {
+ // For supplier: offers tab shows products first, then offers after product selection
+ const offersLoading = props.selectedProduct ? props.loadingOffers : props.loadingProducts
+ const offersCount = props.selectedProduct
+ ? props.relatedOffers?.length
+ : props.relatedProducts?.length
+
tabs.push({
id: 'offers',
label: t('catalog.tabs.offers'),
- count: props.selectedProduct
- ? props.relatedOffers?.length || 0
- : props.relatedProducts?.length || 0
+ count: offersLoading ? undefined : (offersCount || 0),
+ loading: offersLoading
})
tabs.push({
id: 'hubs',
label: t('catalog.tabs.hubs'),
- count: props.relatedHubs?.length || 0
+ count: props.loadingHubs ? undefined : (props.relatedHubs?.length || 0),
+ loading: props.loadingHubs
})
} else if (props.entityType === 'offer') {
if (props.relatedProducts && props.relatedProducts.length > 0) {
@@ -264,12 +286,15 @@ const availableTabs = computed(() => {
tabs.push({
id: 'hubs',
label: t('catalog.tabs.hubs'),
- count: props.relatedHubs?.length || 0
+ count: props.loadingHubs ? undefined : (props.relatedHubs?.length || 0),
+ loading: props.loadingHubs
})
if (props.relatedSuppliers && props.relatedSuppliers.length > 0) {
tabs.push({
id: 'suppliers',
- label: t('catalog.tabs.supplier')
+ label: t('catalog.tabs.supplier'),
+ count: props.loadingSuppliers ? undefined : (props.relatedSuppliers?.length || 0),
+ loading: props.loadingSuppliers
})
}
}
diff --git a/app/composables/useCatalogInfo.ts b/app/composables/useCatalogInfo.ts
index f33f126..cd293a5 100644
--- a/app/composables/useCatalogInfo.ts
+++ b/app/composables/useCatalogInfo.ts
@@ -287,7 +287,7 @@ export function useCatalogInfo() {
}
// Load offers for supplier after product selection
- const loadOffersForSupplier = async (supplierUuid: string, productUuid: string) => {
+ const loadOffersForSupplier = async (_supplierUuid: string, productUuid: string) => {
try {
const supplier = entity.value
if (!supplier?.latitude || !supplier?.longitude) {
@@ -295,52 +295,61 @@ export function useCatalogInfo() {
return
}
- // Find offers near supplier for this product
- const offersData = await execute(
- NearestOffersDocument,
- {
- lat: supplier.latitude,
- lon: supplier.longitude,
- productUuid,
- radius: 500,
- limit: 12
- },
- 'public',
- 'geo'
- )
+ isLoadingOffers.value = true
+ isLoadingHubs.value = true
- relatedOffers.value = offersData?.nearestOffers || []
+ try {
+ // Find offers near supplier for this product
+ const offersData = await execute(
+ NearestOffersDocument,
+ {
+ lat: supplier.latitude,
+ lon: supplier.longitude,
+ productUuid,
+ radius: 500,
+ limit: 12
+ },
+ 'public',
+ 'geo'
+ )
- // Load hubs near each offer and aggregate (limit to 12)
- const allHubs = new Map()
- for (const offer of relatedOffers.value.slice(0, 3)) {
- // Check first 3 offers
- if (!offer.latitude || !offer.longitude) continue
+ relatedOffers.value = offersData?.nearestOffers || []
+ isLoadingOffers.value = false
- try {
- const hubsData = await execute(
- NearestHubsDocument,
- {
- lat: offer.latitude,
- lon: offer.longitude,
- radius: 1000,
- limit: 5
- },
- 'public',
- 'geo'
- )
- hubsData?.nearestHubs?.forEach((hub: any) => {
- if (!allHubs.has(hub.uuid)) {
- allHubs.set(hub.uuid, hub)
- }
- })
- } catch (e) {
- console.warn('Error loading hubs for offer:', offer.uuid, e)
+ // Load hubs near each offer and aggregate (limit to 12)
+ const allHubs = new Map()
+ 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: any) => {
+ if (!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
}
-
- if (allHubs.size >= 12) break
+ relatedHubs.value = Array.from(allHubs.values()).slice(0, 12)
+ } finally {
+ isLoadingOffers.value = false
+ isLoadingHubs.value = false
}
- relatedHubs.value = Array.from(allHubs.values()).slice(0, 12)
} catch (error) {
console.error('Error loading offers for supplier:', error)
}
@@ -396,6 +405,10 @@ export function useCatalogInfo() {
relatedOffers.value = []
selectedProduct.value = null
activeTab.value = 'products'
+ isLoadingProducts.value = false
+ isLoadingHubs.value = false
+ isLoadingSuppliers.value = false
+ isLoadingOffers.value = false
}
return {
@@ -408,6 +421,10 @@ export function useCatalogInfo() {
selectedProduct,
activeTab,
isLoading,
+ isLoadingProducts,
+ isLoadingHubs,
+ isLoadingSuppliers,
+ isLoadingOffers,
// Actions
loadInfo,
diff --git a/app/pages/catalog/index.vue b/app/pages/catalog/index.vue
index 37c4a21..c044b80 100644
--- a/app/pages/catalog/index.vue
+++ b/app/pages/catalog/index.vue
@@ -46,6 +46,10 @@
:selected-product="infoProduct ?? null"
:current-tab="infoTab"
:loading="infoLoading"
+ :loading-products="isLoadingProducts"
+ :loading-hubs="isLoadingHubs"
+ :loading-suppliers="isLoadingSuppliers"
+ :loading-offers="isLoadingOffers"
@close="onInfoClose"
@add-to-filter="onInfoAddToFilter"
@open-info="onInfoOpenRelated"
@@ -134,6 +138,10 @@ const {
relatedOffers,
selectedProduct,
isLoading: infoLoading,
+ isLoadingProducts,
+ isLoadingHubs,
+ isLoadingSuppliers,
+ isLoadingOffers,
loadInfo,
selectProduct: selectInfoProduct,
clearInfo
@@ -253,7 +261,7 @@ watch(infoProduct, async (productUuid) => {
}
})
-// Related points for Info mode (shown on map)
+// Related points for Info mode (shown on map) - filtered by active tab
const relatedPoints = computed(() => {
if (!infoId.value) return []
@@ -265,44 +273,57 @@ const relatedPoints = computed(() => {
type: 'hub' | 'supplier' | 'offer'
}> = []
- // Add hubs
- relatedHubs.value.forEach(hub => {
- if (hub.latitude && hub.longitude) {
- points.push({
- uuid: hub.uuid,
- name: hub.name,
- latitude: hub.latitude,
- longitude: hub.longitude,
- type: 'hub'
- })
- }
- })
+ const currentTab = infoTab.value
- // Add suppliers
- relatedSuppliers.value.forEach(supplier => {
- if (supplier.latitude && supplier.longitude) {
- points.push({
- uuid: supplier.uuid,
- name: supplier.name,
- latitude: supplier.latitude,
- longitude: supplier.longitude,
- type: 'supplier'
- })
- }
- })
+ // Show content based on active tab
+ // Hub entity: offers tab → offers, suppliers tab → suppliers
+ // Supplier entity: offers tab → offers, hubs tab → hubs
+ // Offer entity: hubs tab → hubs, suppliers tab → suppliers
- // Add offers
- relatedOffers.value.forEach(offer => {
- if (offer.latitude && offer.longitude) {
- points.push({
- uuid: offer.uuid,
- name: offer.productName || offer.name,
- latitude: offer.latitude,
- longitude: offer.longitude,
- type: 'offer'
- })
- }
- })
+ // Add hubs (for supplier's hubs tab or offer's hubs tab)
+ if (currentTab === 'hubs') {
+ relatedHubs.value.forEach(hub => {
+ if (hub.latitude && hub.longitude) {
+ points.push({
+ uuid: hub.uuid,
+ name: hub.name,
+ latitude: hub.latitude,
+ longitude: hub.longitude,
+ type: 'hub'
+ })
+ }
+ })
+ }
+
+ // Add suppliers (for hub's suppliers tab or offer's suppliers tab)
+ if (currentTab === 'suppliers') {
+ relatedSuppliers.value.forEach(supplier => {
+ if (supplier.latitude && supplier.longitude) {
+ points.push({
+ uuid: supplier.uuid,
+ name: supplier.name,
+ latitude: supplier.latitude,
+ longitude: supplier.longitude,
+ type: 'supplier'
+ })
+ }
+ })
+ }
+
+ // Add offers (for hub's/supplier's offers tab when product is selected)
+ if (currentTab === 'offers' && infoProduct.value) {
+ relatedOffers.value.forEach(offer => {
+ if (offer.latitude && offer.longitude) {
+ points.push({
+ uuid: offer.uuid,
+ name: offer.productName || offer.name,
+ latitude: offer.latitude,
+ longitude: offer.longitude,
+ type: 'offer'
+ })
+ }
+ })
+ }
return points
})