Kontroll-Erfassung: Positionen nach Fach gruppieren
CI / backend-tests (push) Successful in 1m11s
CI / frontend-build (push) Successful in 26s

useKontrolle lädt zusätzlich die Vorlage und mappt Fach je Material.
KontrollPage gruppiert die Erfassung nach Fach (Vorlagen-Reihenfolge,
"Ohne Fach" zuletzt, keine Überschrift falls alles ohne Fach) statt flacher
Liste - näher am Regal-/Fahrzeug-Layout. E2E-Mocks um Vorlage-Route ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-04 20:13:38 +02:00
co-authored by Claude Sonnet 5
parent 5805366306
commit fbae3fccc7
6 changed files with 125 additions and 25 deletions
+57 -21
View File
@@ -2,8 +2,36 @@ import { useNavigate, useParams } from "react-router-dom";
import { NachfuellDialog } from "./kontrolle/NachfuellDialog";
import { PositionCard } from "./kontrolle/PositionCard";
import type { PositionZustand } from "./kontrolle/types";
import { useKontrolle } from "./kontrolle/useKontrolle";
const OHNE_FACH = "Ohne Fach";
// Gruppierung nach Fach (Vorlagen-Feature "feste Fächer-Liste je Objekttyp"):
// Reihenfolge folgt dem ersten Auftreten in der Positionsliste (die wiederum
// die Vorlagen-Reihenfolge widerspiegelt), "Ohne Fach" immer zuletzt - so
// bleibt die Erfassung nah am Regal/Fahrzeug-Layout statt alphabetisch zu
// springen.
function gruppiereNachFach(positionen: PositionZustand[]): [string, PositionZustand[]][] {
const gruppen = new Map<string, PositionZustand[]>();
for (const zustand of positionen) {
const fach = zustand.fach ?? OHNE_FACH;
const liste = gruppen.get(fach);
if (liste) {
liste.push(zustand);
} else {
gruppen.set(fach, [zustand]);
}
}
const eintraege = [...gruppen.entries()];
eintraege.sort(([a], [b]) => {
if (a === OHNE_FACH) return 1;
if (b === OHNE_FACH) return -1;
return 0;
});
return eintraege;
}
export function KontrollPage() {
const { objektId } = useParams<{ objektId: string }>();
const navigate = useNavigate();
@@ -27,6 +55,9 @@ export function KontrollPage() {
nachfuellDialogSchliessen,
} = useKontrolle(objektId);
const fachGruppen = gruppiereNachFach(positionen);
const nurOhneFach = fachGruppen.length === 1 && fachGruppen[0][0] === OHNE_FACH;
async function handleAbschliessen() {
if (await abschliessen()) {
navigate("/objekte");
@@ -93,28 +124,33 @@ export function KontrollPage() {
Kein Netz Eingaben werden lokal gespeichert und automatisch übertragen, sobald wieder Verbindung besteht.
</div>
)}
<ul className="card-list" style={{ marginBottom: "1.25rem" }}>
{positionen.map((zustand) => (
<li key={zustand.position.id} style={{ listStyle: "none" }}>
<PositionCard
zustand={zustand}
onEingabeAendern={eingabeAendern}
onSenden={positionSenden}
onErneutVersuchen={fehlerErneutVersuchen}
/>
{nachfuellDialog?.materialId === zustand.position.material_id && (
<NachfuellDialog
materialName={zustand.material?.name ?? "Material"}
vorschlagMenge={String(
Number(zustand.position.sollmenge_effektiv) - Number(zustand.eingabe)
{fachGruppen.map(([fach, gruppenPositionen]) => (
<div key={fach} style={{ marginBottom: "1.25rem" }}>
{!nurOhneFach && <h2 style={{ fontSize: "1rem", marginBottom: "0.5rem" }}>{fach}</h2>}
<ul className="card-list">
{gruppenPositionen.map((zustand) => (
<li key={zustand.position.id} style={{ listStyle: "none" }}>
<PositionCard
zustand={zustand}
onEingabeAendern={eingabeAendern}
onSenden={positionSenden}
onErneutVersuchen={fehlerErneutVersuchen}
/>
{nachfuellDialog?.materialId === zustand.position.material_id && (
<NachfuellDialog
materialName={zustand.material?.name ?? "Material"}
vorschlagMenge={String(
Number(zustand.position.sollmenge_effektiv) - Number(zustand.eingabe)
)}
onNachfuellen={(menge) => jetztNachfuellen(nachfuellDialog.fehlbestandId, menge)}
onSchliessen={nachfuellDialogSchliessen}
/>
)}
onNachfuellen={(menge) => jetztNachfuellen(nachfuellDialog.fehlbestandId, menge)}
onSchliessen={nachfuellDialogSchliessen}
/>
)}
</li>
))}
</ul>
</li>
))}
</ul>
</div>
))}
{abschlussFehler && <div className="alert alert-danger">{abschlussFehler}</div>}
<div className="stack">