refactor(wind): Wind nur noch in der Kopfzeile (Karte = nur Nordpfeil) (v1.8.9)

- Wind-Anzeige von der Karte entfernt (war doppelt und nicht als "Wind" erkennbar);
  MapCompass zeigt jetzt nur den Nordpfeil
- Kopfzeile: Wind-Badge ist klickbar → Einsteller (Live vom Standort / Richtung wählen /
  entfernen), klar mit "Wind" beschriftet
- Topbar erhält onWindChange/onFetchLiveWind (nur bei Bearbeitungsrecht)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-23 22:27:48 +02:00
parent 2323f1d360
commit 405639db7d
4 changed files with 100 additions and 114 deletions

View File

@@ -48,6 +48,8 @@ import {
ChevronDown,
Loader2,
Share2,
Wind,
Satellite,
} from 'lucide-react'
import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog'
import { ShareDialog } from '@/components/dialogs/share-dialog'
@@ -56,6 +58,12 @@ import type { Project, DrawFeature, ProjectMode } from '@/types'
import { formatDateTime } from '@/lib/utils'
import { Logo } from '@/components/ui/logo'
// 8-Richtungs-Auswahl (Grad, meteorologisch „Wind aus")
const WIND_OPTIONS: { label: string; deg: number }[] = [
{ label: 'N', deg: 0 }, { label: 'NO', deg: 45 }, { label: 'O', deg: 90 }, { label: 'SO', deg: 135 },
{ label: 'S', deg: 180 }, { label: 'SW', deg: 225 }, { label: 'W', deg: 270 }, { label: 'NW', deg: 315 },
]
interface TopbarProps {
project: Project | null
onNewProject: (mode?: ProjectMode) => void
@@ -82,6 +90,10 @@ interface TopbarProps {
onTogglePresentationLock?: () => void
/** Windrichtung wird live aktualisiert (für die Live-Anzeige oben) */
windLive?: boolean
/** Windrichtung setzen/entfernen (Grad, meteorologisch „Wind aus"; null = entfernen) */
onWindChange?: (dir: number | null) => void
/** Live-Wind (Open-Meteo) vom Standort holen */
onFetchLiveWind?: () => Promise<void> | void
/** true, wenn der aktuelle Nutzer den Bearbeitungs-Lock hält */
isEditingByMe?: boolean
/** Karte speichern & für andere freigeben (Lock lösen) */
@@ -115,6 +127,8 @@ export function Topbar({
presentationLocked,
onTogglePresentationLock,
windLive,
onWindChange,
onFetchLiveWind,
isEditingByMe,
onStopEditing,
editingLoading,
@@ -138,6 +152,8 @@ export function Topbar({
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
const [isDeleting, setIsDeleting] = useState(false)
const [windLoading, setWindLoading] = useState(false)
// Live clock
const [now, setNow] = useState(new Date())
useEffect(() => {
@@ -214,20 +230,70 @@ export function Topbar({
{/* Zone 3 — Uhr + Aktionen */}
<div className="flex items-center gap-1 md:gap-1.5 shrink-0">
{/* Wind — taktische Live-Anzeige (Pfeil zeigt Ausbreitungsrichtung, N oben) */}
{typeof project?.windDirection === 'number' && (
<div
className="hidden sm:flex items-center gap-1.5 rounded-md bg-blue-600 text-white pl-1.5 pr-2 py-1 shrink-0"
title={`Wind aus ${degToCompass(project.windDirection)} (${project.windDirection}°) — treibt nach ${degToCompass(project.windDirection + 180)}${windLive ? ' · live' : ''}`}
>
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0" style={{ transform: `rotate(${project.windDirection + 180}deg)` }} aria-hidden>
<path d="M12 3 L18 16 L12 12.5 L6 16 Z" fill="currentColor" />
</svg>
<span className="text-xs font-bold leading-none whitespace-nowrap">
<span className="hidden lg:inline opacity-80 font-medium">Wind </span>aus {degToCompass(project.windDirection)}
</span>
{windLive && <span className="w-1.5 h-1.5 rounded-full bg-green-300 animate-pulse shrink-0" title="Live" />}
</div>
{/* Wind — klare, einzige Wind-Anzeige. Klick öffnet den Einsteller (Live vom Standort
oder Richtung wählen). Der Pfeil zeigt die Ausbreitungsrichtung, N oben. */}
{project && onWindChange && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
{typeof project.windDirection === 'number' ? (
<button
className="hidden sm:flex items-center gap-1.5 rounded-md bg-blue-600 hover:bg-blue-700 text-white pl-1.5 pr-2 py-1 shrink-0"
title={`Wind aus ${degToCompass(project.windDirection)} (${project.windDirection}°) — treibt nach ${degToCompass(project.windDirection + 180)}. Klicken zum Ändern.`}
>
<Wind className="w-3.5 h-3.5 shrink-0 opacity-90" />
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0" style={{ transform: `rotate(${project.windDirection + 180}deg)` }} aria-hidden>
<path d="M12 3 L18 16 L12 12.5 L6 16 Z" fill="currentColor" />
</svg>
<span className="text-xs font-bold leading-none whitespace-nowrap">
Wind aus {degToCompass(project.windDirection)}
</span>
{windLive && <span className="w-1.5 h-1.5 rounded-full bg-green-300 animate-pulse shrink-0" title="Live" />}
</button>
) : (
<Button variant="outline" size="sm" className="hidden sm:flex h-9 md:h-10 px-2 shrink-0" title="Windrichtung setzen">
<Wind className="w-4 h-4 lg:mr-1.5" />
<span className="hidden lg:inline">Wind</span>
</Button>
)}
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 p-2">
<div className="flex items-center gap-1.5 px-1 pb-1.5 text-xs font-semibold text-muted-foreground">
<Wind className="w-3.5 h-3.5" /> Windrichtung (Wind aus)
</div>
{onFetchLiveWind && (
<button
onClick={async () => { setWindLoading(true); try { await onFetchLiveWind() } finally { setWindLoading(false) } }}
disabled={windLoading}
className="w-full mb-2 flex items-center justify-center gap-1.5 rounded-md bg-blue-600 hover:bg-blue-700 text-white text-xs font-semibold py-1.5 disabled:opacity-60"
>
{windLoading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Satellite className="w-3.5 h-3.5" />}
Live vom Standort
</button>
)}
<div className="text-[10px] text-muted-foreground mb-1 px-0.5">oder Richtung wählen:</div>
<div className="grid grid-cols-4 gap-1">
{WIND_OPTIONS.map((w) => (
<button
key={w.deg}
onClick={() => onWindChange(w.deg)}
className={`h-8 rounded-md text-xs font-semibold transition-colors ${
project.windDirection === w.deg ? 'bg-blue-600 text-white' : 'bg-muted hover:bg-accent text-foreground'
}`}
>
{w.label}
</button>
))}
</div>
{typeof project.windDirection === 'number' && (
<button
onClick={() => onWindChange(null)}
className="mt-2 w-full text-xs text-muted-foreground hover:text-destructive py-1"
>
Wind entfernen
</button>
)}
</DropdownMenuContent>
</DropdownMenu>
)}
{/* Uhr — Ops-Rooms leben nach der Uhr */}