fix(robustheit+ux): atomares Speichern, Offline-Härtung, Einsatz-Beenden, Audit raus

Robustheit / Datensicherheit:
- Features-PUT in prisma.$transaction (delete+create atomar) — verhindert
  Totalverlust bei Abbruch zwischen Löschen und Neuanlegen
- localStorage-Features pro Projekt (lageplan-features-<id>) statt globalem Key
  — verhindert Datenübertrag zwischen Projekten
- Sync-Queue verwirft dauerhafte 4xx-Fehler statt Endlos-Retry
- beforeunload persistiert auch leere Feature-Listen (Löschungen gehen nicht verloren)

UX:
- Leitungsrechner-Dropdown: Popover z-[2000] (lag hinter dem Panel), Label gekürzt
- Neuer "Einsatz beenden"-Button (speichert, löst Lock, schliesst Einsatz)
- Audit-Trail-Einstiege entfernt (Toolbar-Button + Menü-Eintrag)

Version 1.4.10

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepe Ziberi
2026-07-19 01:38:13 +02:00
parent ddb7c63600
commit 8b5f4a6778
8 changed files with 80 additions and 45 deletions

View File

@@ -42,6 +42,7 @@ import {
HelpCircle,
Lock,
Unlock,
CheckCircle2,
} from 'lucide-react'
import { HoseSettingsDialog } from '@/components/dialogs/hose-settings-dialog'
import type { Project, DrawFeature, ProjectMode } from '@/types'
@@ -51,6 +52,7 @@ import { Logo } from '@/components/ui/logo'
interface TopbarProps {
project: Project | null
onNewProject: (mode?: ProjectMode) => void
onEndProject?: () => void
onSaveProject: () => void
onLoadProject: (project: Project, features: DrawFeature[]) => void
onDeleteProject?: (projectId: string) => void
@@ -76,6 +78,7 @@ interface TopbarProps {
export function Topbar({
project,
onNewProject,
onEndProject,
onSaveProject,
onLoadProject,
onDeleteProject,
@@ -285,19 +288,17 @@ export function Topbar({
{isFullscreen ? <Minimize className="w-4 h-4" /> : <Maximize className="w-4 h-4" />}
</Button>
<Button
variant={isAuditOpen ? 'default' : 'outline'}
className="hidden md:flex h-9 px-2 relative"
onClick={onToggleAudit}
title="Audit Trail"
>
<ClipboardList className="w-4 h-4" />
{auditLog.length > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-primary text-primary-foreground text-[10px] rounded-full flex items-center justify-center font-bold">
{auditLog.length > 99 ? '99' : auditLog.length}
</span>
)}
</Button>
{project && onEndProject && (
<Button
variant="outline"
className="hidden md:flex h-9 px-2 md:px-3 border-red-300 text-red-600 hover:bg-red-50 hover:text-red-700 dark:border-red-800 dark:text-red-400 dark:hover:bg-red-950/40"
onClick={onEndProject}
title="Einsatz speichern und schliessen"
>
<CheckCircle2 className="w-4 h-4 lg:mr-1" />
<span className="hidden lg:inline">Einsatz beenden</span>
</Button>
)}
{/* Desktop: User menu dropdown */}
{userName && onLogout && (
@@ -366,10 +367,6 @@ export function Topbar({
<Settings className="w-4 h-4 mr-2" />
Einstellungen
</DropdownMenuItem>
<DropdownMenuItem onClick={onToggleAudit}>
<ClipboardList className="w-4 h-4 mr-2" />
Audit Trail {auditLog.length > 0 && `(${auditLog.length})`}
</DropdownMenuItem>
{onLogout && (
<DropdownMenuItem onClick={onLogout} className="text-destructive">
<LogOut className="w-4 h-4 mr-2" />