feat(validation): add editing versioning revalidation and aligned reports

This commit is contained in:
Schubert Ferenc 2026-07-11 10:26:18 +02:00
parent f73a24df13
commit 302e542fda
28 changed files with 2691 additions and 406 deletions

View file

@ -103,7 +103,7 @@ export function CrudPage<T extends Entity>({
<h1 className="text-3xl font-semibold text-text">{title}</h1>
<p className="mt-2 text-text-light">{subtitle}</p>
</div>
<button onClick={startCreate} className="h-12 rounded-lg bg-primary px-5 font-semibold text-white shadow-soft">Neu</button>
<button onClick={startCreate} className="btn btn-primary h-12">Neu</button>
</header>
<div className="flex items-center gap-3 rounded-lg border border-border bg-surface px-4 py-3 shadow-soft">
@ -130,12 +130,12 @@ export function CrudPage<T extends Entity>({
</thead>
<tbody className="divide-y divide-border">
{rows.map((item) => (
<tr key={item.id}>
<tr key={item.id} className="hover:bg-accent/10">
{columns.map((column) => <td key={String(column.key)} className="whitespace-nowrap px-5 py-4">{valueForInput(item[column.key])}</td>)}
<td className="px-5 py-4">
<div className="flex justify-end gap-2">
<button aria-label="Bearbeiten" onClick={() => startEdit(item)} className="rounded-lg border border-border p-2 text-primary-dark"><Edit2 className="h-4 w-4" /></button>
<button aria-label="Loeschen" onClick={() => deleteMutation.mutate(item)} className="rounded-lg border border-border p-2 text-danger"><Trash2 className="h-4 w-4" /></button>
<button aria-label="Bearbeiten" title="Bearbeiten" onClick={() => startEdit(item)} className="icon-btn text-primary-dark"><Edit2 className="h-4 w-4" /></button>
<button aria-label="Loeschen" title={deleteMutation.isPending ? "Loeschen laeuft..." : "Loeschen"} disabled={deleteMutation.isPending} onClick={() => deleteMutation.mutate(item)} className="icon-btn text-danger">{deleteMutation.isPending ? <span className="spinner" /> : <Trash2 className="h-4 w-4" />}</button>
</div>
</td>
</tr>
@ -151,9 +151,9 @@ export function CrudPage<T extends Entity>({
<footer className="flex items-center justify-between text-sm text-text-light">
<span>{query.data?.total ?? 0} Datensaetze</span>
<div className="flex items-center gap-3">
<button disabled={page <= 1} onClick={() => setPage((value) => value - 1)} className="rounded-lg border border-border px-4 py-2 disabled:opacity-40">Zurueck</button>
<button disabled={page <= 1} title={page <= 1 ? "Erste Seite erreicht" : undefined} onClick={() => setPage((value) => value - 1)} className="btn btn-secondary">Zurueck</button>
<span>Seite {page} von {totalPages}</span>
<button disabled={page >= totalPages} onClick={() => setPage((value) => value + 1)} className="rounded-lg border border-border px-4 py-2 disabled:opacity-40">Weiter</button>
<button disabled={page >= totalPages} title={page >= totalPages ? "Letzte Seite erreicht" : undefined} onClick={() => setPage((value) => value + 1)} className="btn btn-secondary">Weiter</button>
</div>
</footer>
@ -162,7 +162,7 @@ export function CrudPage<T extends Entity>({
<form onSubmit={form.handleSubmit((values) => saveMutation.mutate(values))} className="max-h-[92vh] w-full max-w-3xl overflow-y-auto rounded-lg bg-surface p-6 shadow-soft">
<div className="mb-5 flex items-center justify-between">
<h2 className="text-xl font-semibold">{editing ? "Bearbeiten" : "Neu"}</h2>
<button type="button" onClick={() => setOpen(false)} className="rounded-lg border border-border p-2"><X className="h-4 w-4" /></button>
<button type="button" onClick={() => setOpen(false)} className="icon-btn"><X className="h-4 w-4" /></button>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{fields.map((field) => (
@ -184,8 +184,8 @@ export function CrudPage<T extends Entity>({
</div>
{saveMutation.isError && <p className="mt-4 text-sm text-danger">Speichern fehlgeschlagen. Bitte Eingaben pruefen.</p>}
<div className="mt-6 flex justify-end gap-3">
<button type="button" onClick={() => setOpen(false)} className="rounded-lg border border-border px-5 py-3 font-semibold">Abbrechen</button>
<button type="submit" className="rounded-lg bg-primary px-5 py-3 font-semibold text-white shadow-soft">Speichern</button>
<button type="button" onClick={() => setOpen(false)} className="btn btn-secondary">Abbrechen</button>
<button type="submit" disabled={saveMutation.isPending} title={saveMutation.isPending ? "Speichern laeuft..." : undefined} className="btn btn-primary">{saveMutation.isPending ? <span className="spinner" /> : null}{saveMutation.isPending ? "Speichern..." : "Speichern"}</button>
</div>
</form>
</div>