Make pins explicit and selection open info
All checks were successful
Build Docker Image / build (push) Successful in 5m24s

This commit is contained in:
Ruslan Bakiev
2026-02-07 13:56:36 +07:00
parent 2d54dc3283
commit a73a801a1d
4 changed files with 72 additions and 25 deletions

View File

@@ -12,9 +12,20 @@
</div>
<h3 class="font-semibold text-base text-white">{{ entityName }}</h3>
</div>
<button class="btn btn-ghost btn-xs btn-circle text-white/60 hover:text-white" @click="emit('close')">
<Icon name="lucide:x" size="16" />
</button>
<div class="flex items-center gap-2">
<button
v-if="(entityType === 'hub' || entityType === 'supplier') && entity?.uuid"
class="rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click="emit('pin', entityType, { uuid: entity?.uuid, name: entity?.name })"
aria-label="Pin"
title="Pin"
>
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
<button class="btn btn-ghost btn-xs btn-circle text-white/60 hover:text-white" @click="emit('close')">
<Icon name="lucide:x" size="16" />
</button>
</div>
</div>
</div>
@@ -123,11 +134,12 @@
@select="onProductSelect(product)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'product', product)"
aria-label="Pin product"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</div>
@@ -197,11 +209,12 @@
@select="onSupplierSelect(supplier)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'supplier', supplier)"
aria-label="Pin supplier"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</div>
@@ -242,11 +255,12 @@
@select="onHubSelect(hub)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'hub', hub)"
aria-label="Pin hub"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</div>
@@ -274,11 +288,12 @@
@select="onHubSelect(hub)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'hub', hub)"
aria-label="Pin hub"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</div>

View File

@@ -38,11 +38,12 @@
@select="onSelect(item)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'product', item)"
aria-label="Pin product"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</template>
@@ -62,11 +63,12 @@
@select="onSelect(item)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'hub', item)"
aria-label="Pin hub"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</template>
@@ -86,11 +88,12 @@
@select="onSelect(item)"
/>
<button
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-white/10 hover:bg-white/20 p-1"
class="absolute -top-2 -right-2 rounded-full glass-bright border border-white/30 shadow-lg p-1.5 transition-transform hover:scale-105"
@click.stop="emit('pin', 'supplier', item)"
aria-label="Pin supplier"
title="Pin"
>
<Icon name="lucide:pin" size="14" class="text-white/80" />
<Icon name="lucide:pin" size="16" class="text-white" />
</button>
</div>
</template>

View File

@@ -94,7 +94,8 @@ export function useCatalogSearch() {
})
// Filter by bounds checkbox state from URL
const filterByBounds = computed(() => route.query.bounds !== undefined)
// Use explicit flag so bounds don't auto-enable filtering.
const filterByBounds = computed(() => route.query.boundsFilter === '1')
// Get label for a filter (from cache or fallback to ID)
const getLabel = (type: string, id: string | undefined): string | null => {
@@ -261,7 +262,7 @@ export function useCatalogSearch() {
const setBoundsInUrl = (bounds: { west: number; south: number; east: number; north: number } | null) => {
if (bounds) {
const boundsStr = `${bounds.west.toFixed(4)},${bounds.south.toFixed(4)},${bounds.east.toFixed(4)},${bounds.north.toFixed(4)}`
updateQuery({ bounds: boundsStr })
updateQuery({ bounds: boundsStr, boundsFilter: '1' })
} else {
updateQuery({ bounds: null })
}
@@ -269,7 +270,12 @@ export function useCatalogSearch() {
// Clear bounds from URL
const clearBoundsFromUrl = () => {
updateQuery({ bounds: null })
updateQuery({ bounds: null, boundsFilter: null })
}
// Explicitly enable/disable bounds filter flag in URL
const setBoundsFilterEnabled = (enabled: boolean) => {
updateQuery({ boundsFilter: enabled ? '1' : null })
}
const openInfo = (type: InfoEntityType, uuid: string) => {
@@ -427,6 +433,7 @@ export function useCatalogSearch() {
setQuantity,
setBoundsInUrl,
clearBoundsFromUrl,
setBoundsFilterEnabled,
openInfo,
closeInfo,
setInfoTab,

View File

@@ -25,7 +25,7 @@
:cluster-supplier-uuid="clusterSupplierUuid"
@select="onMapSelect"
@bounds-change="onBoundsChange"
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
@update:filter-by-bounds="onToggleBoundsFilter"
>
<!-- Panel slot - shows selection list OR info OR quote results -->
<template #panel>
@@ -175,7 +175,8 @@ const {
urlBounds,
filterByBounds,
setBoundsInUrl,
clearBoundsFromUrl
clearBoundsFromUrl,
setBoundsFilterEnabled
} = useCatalogSearch()
// Info panel composable
@@ -255,7 +256,20 @@ const getSelectionBounds = () => {
return { west: bounds.west, south: bounds.south, east: bounds.east, north: bounds.north }
}
const onToggleBoundsFilter = (enabled: boolean) => {
if (enabled) {
setBoundsFilterEnabled(true)
const bounds = getSelectionBounds()
if (bounds) {
setBoundsInUrl(bounds)
}
} else {
clearBoundsFromUrl()
}
}
const applySelectionBounds = () => {
if (!filterByBounds.value) return
if (!selectionBoundsBackup.value) {
selectionBoundsBackup.value = {
hadBounds: !!urlBounds.value,
@@ -651,10 +665,18 @@ const onMapSelect = async (item: MapSelectItem) => {
setLabel(infoType, itemId, itemName)
}
// Handle selection from SelectionPanel - add to filter (show badge in search)
// Handle selection from SelectionPanel - open info card (pin only via pin button)
const onSelectItem = (type: string, item: { uuid?: string | null; name?: string | null }) => {
if (item.uuid && item.name) {
selectItem(type, item.uuid, item.name)
if (!item.uuid) return
if (type === 'hub' || type === 'supplier') {
if (item.name) {
setLabel(type, item.uuid, item.name)
}
openInfo(type, item.uuid)
return
}
if (type === 'product') {
router.push(localePath(`/catalog/products/${item.uuid}`))
}
}