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:
Pepe Ziberi
2026-07-22 19:46:43 +02:00
parent 03914083df
commit bbf6fbdd13
3 changed files with 47 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "lageplan",
"version": "1.5.3",
"version": "1.5.4",
"description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation",
"private": true,
"scripts": {

View File

@@ -19,7 +19,7 @@ import { useAuth } from '@/components/providers/auth-provider'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
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 { OnboardingTour, resetOnboardingTour } from '@/components/onboarding/onboarding-tour'
import { useKeyboardShortcuts } from '@/hooks/use-keyboard-shortcuts'
@@ -51,6 +51,8 @@ export default function AppPage() {
const [isEndConfirmOpen, setIsEndConfirmOpen] = useState(false)
// Tap-to-place: angetipptes Symbol wartet auf Platzierung per Karten-Tipp (Mobil)
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 [isDeleteAllConfirmOpen, setIsDeleteAllConfirmOpen] = 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)
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)
}, [ensureEditing, setDrawMode])
@@ -769,10 +775,11 @@ export default function AppPage() {
const handleSymbolSelect = useCallback((symbol: { id: string; imageUrl: string }) => {
ensureEditing() // Lock schon beim Auswählen übernehmen, damit das Platzieren sofort greift
setPendingSymbol(symbol)
setPlacedCount(0)
setIsSidebarOpen(false)
setActiveTab('map')
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])
const handleTextPlace = useCallback((coordinates: [number, number]) => {
@@ -1038,7 +1045,7 @@ export default function AppPage() {
onDrawModeChange={setDrawMode}
undoDrawPointRef={undoDrawPointRef}
pendingSymbol={pendingSymbol ? { iconId: pendingSymbol.id, imageUrl: pendingSymbol.imageUrl } : null}
onSymbolPlaced={() => setPendingSymbol(null)}
onSymbolPlaced={() => setPlacedCount((n) => n + 1)}
isFullscreen={isFullscreen}
onToggleFullscreen={toggleFullscreen}
/>
@@ -1093,13 +1100,20 @@ export default function AppPage() {
/>
</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 && (
<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" />
<span>Auf die Karte tippen zum Platzieren</span>
<button onClick={() => setPendingSymbol(null)} className="ml-1 p-0.5 rounded-full hover:bg-white/20" title="Abbrechen">
<X className="w-4 h-4" />
<span>
{placedCount > 0
? `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>
</div>
)}

View File

@@ -706,16 +706,34 @@ export function MapView({
map.current.addControl(new maplibregl.NavigationControl(), 'bottom-right')
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({
positionOptions: { enableHighAccuracy: 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')
// Auto-trigger geolocation when map loads to center on user's real position
map.current.on('load', () => { geolocate.trigger() })
// Position beim Laden aktivieren (Punkt erscheint), aber bei gesetztem Einsatzort
// 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', () => {
const m = map.current