feat(catalog): persist bounds filter state in URL
All checks were successful
Build Docker Image / build (push) Successful in 4m14s
All checks were successful
Build Docker Image / build (push) Successful in 4m14s
- Add urlBounds and filterByBounds computed from URL query - Add setBoundsInUrl and clearBoundsFromUrl actions - Update index.vue to use URL-based bounds state - Bounds written to URL as comma-separated values (west,south,east,north) This enables sharing links with map viewport bounds filter.
This commit is contained in:
@@ -84,6 +84,18 @@ export function useCatalogSearch() {
|
|||||||
const hubId = computed(() => route.query.hub as string | undefined)
|
const hubId = computed(() => route.query.hub as string | undefined)
|
||||||
const quantity = computed(() => route.query.qty as string | undefined)
|
const quantity = computed(() => route.query.qty as string | undefined)
|
||||||
|
|
||||||
|
// Map bounds from URL (format: west,south,east,north)
|
||||||
|
const urlBounds = computed(() => {
|
||||||
|
const b = route.query.bounds as string | undefined
|
||||||
|
if (!b) return null
|
||||||
|
const parts = b.split(',').map(Number)
|
||||||
|
if (parts.length !== 4 || parts.some(isNaN)) return null
|
||||||
|
return { west: parts[0], south: parts[1], east: parts[2], north: parts[3] }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter by bounds checkbox state from URL
|
||||||
|
const filterByBounds = computed(() => route.query.bounds !== undefined)
|
||||||
|
|
||||||
// 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 => {
|
||||||
if (!id) return null
|
if (!id) return null
|
||||||
@@ -237,6 +249,21 @@ export function useCatalogSearch() {
|
|||||||
updateQuery({ qty })
|
updateQuery({ qty })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set map bounds in URL (for filter by map feature)
|
||||||
|
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 })
|
||||||
|
} else {
|
||||||
|
updateQuery({ bounds: null })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear bounds from URL
|
||||||
|
const clearBoundsFromUrl = () => {
|
||||||
|
updateQuery({ bounds: null })
|
||||||
|
}
|
||||||
|
|
||||||
const openInfo = (type: InfoEntityType, uuid: string) => {
|
const openInfo = (type: InfoEntityType, uuid: string) => {
|
||||||
updateQuery({ info: `${type}:${uuid}`, select: null, infoTab: null, infoProduct: null })
|
updateQuery({ info: `${type}:${uuid}`, select: null, infoTab: null, infoProduct: null })
|
||||||
}
|
}
|
||||||
@@ -350,6 +377,8 @@ export function useCatalogSearch() {
|
|||||||
quantity,
|
quantity,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
mapViewMode,
|
mapViewMode,
|
||||||
|
urlBounds,
|
||||||
|
filterByBounds,
|
||||||
|
|
||||||
// Drawer state
|
// Drawer state
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
@@ -373,6 +402,8 @@ export function useCatalogSearch() {
|
|||||||
removeFilter,
|
removeFilter,
|
||||||
editFilter,
|
editFilter,
|
||||||
setQuantity,
|
setQuantity,
|
||||||
|
setBoundsInUrl,
|
||||||
|
clearBoundsFromUrl,
|
||||||
openInfo,
|
openInfo,
|
||||||
closeInfo,
|
closeInfo,
|
||||||
setInfoTab,
|
setInfoTab,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
:related-points="relatedPoints"
|
:related-points="relatedPoints"
|
||||||
@select="onMapSelect"
|
@select="onMapSelect"
|
||||||
@bounds-change="onBoundsChange"
|
@bounds-change="onBoundsChange"
|
||||||
@update:filter-by-bounds="filterByBounds = $event"
|
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
|
||||||
>
|
>
|
||||||
<!-- Panel slot - shows selection list OR info OR quote results -->
|
<!-- Panel slot - shows selection list OR info OR quote results -->
|
||||||
<template #panel>
|
<template #panel>
|
||||||
@@ -82,8 +82,7 @@ const localePath = useLocalePath()
|
|||||||
// Ref to CatalogPage for accessing bounds
|
// Ref to CatalogPage for accessing bounds
|
||||||
const catalogPageRef = ref<{ currentBounds: Ref<MapBounds | null> } | null>(null)
|
const catalogPageRef = ref<{ currentBounds: Ref<MapBounds | null> } | null>(null)
|
||||||
|
|
||||||
// Filter by map bounds state
|
// Current map bounds (local state, updated when map moves)
|
||||||
const filterByBounds = ref(false)
|
|
||||||
const currentMapBounds = ref<MapBounds | null>(null)
|
const currentMapBounds = ref<MapBounds | null>(null)
|
||||||
|
|
||||||
// Hovered item for map highlight
|
// Hovered item for map highlight
|
||||||
@@ -103,6 +102,10 @@ const currentSelectionItems = computed(() => {
|
|||||||
// Handle bounds change from map
|
// Handle bounds change from map
|
||||||
const onBoundsChange = (bounds: MapBounds) => {
|
const onBoundsChange = (bounds: MapBounds) => {
|
||||||
currentMapBounds.value = bounds
|
currentMapBounds.value = bounds
|
||||||
|
// If filter by bounds is enabled, write to URL
|
||||||
|
if (filterByBounds.value) {
|
||||||
|
setBoundsInUrl(bounds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -122,7 +125,11 @@ const {
|
|||||||
openInfo,
|
openInfo,
|
||||||
closeInfo,
|
closeInfo,
|
||||||
setInfoProduct,
|
setInfoProduct,
|
||||||
setLabel
|
setLabel,
|
||||||
|
urlBounds,
|
||||||
|
filterByBounds,
|
||||||
|
setBoundsInUrl,
|
||||||
|
clearBoundsFromUrl
|
||||||
} = useCatalogSearch()
|
} = useCatalogSearch()
|
||||||
|
|
||||||
// Info panel composable
|
// Info panel composable
|
||||||
@@ -230,12 +237,15 @@ watch(productId, (newProductId) => {
|
|||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// Apply bounds filter when "filter by map bounds" is enabled
|
// Apply bounds filter when "filter by map bounds" is enabled
|
||||||
watch([filterByBounds, currentMapBounds], ([enabled, bounds]) => {
|
// Use URL bounds if available, otherwise use current map bounds
|
||||||
|
watch([filterByBounds, urlBounds, currentMapBounds], ([enabled, urlB, mapB]) => {
|
||||||
|
// Use URL bounds if available, otherwise current map bounds
|
||||||
|
const bounds = urlB || mapB
|
||||||
const boundsToApply = enabled && bounds ? bounds : null
|
const boundsToApply = enabled && bounds ? bounds : null
|
||||||
setHubBoundsFilter(boundsToApply)
|
setHubBoundsFilter(boundsToApply)
|
||||||
setSupplierBoundsFilter(boundsToApply)
|
setSupplierBoundsFilter(boundsToApply)
|
||||||
setProductBoundsFilter(boundsToApply)
|
setProductBoundsFilter(boundsToApply)
|
||||||
})
|
}, { immediate: true })
|
||||||
|
|
||||||
// Watch infoId to load info data
|
// Watch infoId to load info data
|
||||||
watch(infoId, async (info) => {
|
watch(infoId, async (info) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user