feat(personal): Personal/Qualifikationen-Modul (Roadmap Phase 1)
Neues Modul für die BOS-Ressourcenplattform-Erweiterung: Einheit (Zug/Gruppe,
self-referenzierend), Qualifikationstyp (Führerschein/Lehrgang/Berechtigung),
BenutzerQualifikation (mit Gültigkeit) und ObjekttypQualifikationsanforderung
(M:N) - beantwortet "wer darf dieses Fahrzeug fahren?" über
GET /objekte/{id}/berechtigung/{benutzer_id}.
Benutzer und Objekt bekommen optionale einheit_id (additiv, analog
fahrzeug_id-Muster). Nebenbei Bugfix: PATCH /benutzer konnte einheit_id nicht
auf null setzen (Feld-vorhanden-Check via model_fields_set statt is not None).
Frontend: neuer Admin-Tab "Personal" (Einheiten, Qualifikationstypen,
Benutzer-Qualifikationszuordnung), Einheit-Auswahl im Benutzer-Formular.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
@@ -2,9 +2,10 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { apiRequest } from "../api/client";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import type { Benutzer, Bereich, Beladungsvorlage, Fach, Kategorie, Material, Objekt, Objekttyp, Standort } from "../api/types";
|
||||
import type { Benutzer, Bereich, Beladungsvorlage, Einheit, Fach, Kategorie, Material, Objekt, Objekttyp, Qualifikationstyp, Standort } from "../api/types";
|
||||
import { BenutzerSection } from "./admin/BenutzerSection";
|
||||
import { BereichSection } from "./admin/BereichSection";
|
||||
import { EinheitSection } from "./admin/EinheitSection";
|
||||
import { EskalationSection } from "./admin/EskalationSection";
|
||||
import { FachSection } from "./admin/FachSection";
|
||||
import { HistorieSection } from "./admin/HistorieSection";
|
||||
@@ -13,6 +14,7 @@ import { KontrollverantwortungSection } from "./admin/KontrollverantwortungSecti
|
||||
import { MaterialSection } from "./admin/MaterialSection";
|
||||
import { ObjektSection } from "./admin/ObjektSection";
|
||||
import { ObjekttypSection } from "./admin/ObjekttypSection";
|
||||
import { QualifikationSection } from "./admin/QualifikationSection";
|
||||
import { StandortSection } from "./admin/StandortSection";
|
||||
import { VorlageSection } from "./admin/VorlageSection";
|
||||
import { ZustaendigkeitSection } from "./admin/ZustaendigkeitSection";
|
||||
@@ -27,7 +29,8 @@ type Tab =
|
||||
| "historie"
|
||||
| "eskalation"
|
||||
| "kontrollverantwortung"
|
||||
| "zustaendigkeit";
|
||||
| "zustaendigkeit"
|
||||
| "personal";
|
||||
|
||||
// Deckt sich mit den require_roles()-Gates der jeweiligen Backend-Endpunkte
|
||||
// (Rollen-Vorgabe: Material-/Leitungsverantwortliche sahen bisher exakt
|
||||
@@ -52,6 +55,7 @@ const TABS: { key: Tab; label: string; rollen: ("administration" | "materialvera
|
||||
rollen: ["administration", "materialverantwortlicher", "leitungsverantwortlicher"],
|
||||
},
|
||||
{ key: "eskalation", label: "Eskalation", rollen: ["administration"] },
|
||||
{ key: "personal", label: "Personal", rollen: ["administration"] },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -75,6 +79,8 @@ export function AdminPage() {
|
||||
const [materialien, setMaterialien] = useState<Material[]>([]);
|
||||
const [benutzer, setBenutzer] = useState<Benutzer[]>([]);
|
||||
const [faecher, setFaecher] = useState<Fach[]>([]);
|
||||
const [einheiten, setEinheiten] = useState<Einheit[]>([]);
|
||||
const [qualifikationstypen, setQualifikationstypen] = useState<Qualifikationstyp[]>([]);
|
||||
const [fehler, setFehler] = useState<string | null>(null);
|
||||
const [laedt, setLaedt] = useState(true);
|
||||
const [tab, setTab] = useState<Tab | null>(null);
|
||||
@@ -86,7 +92,7 @@ export function AdminPage() {
|
||||
// Seite als "nicht geladen" zeigen, obwohl deren Tabs (z.B. Kontroll-
|
||||
// verantwortung) trotzdem funktionieren sollen. allSettled + leerer
|
||||
// Fallback statt hartem Fehler.
|
||||
const [be, ka, ot, s, v, o, m, b, f] = await Promise.allSettled([
|
||||
const [be, ka, ot, s, v, o, m, b, f, ei, qt] = await Promise.allSettled([
|
||||
apiRequest<Bereich[]>("/bereiche"),
|
||||
apiRequest<Kategorie[]>("/kategorien"),
|
||||
apiRequest<Objekttyp[]>("/objekttypen"),
|
||||
@@ -96,6 +102,8 @@ export function AdminPage() {
|
||||
apiRequest<Material[]>("/materialien"),
|
||||
apiRequest<Benutzer[]>("/benutzer"),
|
||||
apiRequest<Fach[]>("/faecher"),
|
||||
apiRequest<Einheit[]>("/einheiten"),
|
||||
apiRequest<Qualifikationstyp[]>("/qualifikationstypen"),
|
||||
]);
|
||||
const wert = <T,>(r: PromiseSettledResult<T[]>): T[] => (r.status === "fulfilled" ? r.value : []);
|
||||
setBereiche(wert(be));
|
||||
@@ -107,6 +115,8 @@ export function AdminPage() {
|
||||
setMaterialien(wert(m));
|
||||
setBenutzer(wert(b));
|
||||
setFaecher(wert(f));
|
||||
setEinheiten(wert(ei));
|
||||
setQualifikationstypen(wert(qt));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -193,7 +203,7 @@ export function AdminPage() {
|
||||
<StandortSection standorte={standorte} onGeaendert={ladeAlles} onFehler={setFehler} />
|
||||
)}
|
||||
{aktiverTab === "benutzer" && (
|
||||
<BenutzerSection benutzer={benutzer} onGeaendert={ladeAlles} onFehler={setFehler} />
|
||||
<BenutzerSection benutzer={benutzer} einheiten={einheiten} onGeaendert={ladeAlles} onFehler={setFehler} />
|
||||
)}
|
||||
{aktiverTab === "zustaendigkeit" && (
|
||||
<ZustaendigkeitSection standorte={standorte} objekte={objekte} benutzer={benutzer} onFehler={setFehler} />
|
||||
@@ -203,6 +213,17 @@ export function AdminPage() {
|
||||
)}
|
||||
{aktiverTab === "historie" && <HistorieSection onFehler={setFehler} />}
|
||||
{aktiverTab === "eskalation" && <EskalationSection onFehler={setFehler} />}
|
||||
{aktiverTab === "personal" && (
|
||||
<>
|
||||
<EinheitSection einheiten={einheiten} standorte={standorte} onGeaendert={ladeAlles} onFehler={setFehler} />
|
||||
<QualifikationSection
|
||||
qualifikationstypen={qualifikationstypen}
|
||||
benutzer={benutzer}
|
||||
onGeaendert={ladeAlles}
|
||||
onFehler={setFehler}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{aktiverTab === null && <p className="text-muted">Keine Bereiche für deine Rolle verfügbar.</p>}
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user