From 38e403bd39e4ed664c3dbc15fc8dda1af64d9f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20X=2E=20F=C3=BCrst?= Date: Sun, 9 Aug 2026 10:38:46 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20BEOs=20und=20Objekte=20bleiben=20beim=20?= =?UTF-8?q?Wechsel=20der=20Art=20erhalten=20=E2=80=94=20Version=201.16.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei getrennte Ursachen dafür, dass ausgefüllte Felder beim nachträglichen Wählen der Art der Führung verschwanden: - Objekte wurden bei jedem Wechsel der Art unbedingt geleert. Jetzt nur noch, wenn die Objektkategorie tatsächlich umschlägt (SonF ↔ Rest). - currentUserBeo wurde in MainClient bei jedem Rendern neu erzeugt und steht in den Abhängigkeiten des Vorbelegungs-Effekts in LogbuchForm. Dadurch setzte jedes Neurendern der Elternkomponente das ganze Formular zurück — BEOs auf den eigenen Namen, dazu Objekte, Zeiten und Bemerkungen. Ein konkreter Auslöser ist die Backup-Schaltfläche auf demselben Bildschirm. Das Objekt ist jetzt useMemo-stabil. Nebeneffekt: beim Bearbeiten lief das bisherige setObjekte([]) gegen das asynchrone Nachladen der gespeicherten Objekte — dieser Wettlauf entfällt. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- app/MainClient.tsx | 9 +++++++-- components/LogbuchForm.tsx | 18 +++++++++++++----- package.json | 2 +- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ed5131a..edc0821 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ Next.js 16 App Router application. All pages are server components; interactive - **`CustomSelect`**: replaces native ``. Shows HH:MM with ▲/▼ buttons, 5-minute steps, auto-repeat on hold (400 ms delay → 1-hour steps at 350 ms). Keyboard: ↑/↓. -- **`LogbuchForm`**: Beginn/Ende stored as `"YYYY-MM-DDTHH:MM"` strings. Date and time are split into separate `` + ``. The single date field is the **Beginn** date; Ende's date is derived, never entered. Rule for sessions crossing midnight: **start time-of-day later than end time-of-day ⇒ Beginn is the previous day**, anchored on Ende (the moment the form is filled in, hence the reliable one) — `beginnAusEnde()` / `endeAusBeginn()`. So typing a start time can move the visible date back a day; a grey line below the row explains it. A duration over 12 h shows a soft hint but never blocks saving. Entries created before 1.16.0 may have `Beginn > Ende`; `migrate_zeiten.sql` fixes them. +- **`LogbuchForm`**: Beginn/Ende stored as `"YYYY-MM-DDTHH:MM"` strings. Date and time are split into separate `` + ``. The single date field is the **Beginn** date; Ende's date is derived, never entered. Rule for sessions crossing midnight: **start time-of-day later than end time-of-day ⇒ Beginn is the previous day**, anchored on Ende (the moment the form is filled in, hence the reliable one) — `beginnAusEnde()` / `endeAusBeginn()`. So typing a start time can move the visible date back a day; a grey line below the row explains it. A duration over 12 h shows a soft hint but never blocks saving. Entries created before 1.16.0 may have `Beginn > Ende`; `migrate_zeiten.sql` fixes them. Changing ArtFuehrung only clears what no longer fits — Objekte just when the category flips (`SonF` ↔ rest), never BEOs. The `currentUserBeo` prop must be referentially stable (it is `useMemo`'d in `MainClient`): it sits in the prefill effect's deps, so a fresh object on every parent render would wipe the form mid-entry. - **`LogbuchList`**: accepts `compact` and `limit` props. Compact mode used for the 5-entry preview below the form on desktop (`hidden lg:block`). ## Data model diff --git a/app/MainClient.tsx b/app/MainClient.tsx index 31b3de6..545a1ec 100644 --- a/app/MainClient.tsx +++ b/app/MainClient.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { KUPPELN } from '@/types/logbuch'; import type { Kuppel, LogbuchEintrag, SofueStatus } from '@/types/logbuch'; import LogbuchForm from '@/components/LogbuchForm'; @@ -50,7 +50,12 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); - const currentUserBeo = { ID: beoId, Kuerzel: kuerzel, Name: beoName }; + // Muss stabil bleiben: LogbuchForm setzt bei jeder Änderung dieses Objekts das + // Formular zurück. Frisch erzeugt würde jedes Rendern hier die Eingaben löschen. + const currentUserBeo = useMemo( + () => ({ ID: beoId, Kuerzel: kuerzel, Name: beoName }), + [beoId, kuerzel, beoName] + ); function handleSaved(sofue?: SofueStatus | null) { setRefreshKey((k) => k + 1); diff --git a/components/LogbuchForm.tsx b/components/LogbuchForm.tsx index d1a9b6d..5a5120e 100644 --- a/components/LogbuchForm.tsx +++ b/components/LogbuchForm.tsx @@ -175,6 +175,8 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved return () => { verworfen = true; }; }, [ende]); + // Vorbelegen beim Bearbeiten, sonst leeres Formular. currentUserBeo muss vom + // Aufrufer stabil gehalten werden, sonst setzt jedes Rendern die Eingaben zurück. useEffect(() => { if (editEntry) { setArtFuehrung(editEntry.ArtFuehrung); @@ -233,17 +235,23 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved } }, [editEntry]); - // Objekte, Besucher und Spende zurücksetzen beim Wechsel der Art der Führung + // Beim Wechsel der Art der Führung nur das zurücksetzen, was zur neuen Art nicht + // mehr passt. Der erste Lauf bleibt außen vor: dort steht noch der Startwert 'RF', + // während die Vorbelegung aus editEntry gerade erst gesetzt wird. useEffect(() => { const vorherigeArt = prevArtRef.current; prevArtRef.current = artFuehrung; - setObjekte([]); + if (vorherigeArt === null) return; + + // Objekte nur leeren, wenn die Kategorie wechselt (Sonne ↔ Sterne) — sonst gingen + // bereits eingetragene Objekte verloren, wenn die Art erst danach gewählt wird. + if ((vorherigeArt === SONNE_ART) !== (artFuehrung === SONNE_ART)) { + setObjekte([]); + } if (NO_OBJEKTE_ARTEN.includes(artFuehrung)) { setBesucher(''); } - // Nicht beim ersten Lauf leeren — sonst überschreibt der Start-Wert 'RF' - // die aus editEntry vorbelegte Spende. - if (vorherigeArt !== null && artFuehrung !== 'SF') { + if (artFuehrung !== 'SF') { setSpende(''); setSpendeBetragRaw(''); } diff --git a/package.json b/package.json index ce35134..c72f054 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logbuch", - "version": "1.16.0", + "version": "1.16.1", "private": true, "scripts": { "dev": "next dev",