feat(field): Symbole am Stück setzen + eigene GPS-Position (v1.5.4)
- Tap-to-place bleibt scharf: mehrere gleiche Symbole nacheinander setzen (z.B. 4 Hydranten), Hinweis zeigt Anzahl + "Fertig"-Knopf - Zeichen-Werkzeug wählen beendet den Platzier-Modus - GPS: eigene Position dauerhaft mit Genauigkeitskreis + Blickrichtung - Beim Öffnen bleibt die Karte auf dem Einsatzort (springt nicht mehr auf die eigene Position, wenn ein Einsatzort gesetzt ist) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lageplan",
|
"name": "lageplan",
|
||||||
"version": "1.5.3",
|
"version": "1.5.4",
|
||||||
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { useAuth } from '@/components/providers/auth-provider'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
||||||
import { JournalView } from '@/components/journal/journal-view'
|
import { JournalView } from '@/components/journal/journal-view'
|
||||||
import { Lock, Eye, AlertTriangle, WifiOff, GraduationCap, Map as MapIcon, ClipboardList, LayoutGrid, X } from 'lucide-react'
|
import { Lock, Eye, AlertTriangle, WifiOff, GraduationCap, Map as MapIcon, ClipboardList, LayoutGrid } from 'lucide-react'
|
||||||
import { CustomDragLayer } from '@/components/map/custom-drag-layer'
|
import { CustomDragLayer } from '@/components/map/custom-drag-layer'
|
||||||
import { OnboardingTour, resetOnboardingTour } from '@/components/onboarding/onboarding-tour'
|
import { OnboardingTour, resetOnboardingTour } from '@/components/onboarding/onboarding-tour'
|
||||||
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
|
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
|
||||||
@@ -51,6 +51,8 @@ export default function AppPage() {
|
|||||||
const [isEndConfirmOpen, setIsEndConfirmOpen] = useState(false)
|
const [isEndConfirmOpen, setIsEndConfirmOpen] = useState(false)
|
||||||
// Tap-to-place: angetipptes Symbol wartet auf Platzierung per Karten-Tipp (Mobil)
|
// Tap-to-place: angetipptes Symbol wartet auf Platzierung per Karten-Tipp (Mobil)
|
||||||
const [pendingSymbol, setPendingSymbol] = useState<{ id: string; imageUrl: string } | null>(null)
|
const [pendingSymbol, setPendingSymbol] = useState<{ id: string; imageUrl: string } | null>(null)
|
||||||
|
// Feld-Tempo: bleibt scharf, um mehrere gleiche Symbole am Stück zu setzen
|
||||||
|
const [placedCount, setPlacedCount] = useState(0)
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
const [isDeleteAllConfirmOpen, setIsDeleteAllConfirmOpen] = useState(false)
|
const [isDeleteAllConfirmOpen, setIsDeleteAllConfirmOpen] = useState(false)
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false)
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||||
@@ -403,7 +405,11 @@ export default function AppPage() {
|
|||||||
|
|
||||||
// Werkzeugwechsel auf ein Zeichen-Tool übernimmt vorab den Lock (rechtzeitig vor dem Zeichnen)
|
// Werkzeugwechsel auf ein Zeichen-Tool übernimmt vorab den Lock (rechtzeitig vor dem Zeichnen)
|
||||||
const handleDrawModeChange = useCallback((mode: DrawMode) => {
|
const handleDrawModeChange = useCallback((mode: DrawMode) => {
|
||||||
if (mode !== 'select') ensureEditing()
|
if (mode !== 'select') {
|
||||||
|
ensureEditing()
|
||||||
|
setPendingSymbol(null) // ein Zeichen-Werkzeug beendet das Symbol-Platzieren
|
||||||
|
setPlacedCount(0)
|
||||||
|
}
|
||||||
setDrawMode(mode)
|
setDrawMode(mode)
|
||||||
}, [ensureEditing, setDrawMode])
|
}, [ensureEditing, setDrawMode])
|
||||||
|
|
||||||
@@ -769,10 +775,11 @@ export default function AppPage() {
|
|||||||
const handleSymbolSelect = useCallback((symbol: { id: string; imageUrl: string }) => {
|
const handleSymbolSelect = useCallback((symbol: { id: string; imageUrl: string }) => {
|
||||||
ensureEditing() // Lock schon beim Auswählen übernehmen, damit das Platzieren sofort greift
|
ensureEditing() // Lock schon beim Auswählen übernehmen, damit das Platzieren sofort greift
|
||||||
setPendingSymbol(symbol)
|
setPendingSymbol(symbol)
|
||||||
|
setPlacedCount(0)
|
||||||
setIsSidebarOpen(false)
|
setIsSidebarOpen(false)
|
||||||
setActiveTab('map')
|
setActiveTab('map')
|
||||||
setDrawMode('select')
|
setDrawMode('select')
|
||||||
toast({ title: 'Symbol platzieren', description: 'Tippe auf die Karte, um das Symbol zu setzen.' })
|
toast({ title: 'Symbol platzieren', description: 'Auf die Karte tippen — mehrere möglich, dann „Fertig".' })
|
||||||
}, [setIsSidebarOpen, setActiveTab, toast, ensureEditing, setDrawMode])
|
}, [setIsSidebarOpen, setActiveTab, toast, ensureEditing, setDrawMode])
|
||||||
|
|
||||||
const handleTextPlace = useCallback((coordinates: [number, number]) => {
|
const handleTextPlace = useCallback((coordinates: [number, number]) => {
|
||||||
@@ -1038,7 +1045,7 @@ export default function AppPage() {
|
|||||||
onDrawModeChange={setDrawMode}
|
onDrawModeChange={setDrawMode}
|
||||||
undoDrawPointRef={undoDrawPointRef}
|
undoDrawPointRef={undoDrawPointRef}
|
||||||
pendingSymbol={pendingSymbol ? { iconId: pendingSymbol.id, imageUrl: pendingSymbol.imageUrl } : null}
|
pendingSymbol={pendingSymbol ? { iconId: pendingSymbol.id, imageUrl: pendingSymbol.imageUrl } : null}
|
||||||
onSymbolPlaced={() => setPendingSymbol(null)}
|
onSymbolPlaced={() => setPlacedCount((n) => n + 1)}
|
||||||
isFullscreen={isFullscreen}
|
isFullscreen={isFullscreen}
|
||||||
onToggleFullscreen={toggleFullscreen}
|
onToggleFullscreen={toggleFullscreen}
|
||||||
/>
|
/>
|
||||||
@@ -1093,13 +1100,20 @@ export default function AppPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tap-to-place Hinweis — schwebt über der Bottom-Nav, mit Abbrechen */}
|
{/* Tap-to-place Hinweis — schwebt über der Bottom-Nav; mehrere setzen, dann „Fertig" */}
|
||||||
{pendingSymbol && (
|
{pendingSymbol && (
|
||||||
<div className="fixed left-1/2 -translate-x-1/2 z-40 bottom-[76px] md:bottom-6 flex items-center gap-2 px-3 py-2 rounded-full bg-primary text-primary-foreground shadow-lg text-sm">
|
<div className="fixed left-1/2 -translate-x-1/2 z-40 bottom-[76px] md:bottom-6 flex items-center gap-2 pl-3 pr-2 py-2 rounded-full bg-primary text-primary-foreground shadow-lg text-sm">
|
||||||
<img src={pendingSymbol.imageUrl} alt="" className="w-5 h-5 object-contain bg-white rounded" crossOrigin="anonymous" />
|
<img src={pendingSymbol.imageUrl} alt="" className="w-5 h-5 object-contain bg-white rounded" crossOrigin="anonymous" />
|
||||||
<span>Auf die Karte tippen zum Platzieren</span>
|
<span>
|
||||||
<button onClick={() => setPendingSymbol(null)} className="ml-1 p-0.5 rounded-full hover:bg-white/20" title="Abbrechen">
|
{placedCount > 0
|
||||||
<X className="w-4 h-4" />
|
? `Auf Karte tippen · ${placedCount} gesetzt`
|
||||||
|
: 'Auf die Karte tippen zum Platzieren'}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => { setPendingSymbol(null); setPlacedCount(0) }}
|
||||||
|
className="ml-1 px-2.5 py-1 rounded-full bg-white/20 hover:bg-white/30 text-xs font-semibold"
|
||||||
|
>
|
||||||
|
Fertig
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -706,16 +706,34 @@ export function MapView({
|
|||||||
map.current.addControl(new maplibregl.NavigationControl(), 'bottom-right')
|
map.current.addControl(new maplibregl.NavigationControl(), 'bottom-right')
|
||||||
map.current.addControl(new maplibregl.ScaleControl(), 'bottom-left')
|
map.current.addControl(new maplibregl.ScaleControl(), 'bottom-left')
|
||||||
|
|
||||||
// Geolocation: center map on user's position
|
// Geolocation: eigene Position dauerhaft anzeigen (Punkt + Genauigkeit + Blickrichtung)
|
||||||
const geolocate = new maplibregl.GeolocateControl({
|
const geolocate = new maplibregl.GeolocateControl({
|
||||||
positionOptions: { enableHighAccuracy: true },
|
positionOptions: { enableHighAccuracy: true },
|
||||||
trackUserLocation: true,
|
trackUserLocation: true,
|
||||||
showAccuracyCircle: false,
|
showAccuracyCircle: true,
|
||||||
})
|
// showUserHeading zeigt die Blickrichtung — in den Typen dieser Version (noch)
|
||||||
|
// nicht deklariert, zur Laufzeit aber vorhanden.
|
||||||
|
showUserHeading: true,
|
||||||
|
} as any)
|
||||||
map.current.addControl(geolocate, 'bottom-right')
|
map.current.addControl(geolocate, 'bottom-right')
|
||||||
|
|
||||||
// Auto-trigger geolocation when map loads to center on user's real position
|
// Position beim Laden aktivieren (Punkt erscheint), aber bei gesetztem Einsatzort
|
||||||
map.current.on('load', () => { geolocate.trigger() })
|
// auf dem Einsatzort bleiben statt auf die eigene Position zu springen.
|
||||||
|
const einsatzortCenter = project?.mapCenter
|
||||||
|
const hasEinsatzort = !!einsatzortCenter &&
|
||||||
|
!(einsatzortCenter.lng === 8.5417 && einsatzortCenter.lat === 47.3769)
|
||||||
|
map.current.on('load', () => {
|
||||||
|
geolocate.trigger()
|
||||||
|
if (hasEinsatzort && einsatzortCenter) {
|
||||||
|
geolocate.once('geolocate', () => {
|
||||||
|
map.current?.easeTo({
|
||||||
|
center: [einsatzortCenter.lng, einsatzortCenter.lat],
|
||||||
|
zoom: project?.mapZoom || 17,
|
||||||
|
duration: 600,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
map.current.on('load', () => {
|
map.current.on('load', () => {
|
||||||
const m = map.current
|
const m = map.current
|
||||||
|
|||||||
Reference in New Issue
Block a user