Add URL params for InfoPanel tab and product (infoTab, infoProduct)
All checks were successful
Build Docker Image / build (push) Successful in 3m39s

This commit is contained in:
Ruslan Bakiev
2026-01-26 15:55:25 +07:00
parent 8354102895
commit f973784257
3 changed files with 66 additions and 24 deletions

View File

@@ -41,8 +41,8 @@
: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': currentTab === tab.id }" :class="{ 'tab-active !text-white !bg-white/20': activeTab === tab.id }"
@click="currentTab = tab.id" @click="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.count !== undefined" class="ml-1 opacity-70">({{ tab.count }})</span>
@@ -52,7 +52,7 @@
<!-- Tab content --> <!-- Tab content -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<!-- Products tab (only for Offer entity) --> <!-- Products tab (only for Offer entity) -->
<div v-if="currentTab === 'products' && entityType === 'offer'"> <div v-if="activeTab === 'products' && entityType === 'offer'">
<div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60"> <div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60">
<Icon name="lucide:package" size="32" class="mb-2 opacity-50" /> <Icon name="lucide:package" size="32" class="mb-2 opacity-50" />
<p>{{ $t('catalog.empty.noProducts') }}</p> <p>{{ $t('catalog.empty.noProducts') }}</p>
@@ -70,7 +70,7 @@
</div> </div>
<!-- Hubs tab --> <!-- Hubs tab -->
<div v-if="currentTab === 'hubs'"> <div v-if="activeTab === 'hubs'">
<div v-if="relatedHubs.length === 0" class="text-center py-8 text-white/60"> <div v-if="relatedHubs.length === 0" class="text-center py-8 text-white/60">
<Icon name="lucide:warehouse" size="32" class="mb-2 opacity-50" /> <Icon name="lucide:warehouse" size="32" class="mb-2 opacity-50" />
<p>{{ $t('catalog.info.noHubs') }}</p> <p>{{ $t('catalog.info.noHubs') }}</p>
@@ -87,7 +87,7 @@
</div> </div>
<!-- Suppliers tab --> <!-- Suppliers tab -->
<div v-if="currentTab === 'suppliers'"> <div v-if="activeTab === 'suppliers'">
<div v-if="relatedSuppliers.length === 0" class="text-center py-8 text-white/60"> <div v-if="relatedSuppliers.length === 0" class="text-center py-8 text-white/60">
<Icon name="lucide:factory" size="32" class="mb-2 opacity-50" /> <Icon name="lucide:factory" size="32" class="mb-2 opacity-50" />
<p>{{ $t('catalog.info.noSuppliers') }}</p> <p>{{ $t('catalog.info.noSuppliers') }}</p>
@@ -104,7 +104,7 @@
</div> </div>
<!-- Offers tab (two-step for Hub/Supplier) --> <!-- Offers tab (two-step for Hub/Supplier) -->
<div v-if="currentTab === 'offers'"> <div v-if="activeTab === 'offers'">
<!-- Step 1: Select product (for Hub/Supplier) --> <!-- Step 1: Select product (for Hub/Supplier) -->
<div v-if="!selectedProduct && (entityType === 'hub' || entityType === 'supplier')"> <div v-if="!selectedProduct && (entityType === 'hub' || entityType === 'supplier')">
<div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60"> <div v-if="relatedProducts.length === 0" class="text-center py-8 text-white/60">
@@ -183,6 +183,7 @@ const props = defineProps<{
relatedSuppliers?: any[] relatedSuppliers?: any[]
relatedOffers?: any[] relatedOffers?: any[]
selectedProduct?: string | null selectedProduct?: string | null
currentTab?: string
loading?: boolean loading?: boolean
}>() }>()
@@ -191,6 +192,7 @@ const emit = defineEmits<{
'add-to-filter': [] 'add-to-filter': []
'open-info': [type: InfoEntityType, uuid: string] 'open-info': [type: InfoEntityType, uuid: string]
'select-product': [uuid: string | null] 'select-product': [uuid: string | null]
'update:current-tab': [tab: string]
}>() }>()
const { t } = useI18n() const { t } = useI18n()
@@ -202,9 +204,6 @@ const relatedHubs = computed(() => props.relatedHubs ?? [])
const relatedSuppliers = computed(() => props.relatedSuppliers ?? []) const relatedSuppliers = computed(() => props.relatedSuppliers ?? [])
const relatedOffers = computed(() => props.relatedOffers ?? []) const relatedOffers = computed(() => props.relatedOffers ?? [])
// Current active tab
const currentTab = ref<string>('offers')
// Entity name // Entity name
const entityName = computed(() => { const entityName = computed(() => {
return props.entity?.name || props.entity?.productName || props.entityId.slice(0, 8) + '...' return props.entity?.name || props.entity?.productName || props.entityId.slice(0, 8) + '...'
@@ -278,17 +277,20 @@ const availableTabs = computed(() => {
return tabs return tabs
}) })
// Set default active tab when entity type changes // Active tab - use prop or default to first available
watch( const activeTab = computed(() => {
() => props.entityType, // If prop is set and is a valid tab, use it
() => { if (props.currentTab && availableTabs.value.some(t => t.id === props.currentTab)) {
const firstTab = availableTabs.value[0] return props.currentTab
if (firstTab) { }
currentTab.value = firstTab.id // Default to first tab
} return availableTabs.value[0]?.id || 'offers'
}, })
{ immediate: true }
) // Handler for tab click
const onTabClick = (tabId: string) => {
emit('update:current-tab', tabId)
}
// Handlers for selecting related items // Handlers for selecting related items
const onProductSelect = (product: any) => { const onProductSelect = (product: any) => {

View File

@@ -73,6 +73,12 @@ export function useCatalogSearch() {
return null return null
}) })
// Info panel tab (stored in URL for sharing)
const infoTab = computed(() => route.query.infoTab as string | undefined)
// Info panel selected product (stored in URL for sharing)
const infoProduct = computed(() => route.query.infoProduct as string | undefined)
const productId = computed(() => route.query.product as string | undefined) const productId = computed(() => route.query.product as string | undefined)
const supplierId = computed(() => route.query.supplier as string | undefined) const supplierId = computed(() => route.query.supplier as string | undefined)
const hubId = computed(() => route.query.hub as string | undefined) const hubId = computed(() => route.query.hub as string | undefined)
@@ -232,11 +238,19 @@ export function useCatalogSearch() {
} }
const openInfo = (type: InfoEntityType, uuid: string) => { const openInfo = (type: InfoEntityType, uuid: string) => {
updateQuery({ info: `${type}:${uuid}`, select: null }) updateQuery({ info: `${type}:${uuid}`, select: null, infoTab: null, infoProduct: null })
} }
const closeInfo = () => { const closeInfo = () => {
updateQuery({ info: null }) updateQuery({ info: null, infoTab: null, infoProduct: null })
}
const setInfoTab = (tab: string) => {
updateQuery({ infoTab: tab })
}
const setInfoProduct = (productUuid: string | null) => {
updateQuery({ infoProduct: productUuid })
} }
const clearAll = () => { const clearAll = () => {
@@ -326,6 +340,8 @@ export function useCatalogSearch() {
// State // State
selectMode, selectMode,
infoId, infoId,
infoTab,
infoProduct,
displayMode, displayMode,
catalogMode, catalogMode,
productId, productId,
@@ -359,6 +375,8 @@ export function useCatalogSearch() {
setQuantity, setQuantity,
openInfo, openInfo,
closeInfo, closeInfo,
setInfoTab,
setInfoProduct,
clearAll, clearAll,
setLabel, setLabel,
setMapViewMode, setMapViewMode,

View File

@@ -43,12 +43,14 @@
:related-hubs="relatedHubs" :related-hubs="relatedHubs"
:related-suppliers="relatedSuppliers" :related-suppliers="relatedSuppliers"
:related-offers="relatedOffers" :related-offers="relatedOffers"
:selected-product="selectedProduct" :selected-product="infoProduct ?? null"
:current-tab="infoTab"
:loading="infoLoading" :loading="infoLoading"
@close="onInfoClose" @close="onInfoClose"
@add-to-filter="onInfoAddToFilter" @add-to-filter="onInfoAddToFilter"
@open-info="onInfoOpenRelated" @open-info="onInfoOpenRelated"
@select-product="(uuid: string | null) => uuid && selectInfoProduct(uuid)" @select-product="onInfoSelectProduct"
@update:current-tab="setInfoTab"
/> />
<!-- Quote results: show offers after search --> <!-- Quote results: show offers after search -->
@@ -105,6 +107,8 @@ const {
catalogMode, catalogMode,
selectMode, selectMode,
infoId, infoId,
infoTab,
infoProduct,
productId, productId,
supplierId, supplierId,
hubId, hubId,
@@ -116,6 +120,8 @@ const {
cancelSelect, cancelSelect,
openInfo, openInfo,
closeInfo, closeInfo,
setInfoTab,
setInfoProduct,
setLabel setLabel
} = useCatalogSearch() } = useCatalogSearch()
@@ -231,11 +237,22 @@ watch([filterByBounds, currentMapBounds], ([enabled, bounds]) => {
watch(infoId, async (info) => { watch(infoId, async (info) => {
if (info) { if (info) {
await loadInfo(info.type, info.uuid) await loadInfo(info.type, info.uuid)
// If URL has infoProduct, load offers for it
if (infoProduct.value) {
await selectInfoProduct(infoProduct.value)
}
} else { } else {
clearInfo() clearInfo()
} }
}, { immediate: true }) }, { immediate: true })
// Watch infoProduct URL param to load offers when it changes
watch(infoProduct, async (productUuid) => {
if (productUuid && infoId.value) {
await selectInfoProduct(productUuid)
}
})
// Related points for Info mode (shown on map) // Related points for Info mode (shown on map)
const relatedPoints = computed(() => { const relatedPoints = computed(() => {
if (!infoId.value) return [] if (!infoId.value) return []
@@ -418,6 +435,11 @@ const onInfoOpenRelated = (type: 'hub' | 'supplier' | 'offer', uuid: string) =>
openInfo(type, uuid) openInfo(type, uuid)
} }
// Handle product selection in InfoPanel - update URL which triggers loading
const onInfoSelectProduct = (uuid: string | null) => {
setInfoProduct(uuid)
}
// Search for offers // Search for offers
const onSearch = async () => { const onSearch = async () => {
if (!canSearch.value) return if (!canSearch.value) return