Schliesst die letzte offene Luecke aus Phase 1. Bis dahin liessen sich Geraete
nur ueber den Import erfassen — fuer einen Bestand richtig, fuer ein einzelnes
nachbeschafftes Geraet umstaendlich.
Neu:
- /geraete/neu und /geraete/<id>/bearbeiten, beide ueber dieselbe Komponente
- Die Lagerung ist ein Umschalter statt zweier Auswahlfelder. Standort und
Fahrzeug schliessen sich aus; so kann der Fehler gar nicht erst entstehen,
den die Datenbank sonst abweisen wuerde. Beim Wechsel des Fahrzeugs wird die
Raumangabe zurueckgesetzt.
- Prueftermin und Lebensdauerende werden beim Tippen vorgerechnet, mit
denselben Funktionen wie auf dem Server. Beim Speichern rechnet der Server
erneut und behaelt das letzte Wort.
- Gibt es nur einen Standort, ist er beim Anlegen vorbelegt.
- Knopf "Neues Geraet" in der Liste, "Bearbeiten" auf der Detailseite
Behoben:
- Das Formular stuerzte beim ersten Rechnen ab. Die Vorgabewerte lagen in einem
"use client"-Modul und wurden von einer Server-Komponente importiert; dort
kommt nur eine Client-Referenz an, kein Objekt, und das Spreaden ergibt lauter
undefined. Die Werte liegen jetzt in geraet-formular-werte.ts ohne
"use client".
- Das Sternchen an Pflichtfeldern wurde von Sprachausgaben mitgelesen
("Bezeichnung Stern"). Es traegt jetzt aria-hidden; dass ein Feld ausgefuellt
werden muss, sagt bereits das required-Attribut.
- Fehlermeldungen in Formularen sind eindeutig ansteuerbar. Next.js haengt
einen eigenen Routen-Ansager mit der Rolle "alert" in jede Seite.
Tests:
- 7 neue Playwright-Tests zum Formular, darunter die Lagerungsregel und die
Fristenvorschau
- Zwei davon laufen nicht unter Touch-Emulation: Chromium nimmt dort keine
zeichenweise Eingabe in Datumsfelder an. Begruendet uebersprungen, die
Rechnung selbst deckt qr-und-fristen.test.ts ab.
- Insgesamt 104 Unit-Tests und 56 E2E-Tests gruen
Anleitung, Changelog, Devlog, Aufwandsschaetzung und Roadmap fortgeschrieben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import Link from "next/link";
|
|
import { notFound, redirect } from "next/navigation";
|
|
import { ArrowLeft } from "lucide-react";
|
|
import { aktuelleSitzung } from "@/lib/auth";
|
|
import { hatRecht } from "@/lib/authz";
|
|
import { fuerOrganisation } from "@/lib/db";
|
|
import { Button } from "@/components/ui/button";
|
|
import { GeraetFormular } from "@/components/stammdaten/geraet-formular";
|
|
import {
|
|
LEERES_GERAET,
|
|
alsFeldwert,
|
|
} from "@/components/stammdaten/geraet-formular-werte";
|
|
|
|
export const metadata = { title: "Gerät bearbeiten" };
|
|
|
|
/** Ein vorhandenes Gerät bearbeiten. */
|
|
export default async function GeraetBearbeitenSeite({
|
|
params,
|
|
}: {
|
|
params: Promise<{ id: string }>;
|
|
}) {
|
|
const sitzung = await aktuelleSitzung();
|
|
if (!sitzung) redirect("/anmelden");
|
|
|
|
const { id } = await params;
|
|
if (!hatRecht(sitzung, "geraet.bearbeiten")) redirect(`/geraete/${id}`);
|
|
|
|
const geraet = await fuerOrganisation(sitzung.organisationId, (db) =>
|
|
db.geraet.findUnique({ where: { id } }),
|
|
);
|
|
|
|
if (!geraet) notFound();
|
|
|
|
const vorgabe = {
|
|
...LEERES_GERAET,
|
|
id: geraet.id,
|
|
inventarnummer: geraet.inventarnummer,
|
|
bezeichnung: geraet.bezeichnung,
|
|
geraetetypId: geraet.geraetetypId,
|
|
hersteller: geraet.hersteller ?? "",
|
|
modell: geraet.modell ?? "",
|
|
seriennummer: geraet.seriennummer ?? "",
|
|
herstellungsdatum: alsFeldwert(geraet.herstellungsdatum),
|
|
indienststellung: alsFeldwert(geraet.indienststellung),
|
|
// Decimal aus Prisma: als deutscher Text ins Feld, damit das Komma bleibt.
|
|
anschaffungspreis: geraet.anschaffungspreis
|
|
? String(geraet.anschaffungspreis).replace(".", ",")
|
|
: "",
|
|
status: geraet.status,
|
|
pruefintervallMonate: geraet.pruefintervallMonate?.toString() ?? "",
|
|
letztePruefung: alsFeldwert(geraet.letztePruefung),
|
|
lebensdauerJahre: geraet.lebensdauerJahre?.toString() ?? "",
|
|
lagerungsart: (geraet.fahrzeugId ? "fahrzeug" : "standort") as "fahrzeug" | "standort",
|
|
standortId: geraet.standortId,
|
|
fahrzeugId: geraet.fahrzeugId,
|
|
geraeteraumId: geraet.geraeteraumId,
|
|
lagerort: geraet.lagerort ?? "",
|
|
bemerkung: geraet.bemerkung ?? "",
|
|
};
|
|
|
|
return (
|
|
<div className="mx-auto max-w-4xl space-y-4">
|
|
<Button variant="ghost" size="sm" asChild className="-ml-2">
|
|
<Link href={`/geraete/${geraet.id}`}>
|
|
<ArrowLeft aria-hidden />
|
|
Zurück zum Gerät
|
|
</Link>
|
|
</Button>
|
|
|
|
<GeraetFormular
|
|
vorgabe={vorgabe}
|
|
titel={geraet.bezeichnung}
|
|
beschreibung={`Inventarnummer ${geraet.inventarnummer}. Jede Änderung wird protokolliert.`}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|