Die Stammdaten, an denen die Historie haengt. Personen werden deaktiviert, Geraete und Abteilungen archiviert — geloescht wird nichts, woran ein Lauf haengen kann. - Mitarbeiterliste mit Suche ueber Anzeigename, Mail, UPN und sAMAccountName, Filter nach Abteilung, standardmaessig nur aktive; die Suche ist ein eigenes Bauteil, weil #013 und #014 sie wieder brauchen - Personenseite mit Stammdaten, Geraeten, Vorgesetztem; was aus dem Verzeichnis kommt, ist gesperrt — der naechste Abgleich wuerde es ohnehin zurueckdrehen - Abteilungen unter Admin: Ziel-OU, Standardgruppen, Mail-Domain, dazu die Namen, die bei Personen vorkommen, aber nicht gepflegt sind - Geraete mit Suche ueber Hostname, Bezeichnung, Seriennummer und Tag-Code; CSV-Import mit einem Ergebnis je Zeile - Neues Recht mitarbeiterBearbeiten; die Seitenleiste fuehrt Personen und Geraete unter einem Eintrag, im Modus Dienstleistung anders beschriftet Der unabhaengige Review fand zwei Blocker. Der schwerere meldete Erfolg: ein nicht geschlossenes Anfuehrungszeichen verschluckte im CSV-Import alle Folgezeilen bis zum naechsten `"`, und der Import sagte „3 von 3 Zeilen angelegt". Ein Wert reicht jetzt nicht mehr ueber die Zeile hinaus — fuer Hostnamen und Seriennummern ist ein Umbruch praktisch immer ein vergessenes Anfuehrungszeichen, und so endet der Schaden an seiner Zeile. Der zweite: ungepflegte Abteilungen wurden ueber alle Kunden hinweg gruppiert. Dazu sieben weitere Befunde, alle behoben — darunter eine Besitzerauswahl, die Personen fremder Kunden anbot, und ein Techniker, der ein Geraet endgueltig loeschen konnte. 610 Tests gruen, 18 E2E-Ablaeufe gegen den gebauten Stand. Schliesst #011 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
"use client";
|
||
|
||
import { useActionState } from "react";
|
||
import { Rolle } from "@/generated/prisma/enums";
|
||
import { Button } from "@/components/ui/button";
|
||
import { ROLLEN_TEXT } from "@/lib/rollen-text";
|
||
import { benutzerAnlegenAktion, type AktionsErgebnis } from "@/server/actions/benutzer";
|
||
import { FELD } from "@/lib/formular";
|
||
|
||
/**
|
||
* Formular fuer ein lokales Konto. Als Client-Komponente, weil Erfolg und Fehler
|
||
* ohne Neuladen an Ort und Stelle erscheinen sollen – die Rechtepruefung liegt
|
||
* trotzdem ausschliesslich serverseitig in der Aktion.
|
||
*/
|
||
export function BenutzerAnlegen({ aktiv }: { aktiv: boolean }) {
|
||
const [ergebnis, aktion, laeuft] = useActionState<AktionsErgebnis, FormData>(benutzerAnlegenAktion, {});
|
||
|
||
if (!aktiv) {
|
||
return (
|
||
<p className="text-sm text-nebentext">
|
||
Der lokale Login ist abgeschaltet (<code className="mono">AUTH_LOCAL_ENABLED</code>). Neue Konten
|
||
entstehen beim ersten Anmelden über den Anbieter von selbst.
|
||
</p>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<form action={aktion} className="flex flex-col gap-4">
|
||
<div className="grid gap-4 sm:grid-cols-2">
|
||
<label className="flex flex-col gap-1.5 text-sm">
|
||
<span className="text-nebentext">Name</span>
|
||
<input name="name" required className={FELD} />
|
||
</label>
|
||
<label className="flex flex-col gap-1.5 text-sm">
|
||
<span className="text-nebentext">E-Mail</span>
|
||
<input type="email" name="email" required className={FELD} />
|
||
</label>
|
||
<label className="flex flex-col gap-1.5 text-sm">
|
||
<span className="text-nebentext">Rolle</span>
|
||
<select name="rolle" defaultValue={Rolle.TECHNIKER} className={FELD}>
|
||
{Object.values(Rolle).map((rolle) => (
|
||
<option key={rolle} value={rolle}>
|
||
{ROLLEN_TEXT[rolle]}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
<label className="flex flex-col gap-1.5 text-sm">
|
||
<span className="text-nebentext">Passwort (mindestens 12 Zeichen)</span>
|
||
<input
|
||
type="password"
|
||
name="passwort"
|
||
minLength={12}
|
||
required
|
||
autoComplete="new-password"
|
||
className={FELD}
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
{ergebnis.fehler ? (
|
||
<p role="alert" className="text-sm text-fehler">
|
||
{ergebnis.fehler}
|
||
</p>
|
||
) : null}
|
||
{ergebnis.erfolg ? (
|
||
<p role="status" className="text-sm text-erledigt">
|
||
{ergebnis.erfolg}
|
||
</p>
|
||
) : null}
|
||
|
||
<Button
|
||
type="submit"
|
||
disabled={laeuft}
|
||
className="h-11 self-start rounded-pille px-5 text-sm font-semibold"
|
||
>
|
||
{laeuft ? "Wird angelegt …" : "Konto anlegen"}
|
||
</Button>
|
||
</form>
|
||
);
|
||
}
|