feat(catalog): add loading states for InfoPanel tabs and filter map by active tab
All checks were successful
Build Docker Image / build (push) Successful in 3m35s
All checks were successful
Build Docker Image / build (push) Successful in 3m35s
- Add separate loading states for products, hubs, suppliers, offers - Show spinner on tabs while loading, disable tab during load - Filter relatedPoints on map by current active tab
This commit is contained in:
@@ -41,11 +41,17 @@
|
|||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
role="tab"
|
role="tab"
|
||||||
class="tab text-white/70"
|
class="tab text-white/70"
|
||||||
:class="{ 'tab-active !text-white !bg-white/20': activeTab === tab.id }"
|
:class="{
|
||||||
@click="onTabClick(tab.id)"
|
'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.label }}
|
||||||
<span v-if="tab.count !== undefined" class="ml-1 opacity-70">({{ tab.count }})</span>
|
<span v-if="tab.loading" class="ml-1">
|
||||||
|
<span class="loading loading-spinner loading-xs" />
|
||||||
|
</span>
|
||||||
|
<span v-else-if="tab.count !== undefined" class="ml-1 opacity-70">({{ tab.count }})</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -185,6 +191,10 @@ const props = defineProps<{
|
|||||||
selectedProduct?: string | null
|
selectedProduct?: string | null
|
||||||
currentTab?: string
|
currentTab?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
loadingProducts?: boolean
|
||||||
|
loadingHubs?: boolean
|
||||||
|
loadingSuppliers?: boolean
|
||||||
|
loadingOffers?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -226,33 +236,45 @@ const entityIcon = computed(() => {
|
|||||||
|
|
||||||
// Available tabs based on entity type and data
|
// Available tabs based on entity type and data
|
||||||
const availableTabs = computed(() => {
|
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') {
|
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({
|
tabs.push({
|
||||||
id: 'offers',
|
id: 'offers',
|
||||||
label: t('catalog.tabs.offers'),
|
label: t('catalog.tabs.offers'),
|
||||||
count: props.selectedProduct
|
count: offersLoading ? undefined : (offersCount || 0),
|
||||||
? props.relatedOffers?.length || 0
|
loading: offersLoading
|
||||||
: props.relatedProducts?.length || 0
|
|
||||||
})
|
})
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'suppliers',
|
id: 'suppliers',
|
||||||
label: t('catalog.tabs.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') {
|
} 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({
|
tabs.push({
|
||||||
id: 'offers',
|
id: 'offers',
|
||||||
label: t('catalog.tabs.offers'),
|
label: t('catalog.tabs.offers'),
|
||||||
count: props.selectedProduct
|
count: offersLoading ? undefined : (offersCount || 0),
|
||||||
? props.relatedOffers?.length || 0
|
loading: offersLoading
|
||||||
: props.relatedProducts?.length || 0
|
|
||||||
})
|
})
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'hubs',
|
id: 'hubs',
|
||||||
label: t('catalog.tabs.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') {
|
} else if (props.entityType === 'offer') {
|
||||||
if (props.relatedProducts && props.relatedProducts.length > 0) {
|
if (props.relatedProducts && props.relatedProducts.length > 0) {
|
||||||
@@ -264,12 +286,15 @@ const availableTabs = computed(() => {
|
|||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'hubs',
|
id: 'hubs',
|
||||||
label: t('catalog.tabs.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) {
|
if (props.relatedSuppliers && props.relatedSuppliers.length > 0) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
id: 'suppliers',
|
id: 'suppliers',
|
||||||
label: t('catalog.tabs.supplier')
|
label: t('catalog.tabs.supplier'),
|
||||||
|
count: props.loadingSuppliers ? undefined : (props.relatedSuppliers?.length || 0),
|
||||||
|
loading: props.loadingSuppliers
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export function useCatalogInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load offers for supplier after product selection
|
// Load offers for supplier after product selection
|
||||||
const loadOffersForSupplier = async (supplierUuid: string, productUuid: string) => {
|
const loadOffersForSupplier = async (_supplierUuid: string, productUuid: string) => {
|
||||||
try {
|
try {
|
||||||
const supplier = entity.value
|
const supplier = entity.value
|
||||||
if (!supplier?.latitude || !supplier?.longitude) {
|
if (!supplier?.latitude || !supplier?.longitude) {
|
||||||
@@ -295,6 +295,10 @@ export function useCatalogInfo() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoadingOffers.value = true
|
||||||
|
isLoadingHubs.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
// Find offers near supplier for this product
|
// Find offers near supplier for this product
|
||||||
const offersData = await execute(
|
const offersData = await execute(
|
||||||
NearestOffersDocument,
|
NearestOffersDocument,
|
||||||
@@ -310,6 +314,7 @@ export function useCatalogInfo() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
relatedOffers.value = offersData?.nearestOffers || []
|
relatedOffers.value = offersData?.nearestOffers || []
|
||||||
|
isLoadingOffers.value = false
|
||||||
|
|
||||||
// Load hubs near each offer and aggregate (limit to 12)
|
// Load hubs near each offer and aggregate (limit to 12)
|
||||||
const allHubs = new Map<string, any>()
|
const allHubs = new Map<string, any>()
|
||||||
@@ -341,6 +346,10 @@ export function useCatalogInfo() {
|
|||||||
if (allHubs.size >= 12) break
|
if (allHubs.size >= 12) break
|
||||||
}
|
}
|
||||||
relatedHubs.value = Array.from(allHubs.values()).slice(0, 12)
|
relatedHubs.value = Array.from(allHubs.values()).slice(0, 12)
|
||||||
|
} finally {
|
||||||
|
isLoadingOffers.value = false
|
||||||
|
isLoadingHubs.value = false
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading offers for supplier:', error)
|
console.error('Error loading offers for supplier:', error)
|
||||||
}
|
}
|
||||||
@@ -396,6 +405,10 @@ export function useCatalogInfo() {
|
|||||||
relatedOffers.value = []
|
relatedOffers.value = []
|
||||||
selectedProduct.value = null
|
selectedProduct.value = null
|
||||||
activeTab.value = 'products'
|
activeTab.value = 'products'
|
||||||
|
isLoadingProducts.value = false
|
||||||
|
isLoadingHubs.value = false
|
||||||
|
isLoadingSuppliers.value = false
|
||||||
|
isLoadingOffers.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -408,6 +421,10 @@ export function useCatalogInfo() {
|
|||||||
selectedProduct,
|
selectedProduct,
|
||||||
activeTab,
|
activeTab,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isLoadingProducts,
|
||||||
|
isLoadingHubs,
|
||||||
|
isLoadingSuppliers,
|
||||||
|
isLoadingOffers,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
loadInfo,
|
loadInfo,
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
:selected-product="infoProduct ?? null"
|
:selected-product="infoProduct ?? null"
|
||||||
:current-tab="infoTab"
|
:current-tab="infoTab"
|
||||||
:loading="infoLoading"
|
:loading="infoLoading"
|
||||||
|
:loading-products="isLoadingProducts"
|
||||||
|
:loading-hubs="isLoadingHubs"
|
||||||
|
:loading-suppliers="isLoadingSuppliers"
|
||||||
|
:loading-offers="isLoadingOffers"
|
||||||
@close="onInfoClose"
|
@close="onInfoClose"
|
||||||
@add-to-filter="onInfoAddToFilter"
|
@add-to-filter="onInfoAddToFilter"
|
||||||
@open-info="onInfoOpenRelated"
|
@open-info="onInfoOpenRelated"
|
||||||
@@ -134,6 +138,10 @@ const {
|
|||||||
relatedOffers,
|
relatedOffers,
|
||||||
selectedProduct,
|
selectedProduct,
|
||||||
isLoading: infoLoading,
|
isLoading: infoLoading,
|
||||||
|
isLoadingProducts,
|
||||||
|
isLoadingHubs,
|
||||||
|
isLoadingSuppliers,
|
||||||
|
isLoadingOffers,
|
||||||
loadInfo,
|
loadInfo,
|
||||||
selectProduct: selectInfoProduct,
|
selectProduct: selectInfoProduct,
|
||||||
clearInfo
|
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(() => {
|
const relatedPoints = computed(() => {
|
||||||
if (!infoId.value) return []
|
if (!infoId.value) return []
|
||||||
|
|
||||||
@@ -265,7 +273,15 @@ const relatedPoints = computed(() => {
|
|||||||
type: 'hub' | 'supplier' | 'offer'
|
type: 'hub' | 'supplier' | 'offer'
|
||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
// Add hubs
|
const currentTab = infoTab.value
|
||||||
|
|
||||||
|
// 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 hubs (for supplier's hubs tab or offer's hubs tab)
|
||||||
|
if (currentTab === 'hubs') {
|
||||||
relatedHubs.value.forEach(hub => {
|
relatedHubs.value.forEach(hub => {
|
||||||
if (hub.latitude && hub.longitude) {
|
if (hub.latitude && hub.longitude) {
|
||||||
points.push({
|
points.push({
|
||||||
@@ -277,8 +293,10 @@ const relatedPoints = computed(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Add suppliers
|
// Add suppliers (for hub's suppliers tab or offer's suppliers tab)
|
||||||
|
if (currentTab === 'suppliers') {
|
||||||
relatedSuppliers.value.forEach(supplier => {
|
relatedSuppliers.value.forEach(supplier => {
|
||||||
if (supplier.latitude && supplier.longitude) {
|
if (supplier.latitude && supplier.longitude) {
|
||||||
points.push({
|
points.push({
|
||||||
@@ -290,8 +308,10 @@ const relatedPoints = computed(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Add offers
|
// Add offers (for hub's/supplier's offers tab when product is selected)
|
||||||
|
if (currentTab === 'offers' && infoProduct.value) {
|
||||||
relatedOffers.value.forEach(offer => {
|
relatedOffers.value.forEach(offer => {
|
||||||
if (offer.latitude && offer.longitude) {
|
if (offer.latitude && offer.longitude) {
|
||||||
points.push({
|
points.push({
|
||||||
@@ -303,6 +323,7 @@ const relatedPoints = computed(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return points
|
return points
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user