diff --git a/frontend/src/pages/admin/BeladungsplanerBoard.tsx b/frontend/src/pages/admin/BeladungsplanerBoard.tsx new file mode 100644 index 0000000..07f6619 --- /dev/null +++ b/frontend/src/pages/admin/BeladungsplanerBoard.tsx @@ -0,0 +1,131 @@ +import type { Fach, Material } from "../../api/types"; + +interface PositionZeile { + materialId: number | ""; + fach: string; + sollmenge: string; +} + +interface Props { + zeilen: PositionZeile[]; + onZeilenChange: (zeilen: PositionZeile[]) => void; + materialien: Material[]; + faecher: Fach[]; +} + +const OHNE_FACH = "__ohne_fach__"; + +/** Shelf-inspiriert (Nutzer-Vorgabe 2026-09-05: gute Inhalte aus Vergleichsprojekten + * übernehmen): Beladungsplanung per Drag&Drop statt Zeile-für-Zeile-Formular - + * Material aus dem Pool links in eine Fach-Spalte rechts ziehen, weiterziehen + * verschiebt es ins andere Fach, "×" entfernt es wieder in den Pool. */ +export function BeladungsplanerBoard({ zeilen, onZeilenChange, materialien, faecher }: Props) { + const zugewieseneIds = new Set(zeilen.filter((z) => z.materialId !== "").map((z) => z.materialId)); + const pool = materialien.filter((m) => !zugewieseneIds.has(m.id)); + const spalten = [...faecher.map((f) => f.name), OHNE_FACH]; + + function materialName(id: number | ""): string { + if (id === "") return ""; + return materialien.find((m) => m.id === id)?.name ?? `#${id}`; + } + + function onDropAufSpalte(e: React.DragEvent, fachName: string) { + e.preventDefault(); + const materialId = Number(e.dataTransfer.getData("text/plain")); + if (!materialId) return; + const zielFach = fachName === OHNE_FACH ? "" : fachName; + const vorhanden = zeilen.find((z) => z.materialId === materialId); + if (vorhanden) { + onZeilenChange(zeilen.map((z) => (z.materialId === materialId ? { ...z, fach: zielFach } : z))); + } else { + onZeilenChange([...zeilen, { materialId, fach: zielFach, sollmenge: "1" }]); + } + } + + function sollmengeAendern(materialId: number, sollmenge: string) { + onZeilenChange(zeilen.map((z) => (z.materialId === materialId ? { ...z, sollmenge } : z))); + } + + function entfernen(materialId: number) { + onZeilenChange(zeilen.filter((z) => z.materialId !== materialId)); + } + + return ( +
+
e.preventDefault()} + onDrop={(e) => { + // Zurück in den Pool ziehen entfernt die Zuordnung. + e.preventDefault(); + const materialId = Number(e.dataTransfer.getData("text/plain")); + if (materialId) entfernen(materialId); + }} + > +

Material-Pool

+ {pool.length === 0 &&

Alles zugeordnet.

} + +
+ + {spalten.map((spalte) => { + const inhalt = zeilen.filter((z) => (spalte === OHNE_FACH ? !z.fach : z.fach === spalte)); + return ( +
e.preventDefault()} + onDrop={(e) => onDropAufSpalte(e, spalte)} + > +

{spalte === OHNE_FACH ? "Ohne Fach" : spalte}

+ {inhalt.length === 0 && ( +

Material hierher ziehen…

+ )} + +
+ ); + })} +
+ ); +} diff --git a/frontend/src/pages/admin/VorlageSection.tsx b/frontend/src/pages/admin/VorlageSection.tsx index 960d901..1e09775 100644 --- a/frontend/src/pages/admin/VorlageSection.tsx +++ b/frontend/src/pages/admin/VorlageSection.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { apiRequest } from "../../api/client"; import type { Beladungsvorlage, Fach, Material, Objekttyp } from "../../api/types"; +import { BeladungsplanerBoard } from "./BeladungsplanerBoard"; import { FachAuswahl } from "./FachAuswahl"; interface PositionZeile { @@ -41,10 +42,6 @@ export function VorlageSection({ vorlagen, objekttypen, materialien, faecher, on ); } - function neueVersionZeileAendern(index: number, zeile: Partial) { - setNeueVersionZeilen((bisherige) => bisherige.map((z, i) => (i === index ? { ...z, ...zeile } : z))); - } - async function positionenSpeichern(vorlageId: number) { const positionen = neueVersionZeilen .filter((z) => z.materialId !== "") @@ -177,42 +174,12 @@ export function VorlageSection({ vorlagen, objekttypen, materialien, faecher, on Objekte behalten ihre bisherige Sollmenge – nur neu angelegte Objekte nutzen die aktualisierten Werte.

- {neueVersionZeilen.map((zeile, i) => ( -
- - f.objekttyp_id === v.objekttyp_id)} - wert={zeile.fach} - onAendern={(wert) => neueVersionZeileAendern(i, { fach: wert })} - /> - neueVersionZeileAendern(i, { sollmenge: e.target.value })} - /> -
- ))} - + f.objekttyp_id === v.objekttyp_id)} + />