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:
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user