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({ />