Files
MABEA/frontend/src/pages/KontrollPage.tsx
T
patrickandClaude Sonnet 5 fbc3b2223b
CI / backend-tests (push) Successful in 2m2s
CI / frontend-build (push) Successful in 18s
feat(ui): UI-006 Abweichungen in der Kontrolle dauerhaft hervorheben
PositionCard bekommt eine farbige linke Randlinie je Zustand (grün
passt, gelb Abweichung/Fehlbestand, rot Übertragungsfehler) - eine
Abweichung war bisher nur während des kurz offenen Nachfüll-Dialogs
sichtbar, danach nicht mehr von einer normalen Bestätigung zu
unterscheiden. Fortschrittsanzeige existierte bereits. Nebenbei
hartkodierte Hex-Fallbackfarben durch Token-Referenzen ersetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-06 21:39:13 +02:00

148 lines
5.0 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,
jetztMindermengeGenehmigen,
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)", borderRadius: "3px", marginTop: "0.35rem" }}>
<div
style={{
height: "100%",
borderRadius: "3px",
background: "var(--color-primary)",
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)}
onMindermengeGenehmigen={(begruendung) =>
jetztMindermengeGenehmigen(nachfuellDialog.fehlbestandId, begruendung)
}
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>
);
}