feat(collections): filters, add-place popup, category badges, map-click hardening (#1081)

- Map: markers no longer rebuild on every unrelated re-render (memoised the
  mappable list + only update the hero size when it really changes), the floating
  controls bar is click-through except its buttons, and the collection map runs
  with the hover tooltip off. Together these stop a marker click from landing on
  a mid-rebuild element / the tooltip so the pick actually registers.
- Filters moved out of the hero into a compact status + category dropdown row
  above the places (custom dropdowns); the hero no longer carries the stat chips.
- Add-place is a single popup now: search fills the location, and name / status /
  category / description / links are all editable together before saving.
- Category shown as a badge top-left on the detail cover and next to the status
  in each list row (divided by a hairline).
- Slimmer hero: shorter, tighter spacing, links tucked into the eyebrow row
  instead of their own line.
This commit is contained in:
Maurice
2026-07-01 13:37:25 +02:00
committed by Maurice
parent f97e284592
commit 0c484959ce
14 changed files with 371 additions and 191 deletions
+4 -2
View File
@@ -412,6 +412,7 @@ export const MapView = memo(function MapView({
route = null,
routeSegments = [],
selectedPlaceId = null,
hoverDisabled = false,
onMarkerClick,
onMapClick,
onMapContextMenu = null,
@@ -464,9 +465,10 @@ export const MapView = memo(function MapView({
const [tooltipPos, setTooltipPos] = useState<{ x: number; y: number } | null>(null)
const handleMarkerHover = useCallback((place: any, x: number, y: number) => {
if (hoverDisabled) return
setHoveredPlace(place)
setTooltipPos({ x, y })
}, [])
}, [hoverDisabled])
const handleMarkerHoverOut = useCallback(() => {
setHoveredPlace(null)
@@ -601,7 +603,7 @@ export const MapView = memo(function MapView({
} catch { return [] }
}), [places])
const TooltipOverlay = hoveredPlace && tooltipPos && !isTouchDevice
const TooltipOverlay = !hoverDisabled && hoveredPlace && tooltipPos && !isTouchDevice
const CatIcon = TooltipOverlay ? getCategoryIcon(hoveredPlace.category_icon) : null
const { position: userPosition, mode: trackingMode, error: trackingError, cycleMode: cycleTrackingMode } = useGeolocation()
+8 -1
View File
@@ -68,6 +68,7 @@ interface Props {
routeSegments?: RouteSegment[]
selectedPlaceId?: number | null
onMarkerClick?: (id: number) => void
hoverDisabled?: boolean
onMapClick?: (info: { latlng: { lat: number; lng: number } }) => void
onMapContextMenu?: ((e: { latlng: { lat: number; lng: number }; originalEvent: MouseEvent }) => void) | null
center?: [number, number]
@@ -184,6 +185,7 @@ export function MapViewGL({
route = null,
routeSegments = [],
selectedPlaceId = null,
hoverDisabled = false,
onMarkerClick,
onMapClick,
onMapContextMenu = null,
@@ -264,6 +266,8 @@ export function MapViewGL({
onClickRefs.current.marker = onMarkerClick
onClickRefs.current.map = onMapClick
onClickRefs.current.context = onMapContextMenu
const hoverDisabledRef = useRef(hoverDisabled)
hoverDisabledRef.current = hoverDisabled
// Build/rebuild the map on provider/style/token/3d change
useEffect(() => {
@@ -649,14 +653,17 @@ export function MapViewGL({
onClickRefs.current.marker?.(place.id)
})
el.addEventListener('mouseenter', (ev) => {
if (hoverDisabledRef.current) return
hoverIdRef.current = place.id
setHoverPlace(place as Place & { category_color?: string; category_icon?: string; category_name?: string })
setHoverPos({ x: (ev as MouseEvent).clientX, y: (ev as MouseEvent).clientY })
})
el.addEventListener('mousemove', (ev) => {
if (hoverDisabledRef.current) return
setHoverPos({ x: (ev as MouseEvent).clientX, y: (ev as MouseEvent).clientY })
})
el.addEventListener('mouseleave', () => {
if (hoverDisabledRef.current) return
hoverIdRef.current = null
setHoverPlace(null)
setHoverPos(null)
@@ -946,7 +953,7 @@ export function MapViewGL({
)}
{/* Hover tooltip — cursor-following name/category/address card, identical to
the Leaflet map's overlay (no anchored popup, no photo). */}
{hoverPlace && hoverPos && !isMobile && (
{!hoverDisabled && hoverPlace && hoverPos && !isMobile && (
<div data-testid="tooltip" style={{
position: 'fixed',
left: hoverPos.x + 14,