diff --git a/DEVLOG.md b/DEVLOG.md index 63eb2f3..1277c30 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -3106,3 +3106,88 @@ Keine Commits in dieser Session. - frontend/src/pages/kontrolle/useKontrolle.ts | 13 +++-- --- +## 2026-09-04 20:22 – 20:22 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** backend + +### Commits +- c1f6eaa Kontrollverantwortung im Frontend anbinden (Karte 01) + +### Geänderte Dateien +- DEVLOG.md | 48 ++++++ +- backend/DEVLOG.md | 16 ++ +- backend/app/api/v1/endpoints/zustaendigkeit.py | 10 ++ +- backend/tests/test_zustaendigkeit.py | 45 +++++ +- frontend/src/api/types.ts | 7 + +- frontend/src/pages/AdminPage.tsx | 8 +- +- .../pages/admin/KontrollverantwortungSection.tsx | 185 +++++++++++++++++++++ + +--- +## 2026-09-04 20:23 – 20:23 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 48 ++++++ +- backend/DEVLOG.md | 16 ++ +- backend/app/api/v1/endpoints/zustaendigkeit.py | 10 ++ +- backend/tests/test_zustaendigkeit.py | 45 +++++ +- frontend/src/api/types.ts | 7 + +- frontend/src/pages/AdminPage.tsx | 8 +- +- .../pages/admin/KontrollverantwortungSection.tsx | 185 +++++++++++++++++++++ + +--- +## 2026-09-04 20:23 – 20:24 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 48 ++++++ +- backend/DEVLOG.md | 16 ++ +- backend/app/api/v1/endpoints/zustaendigkeit.py | 10 ++ +- backend/tests/test_zustaendigkeit.py | 45 +++++ +- frontend/src/api/types.ts | 7 + +- frontend/src/pages/AdminPage.tsx | 8 +- +- .../pages/admin/KontrollverantwortungSection.tsx | 185 +++++++++++++++++++++ + +--- +## 2026-09-04 20:24 – 20:24 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 48 ++++++ +- backend/DEVLOG.md | 16 ++ +- backend/app/api/v1/endpoints/zustaendigkeit.py | 10 ++ +- backend/tests/test_zustaendigkeit.py | 45 +++++ +- frontend/src/api/types.ts | 7 + +- frontend/src/pages/AdminPage.tsx | 8 +- +- .../pages/admin/KontrollverantwortungSection.tsx | 185 +++++++++++++++++++++ + +--- +## 2026-09-04 20:24 – 20:25 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 48 ++++++ +- backend/DEVLOG.md | 16 ++ +- backend/app/api/v1/endpoints/zustaendigkeit.py | 10 ++ +- backend/tests/test_zustaendigkeit.py | 45 +++++ +- frontend/src/api/types.ts | 7 + +- frontend/src/pages/AdminPage.tsx | 8 +- +- .../pages/admin/KontrollverantwortungSection.tsx | 185 +++++++++++++++++++++ + +--- diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index bcf3175..90e8103 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -105,6 +105,13 @@ export interface Fach { sortierung: number; } +export interface Zustaendigkeit { + id: string; + benutzer_id: number; + standort_id: number | null; + objekt_id: number | null; +} + export interface Kontrollverantwortung { id: string; objekt_id: number; diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index 36a6f37..3642d7e 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -14,6 +14,7 @@ import { ObjektSection } from "./admin/ObjektSection"; import { ObjekttypSection } from "./admin/ObjekttypSection"; import { StandortSection } from "./admin/StandortSection"; import { VorlageSection } from "./admin/VorlageSection"; +import { ZustaendigkeitSection } from "./admin/ZustaendigkeitSection"; type Tab = | "objekte" @@ -24,7 +25,8 @@ type Tab = | "benutzer" | "historie" | "eskalation" - | "kontrollverantwortung"; + | "kontrollverantwortung" + | "zustaendigkeit"; const TABS: { key: Tab; label: string }[] = [ { key: "objekte", label: "Objekte" }, @@ -33,6 +35,7 @@ const TABS: { key: Tab; label: string }[] = [ { key: "struktur", label: "Struktur" }, { key: "standorte", label: "Standorte" }, { key: "benutzer", label: "Benutzer" }, + { key: "zustaendigkeit", label: "Zuständigkeit" }, { key: "kontrollverantwortung", label: "Kontrollverantwortung" }, { key: "historie", label: "Änderungslog" }, { key: "eskalation", label: "Eskalation" }, @@ -168,6 +171,9 @@ export function AdminPage() { {tab === "benutzer" && ( )} + {tab === "zustaendigkeit" && ( + + )} {tab === "kontrollverantwortung" && ( )} diff --git a/frontend/src/pages/admin/ZustaendigkeitSection.tsx b/frontend/src/pages/admin/ZustaendigkeitSection.tsx new file mode 100644 index 0000000..2e94baa --- /dev/null +++ b/frontend/src/pages/admin/ZustaendigkeitSection.tsx @@ -0,0 +1,194 @@ +import { useEffect, useState } from "react"; + +import { apiRequest } from "../../api/client"; +import type { Benutzer, Objekt, Standort, Zustaendigkeit } from "../../api/types"; + +interface Props { + standorte: Standort[]; + objekte: Objekt[]; + benutzer: Benutzer[]; + onFehler: (text: string) => void; +} + +/** Karte 04: Standort-Zuordnung vererbt sich auf alle Objekte am Standort, + * Objekt-Zuordnung ist feinere Ergänzung obendrauf (Vereinigung, kein Ersatz - + * siehe backend/app/services/zustaendigkeit.py). Kein starres zentral/dezentral, + * Administrator teilt frei zu. */ +export function ZustaendigkeitSection({ standorte, objekte, benutzer, onFehler }: Props) { + const [eintraege, setEintraege] = useState([]); + const [laedt, setLaedt] = useState(true); + const [benutzerId, setBenutzerId] = useState(""); + const [zuordnungsart, setZuordnungsart] = useState<"standort" | "objekt">("standort"); + const [standortId, setStandortId] = useState(""); + const [objektId, setObjektId] = useState(""); + const [wirdAngelegt, setWirdAngelegt] = useState(false); + + async function laden() { + setLaedt(true); + try { + const daten = await apiRequest("/zustaendigkeiten"); + setEintraege(daten); + } catch { + onFehler("Zuständigkeiten konnten nicht geladen werden."); + } finally { + setLaedt(false); + } + } + + useEffect(() => { + laden(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + async function anlegen() { + if (benutzerId === "") { + onFehler("Benutzer auswählen."); + return; + } + setWirdAngelegt(true); + try { + await apiRequest("/zustaendigkeiten", { + method: "POST", + body: { + benutzer_id: benutzerId, + standort_id: zuordnungsart === "standort" ? standortId || null : null, + objekt_id: zuordnungsart === "objekt" ? objektId || null : null, + }, + }); + setStandortId(""); + setObjektId(""); + await laden(); + } catch { + onFehler("Zuständigkeit konnte nicht angelegt werden."); + } finally { + setWirdAngelegt(false); + } + } + + async function loeschen(id: string) { + try { + await apiRequest(`/zustaendigkeiten/${id}`, { method: "DELETE" }); + await laden(); + } catch { + onFehler("Zuständigkeit konnte nicht gelöscht werden."); + } + } + + function benutzerName(id: number): string { + return benutzer.find((b) => b.id === id)?.name ?? `Benutzer ${id}`; + } + + function zielLabel(e: Zustaendigkeit): string { + if (e.standort_id !== null) { + const s = standorte.find((x) => x.id === e.standort_id); + return `Standort: ${s?.name ?? e.standort_id}`; + } + const o = objekte.find((x) => x.id === e.objekt_id); + return `Objekt: ${o ? `${o.name} (${o.code})` : e.objekt_id}`; + } + + const kannAnlegen = + benutzerId !== "" && ((zuordnungsart === "standort" && standortId !== "") || (zuordnungsart === "objekt" && objektId !== "")); + + return ( +
+

Zuständigkeit

+
+

+ Wer ist materialverantwortlich für welchen Standort/welches Objekt. Standort-Zuordnung + gilt für alle Objekte dort, Objekt-Zuordnung ist eine zusätzliche, feinere Ergänzung. +

+
+
+ + +
+
+ + +
+ {zuordnungsart === "standort" ? ( +
+ + +
+ ) : ( +
+ + +
+ )} + +
+ + {laedt &&

Lade…

} + {!laedt && eintraege.length === 0 && ( +

Keine Zuständigkeiten.

+ )} + {!laedt && eintraege.length > 0 && ( +
    + {eintraege.map((e) => ( +
  • + + {benutzerName(e.benutzer_id)} {zielLabel(e)} + + +
  • + ))} +
+ )} +
+
+ ); +}