From d758c8c7443bd15d16b346c3ba3d34095370f6f9 Mon Sep 17 00:00:00 2001 From: Pepe Ziberi Date: Wed, 22 Jul 2026 21:11:42 +0200 Subject: [PATCH] feat(wind): taktische Live-Windanzeige in der Kopfzeile (v1.6.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wind-Badge oben in der Topbar: Pfeil zeigt die Ausbreitungsrichtung (wohin es treibt, Nord oben) + Himmelsrichtung „aus NW", mit Live-Punkt - Aktualisiert sich in Echtzeit: solange im Live-Modus, alle 10 Min. frische Windrichtung von Open-Meteo für den Einsatzort - Manuelles Setzen (Windrose) schaltet Live-Modus ab; „Live vom Standort" schaltet ihn wieder an - Tooltip: „Wind aus NW (315°) — treibt nach SO · live" Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- src/app/app/page.tsx | 30 ++++++++++++++++++++++++++---- src/components/layout/topbar.tsx | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d8a987d..b44614d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lageplan", - "version": "1.6.1", + "version": "1.6.2", "description": "Feuerwehr Lageplan - Krokier-App für Einsatzdokumentation", "private": true, "scripts": { diff --git a/src/app/app/page.tsx b/src/app/app/page.tsx index b2cf0ce..d301180 100644 --- a/src/app/app/page.tsx +++ b/src/app/app/page.tsx @@ -473,9 +473,16 @@ export default function AppPage() { onConflict: handleSaveConflict, }) - // Windrichtung fürs Lagebild setzen (lokal + am Projekt speichern) - const handleWindChange = useCallback(async (dir: number | null) => { + // Wind-Modus: live (aus Open-Meteo, aktualisiert sich) vs. manuell gesetzt + const [windLive, setWindLive] = useState(true) + const windLiveRef = useRef(true) + useEffect(() => { windLiveRef.current = windLive }, [windLive]) + + // Windrichtung fürs Lagebild setzen (lokal + am Projekt speichern). + // opts.live=true markiert einen Live-Wert (periodische Aktualisierung bleibt an). + const handleWindChange = useCallback(async (dir: number | null, opts?: { live?: boolean }) => { if (!currentProject) return + setWindLive(opts?.live === true) setCurrentProject((prev) => (prev ? { ...prev, windDirection: dir } : prev)) try { await fetch(`/api/projects/${currentProject.id}`, { @@ -496,7 +503,7 @@ export default function AppPage() { toast({ title: 'Wind nicht verfügbar', description: 'Keine Live-Daten für diesen Standort.', variant: 'destructive' }) return } - await handleWindChange(wind.direction) + await handleWindChange(wind.direction, { live: true }) toast({ title: 'Live-Wind übernommen', description: `aus ${degToCompass(wind.direction)} (${wind.direction}°) · ${Math.round(wind.speed)} km/h${wind.gusts ? `, Böen ${Math.round(wind.gusts)} km/h` : ''}`, @@ -515,10 +522,24 @@ export default function AppPage() { const c = proj.mapCenter if (!c?.lat || !c?.lng) return fetchLiveWind(c.lat, c.lng).then((wind) => { - if (wind) handleWindChange(wind.direction) + if (wind) handleWindChange(wind.direction, { live: true }) }) }, [currentProject?.id, currentProject?.windDirection, handleWindChange]) + // Echtzeit: solange der Wind im Live-Modus ist, alle 10 Min. aktualisieren + useEffect(() => { + if (!currentProject?.id) return + const id = setInterval(() => { + if (!windLiveRef.current || typeof navigator === 'undefined' || !navigator.onLine) return + const c = mapRef.current?.getCenter?.() || currentProject.mapCenter + if (!c) return + fetchLiveWind(c.lat, c.lng).then((wind) => { + if (wind) handleWindChange(wind.direction, { live: true }) + }) + }, 10 * 60 * 1000) + return () => clearInterval(id) + }, [currentProject?.id, handleWindChange]) + // Fullscreen toggle const toggleFullscreen = useCallback(() => { if (!document.fullscreenElement) { @@ -1007,6 +1028,7 @@ export default function AppPage() { handleStopEditing() } }} + windLive={windLive} /> {/* Kompakte Status-Zeile — eine Zeile mit Chips statt bis zu vier gestapelten Bannern. diff --git a/src/components/layout/topbar.tsx b/src/components/layout/topbar.tsx index 9fdf3ca..4e5ccf4 100644 --- a/src/components/layout/topbar.tsx +++ b/src/components/layout/topbar.tsx @@ -49,6 +49,7 @@ import { Loader2, } from 'lucide-react' import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog' +import { degToCompass } from '@/components/map/map-compass' import type { Project, DrawFeature, ProjectMode } from '@/types' import { formatDateTime } from '@/lib/utils' import { Logo } from '@/components/ui/logo' @@ -77,6 +78,8 @@ interface TopbarProps { onStartTour?: () => void presentationLocked?: boolean onTogglePresentationLock?: () => void + /** Windrichtung wird live aktualisiert (für die Live-Anzeige oben) */ + windLive?: boolean } export function Topbar({ @@ -103,6 +106,7 @@ export function Topbar({ onStartTour, presentationLocked, onTogglePresentationLock, + windLive, }: TopbarProps) { const [isLoadDialogOpen, setIsLoadDialogOpen] = useState(false) const [isHoseSettingsOpen, setIsHoseSettingsOpen] = useState(false) @@ -198,6 +202,22 @@ export function Topbar({ {/* Zone 3 — Uhr + Aktionen */}
+ {/* Wind — taktische Live-Anzeige (Pfeil zeigt Ausbreitungsrichtung, N oben) */} + {typeof project?.windDirection === 'number' && ( +
+ + + + + Wind aus {degToCompass(project.windDirection)} + + {windLive && } +
+ )} + {/* Uhr — Ops-Rooms leben nach der Uhr */} {now.toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit' })}