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

@@ -21,10 +21,12 @@ export function useAutoSave({
isEditingByMe,
setSyncQueueCount,
}: UseAutoSaveOptions) {
// Persist features to localStorage on change (including empty array to reflect deletions)
// Persist features to localStorage on change (per project — verhindert, dass Features
// eines Projekts in ein anderes "bluten"). Inkl. leerem Array (spiegelt Löschungen).
useEffect(() => {
localStorage.setItem('lageplan-features', JSON.stringify(features))
}, [features])
if (!currentProject?.id) return
localStorage.setItem(`lageplan-features-${currentProject.id}`, JSON.stringify(features))
}, [features, currentProject?.id])
// Auto-save to API — debounced 2s after every feature change + fallback interval
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
@@ -81,7 +83,9 @@ export function useAutoSave({
// Also save on page unload / tab switch
useEffect(() => {
const handleBeforeUnload = () => {
if (currentProject?.id && featuresRef.current.length > 0) {
// Nur der aktive Bearbeiter persistiert beim Schliessen — auch eine leere Liste,
// damit "alles gelöscht + Tab zu" nicht die alten Elemente in der DB stehen lässt.
if (currentProject?.id && isEditingByMe) {
const payload = JSON.stringify({ features: featuresRef.current })
navigator.sendBeacon(`/api/projects/${currentProject.id}/features`, new Blob([payload], { type: 'application/json' }))
}