Make pins explicit and selection open info
All checks were successful
Build Docker Image / build (push) Successful in 5m24s
All checks were successful
Build Docker Image / build (push) Successful in 5m24s
This commit is contained in:
@@ -12,11 +12,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="font-semibold text-base text-white">{{ entityName }}</h3>
|
<h3 class="font-semibold text-base text-white">{{ entityName }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
<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')">
|
<button class="btn btn-ghost btn-xs btn-circle text-white/60 hover:text-white" @click="emit('close')">
|
||||||
<Icon name="lucide:x" size="16" />
|
<Icon name="lucide:x" size="16" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Content (scrollable) -->
|
<!-- Content (scrollable) -->
|
||||||
<div class="flex-1 overflow-y-auto p-4">
|
<div class="flex-1 overflow-y-auto p-4">
|
||||||
@@ -123,11 +134,12 @@
|
|||||||
@select="onProductSelect(product)"
|
@select="onProductSelect(product)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'product', product)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,11 +209,12 @@
|
|||||||
@select="onSupplierSelect(supplier)"
|
@select="onSupplierSelect(supplier)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'supplier', supplier)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -242,11 +255,12 @@
|
|||||||
@select="onHubSelect(hub)"
|
@select="onHubSelect(hub)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'hub', hub)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -274,11 +288,12 @@
|
|||||||
@select="onHubSelect(hub)"
|
@select="onHubSelect(hub)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'hub', hub)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,11 +38,12 @@
|
|||||||
@select="onSelect(item)"
|
@select="onSelect(item)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'product', item)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -62,11 +63,12 @@
|
|||||||
@select="onSelect(item)"
|
@select="onSelect(item)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'hub', item)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -86,11 +88,12 @@
|
|||||||
@select="onSelect(item)"
|
@select="onSelect(item)"
|
||||||
/>
|
/>
|
||||||
<button
|
<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)"
|
@click.stop="emit('pin', 'supplier', item)"
|
||||||
aria-label="Pin 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ export function useCatalogSearch() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Filter by bounds checkbox state from URL
|
// 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)
|
// Get label for a filter (from cache or fallback to ID)
|
||||||
const getLabel = (type: string, id: string | undefined): string | null => {
|
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) => {
|
const setBoundsInUrl = (bounds: { west: number; south: number; east: number; north: number } | null) => {
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
const boundsStr = `${bounds.west.toFixed(4)},${bounds.south.toFixed(4)},${bounds.east.toFixed(4)},${bounds.north.toFixed(4)}`
|
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 {
|
} else {
|
||||||
updateQuery({ bounds: null })
|
updateQuery({ bounds: null })
|
||||||
}
|
}
|
||||||
@@ -269,7 +270,12 @@ export function useCatalogSearch() {
|
|||||||
|
|
||||||
// Clear bounds from URL
|
// Clear bounds from URL
|
||||||
const clearBoundsFromUrl = () => {
|
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) => {
|
const openInfo = (type: InfoEntityType, uuid: string) => {
|
||||||
@@ -427,6 +433,7 @@ export function useCatalogSearch() {
|
|||||||
setQuantity,
|
setQuantity,
|
||||||
setBoundsInUrl,
|
setBoundsInUrl,
|
||||||
clearBoundsFromUrl,
|
clearBoundsFromUrl,
|
||||||
|
setBoundsFilterEnabled,
|
||||||
openInfo,
|
openInfo,
|
||||||
closeInfo,
|
closeInfo,
|
||||||
setInfoTab,
|
setInfoTab,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
:cluster-supplier-uuid="clusterSupplierUuid"
|
:cluster-supplier-uuid="clusterSupplierUuid"
|
||||||
@select="onMapSelect"
|
@select="onMapSelect"
|
||||||
@bounds-change="onBoundsChange"
|
@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 -->
|
<!-- Panel slot - shows selection list OR info OR quote results -->
|
||||||
<template #panel>
|
<template #panel>
|
||||||
@@ -175,7 +175,8 @@ const {
|
|||||||
urlBounds,
|
urlBounds,
|
||||||
filterByBounds,
|
filterByBounds,
|
||||||
setBoundsInUrl,
|
setBoundsInUrl,
|
||||||
clearBoundsFromUrl
|
clearBoundsFromUrl,
|
||||||
|
setBoundsFilterEnabled
|
||||||
} = useCatalogSearch()
|
} = useCatalogSearch()
|
||||||
|
|
||||||
// Info panel composable
|
// Info panel composable
|
||||||
@@ -255,7 +256,20 @@ const getSelectionBounds = () => {
|
|||||||
return { west: bounds.west, south: bounds.south, east: bounds.east, north: bounds.north }
|
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 = () => {
|
const applySelectionBounds = () => {
|
||||||
|
if (!filterByBounds.value) return
|
||||||
if (!selectionBoundsBackup.value) {
|
if (!selectionBoundsBackup.value) {
|
||||||
selectionBoundsBackup.value = {
|
selectionBoundsBackup.value = {
|
||||||
hadBounds: !!urlBounds.value,
|
hadBounds: !!urlBounds.value,
|
||||||
@@ -651,10 +665,18 @@ const onMapSelect = async (item: MapSelectItem) => {
|
|||||||
setLabel(infoType, itemId, itemName)
|
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 }) => {
|
const onSelectItem = (type: string, item: { uuid?: string | null; name?: string | null }) => {
|
||||||
if (item.uuid && item.name) {
|
if (!item.uuid) return
|
||||||
selectItem(type, item.uuid, item.name)
|
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}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user