Datumsfelder lassen sich wieder tippen
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m50s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m13s

In den Berichten hing jedes Datumsfeld unmittelbar an der Adresszeile: eine
Aenderung hiess router.push, also neu laden und neu rendern. Ein
<input type="date"> meldet beim Tippen der Jahreszahl aber viermal -- 0002,
0020, 0202, 2026 -- und die ersten drei loesten je eine Navigation aus, die das
Feld auf den Stand aus der Adresse zuruecksetzte. Mitten im Tippen. Mit dem
Kalender ging es, weil der in einem Zug ein fertiges Datum setzt; das war der
Hinweis darauf, wo es klemmt.

DateField haelt den Tippstand jetzt bei sich und meldet nur, was eine Aussage
ist: ein vollstaendiges Datum mit vierstelliger Jahreszahl ab 1000, oder das
Leeren des Feldes. Beim Verlassen wird nachgereicht, was liegengeblieben ist.
Der Stichtag im Organigramm ist ein blankes input und benutzt dieselbe Regel
ueber istMeldbaresDatum -- zwei Fassungen davon liefen hier erfahrungsgemaess
auseinander.

Der Abgleich mit dem Wert von aussen laeuft waehrend des Renderns, nicht in
einem Effekt: der liefe erst nach dem Zeichnen, das Feld zeigte also fuer einen
Bildaufbau den alten Stand -- und die Regel gegen setState im Effekt verbietet
ihn aus genau diesem Grund.

Die Felder in den Panels bleiben, wie sie sind: dort ist der Zustand lokal, es
gibt keine Navigation und damit auch kein Zuruecksetzen.
This commit is contained in:
2026-09-29 21:39:35 +02:00
parent d9966c9623
commit 96c94642ba
4 changed files with 192 additions and 15 deletions

View File

@@ -2,8 +2,9 @@
import { CalendarClock } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { Button } from "@/components/ui/Button";
import { FILTER_SELECT_CLASS } from "@/components/ui/Field";
import { FILTER_SELECT_CLASS, istMeldbaresDatum } from "@/components/ui/Field";
import { fmtDate } from "@/lib/format";
type AsOfPickerProps = {
@@ -27,6 +28,14 @@ export function AsOfPicker({ asOf, today, projectedCount, historyStartsAt }: AsO
router.push(sp.size > 0 ? `${pathname}?${sp}` : pathname, { scroll: false });
}
// Angeglichen während des Renderns, nicht in einem Effekt — siehe DateField.
const [entwurf, setEntwurf] = useState(asOf);
const [zuletzt, setZuletzt] = useState(asOf);
if (asOf !== zuletzt) {
setZuletzt(asOf);
setEntwurf(asOf);
}
const isToday = asOf === today;
const isFuture = asOf > today;
// Assignments only started being recorded when the history table was
@@ -41,11 +50,23 @@ export function AsOfPicker({ asOf, today, projectedCount, historyStartsAt }: AsO
<CalendarClock className="h-4 w-4 text-ink-muted" />
Stichtag
</label>
{/* Der Tippstand bleibt hier, gemeldet wird nur ein vollständiges
Datum: jede Änderung ist eine Navigation, und beim Tippen der
Jahreszahl entstehen unterwegs 0002, 0020 und 0202 — jede davon
lud die Seite neu und setzte das Feld mitten im Tippen zurück.
Dieselbe Regel wie in DateField, dort steht auch der lange Grund. */}
<input
id="orgchart-asof"
type="date"
value={asOf}
onChange={(e) => setAsOf(e.target.value || undefined)}
value={entwurf}
onChange={(e) => {
const wert = e.target.value;
setEntwurf(wert);
if (istMeldbaresDatum(wert) && wert !== asOf) setAsOf(wert || undefined);
}}
onBlur={() => {
if (entwurf !== asOf && istMeldbaresDatum(entwurf)) setAsOf(entwurf || undefined);
}}
className={FILTER_SELECT_CLASS}
/>
{!isToday && (