Files
MABEA/frontend/src/pages/KontrollPage.tsx
T
patrickandClaude Sonnet 5 d8e3f3aa21
CI / backend-tests (push) Failing after 1m20s
CI / frontend-build (push) Successful in 24s
Bestückungs-Pflege: Fach-Sidebar + Verwaltung linksbündig statt zentriert
ObjektPositionenPanel: Fach-Sidebar (Vorlagen-Feature "feste Fächer-Liste")
links, damit die Bestückung übersichtlich bleibt und auch Dritte (Vertretung
ohne die Kontrollperson) sehen, wo welches Material im Rucksack hingehört.
Gruppierungslogik aus KontrollPage in gemeinsames Util (utils/fachGruppierung)
ausgelagert, da jetzt zweimal gebraucht.

.page-wide: margin:0 statt zentriert (Nutzer-Vorgabe: bei 1200px sonst viel
verschenkter Platz links/rechts auf breiten Monitoren).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-04 22:51:43 +02:00

144 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useNavigate, useParams } from "react-router-dom";
import { NachfuellDialog } from "./kontrolle/NachfuellDialog";
import { PositionCard } from "./kontrolle/PositionCard";
import { useKontrolle } from "./kontrolle/useKontrolle";
import { gruppiereNachFach, OHNE_FACH } from "../utils/fachGruppierung";
export function KontrollPage() {
const { objektId } = useParams<{ objektId: string }>();
const navigate = useNavigate();
const {
online,
objekt,
sperre,
positionen,
startFehler,
wirdAbgeschlossen,
abschlussFehler,
alleUebertragen,
nachfuellDialog,
starteKontrolle,
eingabeAendern,
positionSenden,
fehlerErneutVersuchen,
abschliessen,
abbrechen,
jetztNachfuellen,
nachfuellDialogSchliessen,
} = useKontrolle(objektId);
const fachGruppen = gruppiereNachFach(positionen, (zustand) => zustand.fach);
const nurOhneFach = fachGruppen.length === 1 && fachGruppen[0][0] === OHNE_FACH;
async function handleAbschliessen() {
if (await abschliessen()) {
navigate("/objekte");
}
}
async function handleAbbrechen() {
const grund = window.prompt("Grund für den Abbruch (optional):") ?? undefined;
await abbrechen(grund);
navigate("/objekte");
}
if (sperre) {
return (
<main className="page">
<h1>Objekt gesperrt</h1>
<div className="card">
<p>
Wird gerade kontrolliert von <strong>{sperre.benutzer ?? "unbekannt"}</strong>, seit {sperre.seit}.
</p>
<button className="btn btn-primary" onClick={() => starteKontrolle(true)}>
Trotzdem übernehmen
</button>
</div>
</main>
);
}
if (startFehler) {
return (
<main className="page">
<div className="alert alert-danger">{startFehler}</div>
<button className="btn btn-secondary" onClick={() => starteKontrolle(false)}>
Erneut versuchen
</button>
</main>
);
}
return (
<main className="page">
<h1>{objekt?.name ?? "Kontrolle"}</h1>
{positionen.length > 0 && (
<div className="card" style={{ marginBottom: "1rem" }}>
<div className="card-subtitle">
{positionen.filter((p) => p.status === "gespeichert").length} von {positionen.length} Positionen bestätigt
</div>
<div style={{ height: "6px", background: "var(--color-border, #e2e8f0)", borderRadius: "3px", marginTop: "0.35rem" }}>
<div
style={{
height: "100%",
borderRadius: "3px",
background: "var(--color-primary, #2563eb)",
width: `${Math.round(
(positionen.filter((p) => p.status === "gespeichert").length / positionen.length) * 100
)}%`,
}}
/>
</div>
</div>
)}
{!online && (
<div className="alert alert-warning">
Kein Netz Eingaben werden lokal gespeichert und automatisch übertragen, sobald wieder Verbindung besteht.
</div>
)}
{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}
/>
)}
</li>
))}
</ul>
</div>
))}
{abschlussFehler && <div className="alert alert-danger">{abschlussFehler}</div>}
<div className="stack">
<button
className="btn btn-primary btn-block"
onClick={handleAbschliessen}
disabled={!alleUebertragen || wirdAbgeschlossen}
>
{alleUebertragen ? "Kontrolle abschließen" : "Noch nicht alle Positionen gespeichert…"}
</button>
<button className="btn btn-danger btn-block" onClick={handleAbbrechen}>
Kontrolle abbrechen
</button>
</div>
</main>
);
}