From 08b10c7c3784d3d1bedf1b280449fce3558ff28d Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 4 Sep 2026 00:40:37 +0200 Subject: [PATCH] Sprint 8: Material anlegen/bearbeiten im Admin-Portal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PATCH /materialien/{id} existierte im Backend bereits (Prompt 07), im Frontend bisher nicht angebunden. Formular dient für Anlage UND Bearbeitung (Klick auf "Bearbeiten" befüllt dasselbe Formular). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt --- frontend/src/api/types.ts | 7 ++ frontend/src/pages/AdminPage.tsx | 160 +++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 7 deletions(-) diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 42c889b..27a5c2c 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -44,8 +44,15 @@ export interface Kontrollposition { export interface Material { id: number; name: string; + artikelnummer: string | null; einheit: string; materialtyp: string; + kategorie_id: number | null; + hersteller: string | null; + beschreibung: string | null; + code: string | null; + warnzeitraum_tage: number | null; + aktiv: boolean; } export interface Standort { diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index 3c56b9f..4d1ed0c 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -1,19 +1,33 @@ import { useEffect, useState } from "react"; import { apiRequest } from "../api/client"; -import type { Beladungsvorlage, Objekt, Standort } from "../api/types"; +import type { Beladungsvorlage, Material, Objekt, Standort } from "../api/types"; + +const MATERIALTYPEN = [ + { value: "standard", label: "Standard" }, + { value: "ablauf_charge", label: "Ablauf/Charge" }, + { value: "geraet_sn", label: "Gerät mit Seriennummer" }, +]; + +const LEERES_MATERIAL_FORMULAR = { + name: "", + artikelnummer: "", + einheit: "Stück", + materialtyp: "standard", +}; /** - * Minimale Admin-Seite (Standort/Objekt-Anlage), nachgezogen weil das - * Frontend bisher nur den Mitarbeiter-Kontrollflow (Sprint 7) abdeckte und - * Stammdaten/Objekte nur per API/Skript angelegt werden konnten. Bewusst - * schlank: keine Bereich-/Kategorie-/Material-/Vorlagen-CRUD, nur was für - * die Pilotphase sofort nötig ist (Vorlagen kommen aus dem Excel-Import). + * Minimale Admin-Seite (Standort/Material/Objekt-Anlage), nachgezogen weil + * das Frontend bisher nur den Mitarbeiter-Kontrollflow (Sprint 7) abdeckte + * und Stammdaten/Objekte nur per API/Skript angelegt werden konnten. Bewusst + * schlank: keine Bereich-/Kategorie-/Vorlagen-CRUD, nur was für die + * Pilotphase sofort nötig ist (Vorlagen kommen aus dem Excel-Import). */ export function AdminPage() { const [standorte, setStandorte] = useState([]); const [vorlagen, setVorlagen] = useState([]); const [objekte, setObjekte] = useState([]); + const [materialien, setMaterialien] = useState([]); const [fehler, setFehler] = useState(null); const [laedt, setLaedt] = useState(true); @@ -26,15 +40,70 @@ export function AdminPage() { const [standortId, setStandortId] = useState(""); const [objektWirdAngelegt, setObjektWirdAngelegt] = useState(false); + const [materialFormular, setMaterialFormular] = useState(LEERES_MATERIAL_FORMULAR); + const [bearbeiteterMaterialId, setBearbeiteterMaterialId] = useState(null); + const [materialWirdGespeichert, setMaterialWirdGespeichert] = useState(false); + async function ladeAlles() { - const [s, v, o] = await Promise.all([ + const [s, v, o, m] = await Promise.all([ apiRequest("/standorte"), apiRequest("/vorlagen"), apiRequest("/objekte"), + apiRequest("/materialien"), ]); setStandorte(s); setVorlagen(v.filter((vorlage) => vorlage.status === "aktiv")); setObjekte(o); + setMaterialien(m); + } + + function materialBearbeiten(material: Material) { + setBearbeiteterMaterialId(material.id); + setMaterialFormular({ + name: material.name, + artikelnummer: material.artikelnummer ?? "", + einheit: material.einheit, + materialtyp: material.materialtyp, + }); + } + + function materialFormularZuruecksetzen() { + setBearbeiteterMaterialId(null); + setMaterialFormular(LEERES_MATERIAL_FORMULAR); + } + + async function materialSpeichern() { + setFehler(null); + setMaterialWirdGespeichert(true); + try { + if (bearbeiteterMaterialId !== null) { + await apiRequest(`/materialien/${bearbeiteterMaterialId}`, { + method: "PATCH", + body: { + name: materialFormular.name, + artikelnummer: materialFormular.artikelnummer || null, + einheit: materialFormular.einheit, + materialtyp: materialFormular.materialtyp, + }, + }); + } else { + await apiRequest("/materialien", { + method: "POST", + body: { + name: materialFormular.name, + artikelnummer: materialFormular.artikelnummer || null, + einheit: materialFormular.einheit, + materialtyp: materialFormular.materialtyp, + }, + }); + } + materialFormularZuruecksetzen(); + await ladeAlles(); + } catch { + setFehler("Material konnte nicht gespeichert werden."); + } finally { + setMaterialWirdGespeichert(false); + } } useEffect(() => { @@ -129,6 +198,83 @@ export function AdminPage() { +
+

Material {bearbeiteterMaterialId !== null ? "bearbeiten" : "anlegen"}

+
+
+
+ + setMaterialFormular({ ...materialFormular, name: e.target.value })} + /> +
+
+
+ + setMaterialFormular({ ...materialFormular, artikelnummer: e.target.value })} + /> +
+
+ + setMaterialFormular({ ...materialFormular, einheit: e.target.value })} + /> +
+
+
+ + +
+
+ + {bearbeiteterMaterialId !== null && ( + + )} +
+
+ + {materialien.length > 0 && ( +
    + {materialien.map((m) => ( +
  • + + {m.name} ({m.einheit}) + + +
  • + ))} +
+ )} +
+
+

Objekt aus Vorlage anlegen