From aad4d2af7c229b5fdb4c5ad7d1dcc0cb896fe9dd Mon Sep 17 00:00:00 2001 From: Kenneth Brewer Date: Wed, 29 Apr 2026 02:13:43 -0400 Subject: [PATCH] Addressed PR comments about the textarea auto growing, the in flight notes changes asking for confirmation, and updated the hue sort to sort by lightness and then hue so that grayscale values will not collide with red --- .../inertia/components/maps/MapComponent.tsx | 37 ++++++-- .../components/maps/MapMarkerFormPopup.tsx | 37 +++++++- admin/inertia/components/maps/MarkerPanel.tsx | 30 +++++-- .../components/maps/ViewMapMarkerPopup.tsx | 84 ++++++++++--------- admin/inertia/css/app.css | 47 +++++++++++ 5 files changed, 179 insertions(+), 56 deletions(-) diff --git a/admin/inertia/components/maps/MapComponent.tsx b/admin/inertia/components/maps/MapComponent.tsx index 1fe5208..0580e3c 100644 --- a/admin/inertia/components/maps/MapComponent.tsx +++ b/admin/inertia/components/maps/MapComponent.tsx @@ -27,11 +27,18 @@ export default function MapComponent() { const [placingMarker, setPlacingMarker] = useState<{ lng: number; lat: number } | null>(null) const [selectedMarkerId, setSelectedMarkerId] = useState(null) const [editingMarkerId, setEditingMarkerId] = useState(null) + const [hasUnsavedMarkerChanges, setHasUnsavedMarkerChanges] = useState(false) const [scaleUnit, setScaleUnit] = useState( () => (localStorage.getItem('nomad:map-scale-unit') as ScaleUnit) || 'metric' ) + const confirmDiscardMarkerChanges = useCallback(() => { + if (!hasUnsavedMarkerChanges) return true + + return window.confirm('Discard unsaved marker changes?') + }, [hasUnsavedMarkerChanges]) + useEffect(() => { const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) @@ -46,11 +53,17 @@ export default function MapComponent() { localStorage.setItem('nomad:map-scale-unit', unit) }, []) - const handleMapClick = useCallback((e: MapLayerMouseEvent) => { - setPlacingMarker({ lng: e.lngLat.lng, lat: e.lngLat.lat }) - setSelectedMarkerId(null) - setEditingMarkerId(null) - }, []) + const handleMapClick = useCallback( + (e: MapLayerMouseEvent) => { + if (!confirmDiscardMarkerChanges()) return + + setPlacingMarker({ lng: e.lngLat.lng, lat: e.lngLat.lat }) + setSelectedMarkerId(null) + setEditingMarkerId(null) + setHasUnsavedMarkerChanges(false) + }, + [confirmDiscardMarkerChanges] + ) const handleFlyTo = useCallback((longitude: number, latitude: number) => { mapRef.current?.flyTo({ center: [longitude, latitude], zoom: 12, duration: 1500 }) @@ -104,9 +117,13 @@ export default function MapComponent() { anchor="bottom" onClick={(e) => { e.originalEvent.stopPropagation() + + if (!confirmDiscardMarkerChanges()) return + setSelectedMarkerId(marker.id === selectedMarkerId ? null : marker.id) setPlacingMarker(null) setEditingMarkerId(null) + setHasUnsavedMarkerChanges(false) }} > setPlacingMarker(null)} + onCancel={() => { + if (!confirmDiscardMarkerChanges()) return + + setPlacingMarker(null) + setEditingMarkerId(null) + setHasUnsavedMarkerChanges(false) + }} /> )} @@ -141,6 +164,7 @@ export default function MapComponent() { longitude={selectedMarker.longitude} latitude={selectedMarker.latitude} initialMarker={selectedMarker} + onDirtyChange={setHasUnsavedMarkerChanges} onSave={async ({ id, name, notes, color }) => { if (!id) return @@ -151,6 +175,7 @@ export default function MapComponent() { }) setEditingMarkerId(null) + setHasUnsavedMarkerChanges(false) }} onCancel={() => setEditingMarkerId(null)} /> diff --git a/admin/inertia/components/maps/MapMarkerFormPopup.tsx b/admin/inertia/components/maps/MapMarkerFormPopup.tsx index b5bd839..190e6e7 100644 --- a/admin/inertia/components/maps/MapMarkerFormPopup.tsx +++ b/admin/inertia/components/maps/MapMarkerFormPopup.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react' import { Popup } from 'react-map-gl/maplibre' import { PIN_COLORS } from '~/hooks/useMapMarkers' @@ -20,6 +20,7 @@ type MapMarkerFormPopupProps = { color: PinColorId }) => void onCancel: () => void + onDirtyChange?: (dirty: boolean) => void } export default function MapMarkerFormPopup({ @@ -28,11 +29,39 @@ export default function MapMarkerFormPopup({ initialMarker, onSave, onCancel, + onDirtyChange, }: MapMarkerFormPopupProps) { const [name, setName] = useState(initialMarker?.name ?? '') const [notes, setNotes] = useState(initialMarker?.notes ?? '') const [color, setColor] = useState(initialMarker?.color ?? 'orange') + const textareaRef = useRef(null) + + const resizeTextarea = useCallback(() => { + const textarea = textareaRef.current + if (!textarea) return + + textarea.style.height = 'auto' + textarea.style.height = `${textarea.scrollHeight}px` + }, []) + + useLayoutEffect(() => { + resizeTextarea() + }, [resizeTextarea]) + + useLayoutEffect(() => { + resizeTextarea() + }, []) + + const isDirty = + name !== (initialMarker?.name ?? '') || + notes !== (initialMarker?.notes ?? '') || + color !== (initialMarker?.color ?? 'orange') + + useEffect(() => { + onDirtyChange?.(isDirty) + }, [isDirty, onDirtyChange]) + const handleSave = () => { if (!name.trim()) return @@ -68,16 +97,16 @@ export default function MapMarkerFormPopup({ />