Objekttyp.ist_zugfahrzeug (Migration 0011) grenzt die Fahrzeug-Zuordnung serverseitig auf tatsächliche Zugfahrzeuge ein (422 bei Verstoß), Frontend filtert die Fahrzeug-Dropdowns entsprechend. Neue Dashboard-Endpunkte /dashboard/einsatzbereitschaft und /dashboard/prueftermine plus DashboardPage mit drei Kacheln (Einsatzbereitschaft, Prüftermine, Ablaufdaten). Aktive Mindermengen-Genehmigung zählt als "eingeschränkt einsatzbereit", nicht als voll einsatzbereit (U2/Karte 08). Neue FehlbestandListePage: Fehlbestände eigenständig nachfüllen/Mindermenge genehmigen, ohne komplette Kontrolle des Objekts zu starten (Backend existierte bereits, nur das Frontend fehlte). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
import { useState } from "react";
|
|
|
|
import { apiRequest } from "../../api/client";
|
|
import type { Bereich, Kategorie, Objekttyp } from "../../api/types";
|
|
|
|
interface Props {
|
|
bereiche: Bereich[];
|
|
kategorien: Kategorie[];
|
|
objekttypen: Objekttyp[];
|
|
onGeaendert: () => Promise<void>;
|
|
onFehler: (text: string) => void;
|
|
}
|
|
|
|
export function ObjekttypSection({ bereiche, kategorien, objekttypen, onGeaendert, onFehler }: Props) {
|
|
const [name, setName] = useState("");
|
|
const [bereichId, setBereichId] = useState<number | "">("");
|
|
const [kategorieId, setKategorieId] = useState<number | "">("");
|
|
const [wirdAngelegt, setWirdAngelegt] = useState(false);
|
|
const [bearbeiteteId, setBearbeiteteId] = useState<number | null>(null);
|
|
const [bearbeitungsName, setBearbeitungsName] = useState("");
|
|
const [istZugfahrzeug, setIstZugfahrzeug] = useState(false);
|
|
const [bearbeitungsZugfahrzeug, setBearbeitungsZugfahrzeug] = useState(false);
|
|
|
|
function bearbeiten(o: Objekttyp) {
|
|
setBearbeiteteId(o.id);
|
|
setBearbeitungsName(o.name);
|
|
setBearbeitungsZugfahrzeug(o.ist_zugfahrzeug);
|
|
}
|
|
|
|
async function speichern(id: number) {
|
|
try {
|
|
await apiRequest(`/objekttypen/${id}`, {
|
|
method: "PATCH",
|
|
body: { name: bearbeitungsName, ist_zugfahrzeug: bearbeitungsZugfahrzeug },
|
|
});
|
|
setBearbeiteteId(null);
|
|
await onGeaendert();
|
|
} catch {
|
|
onFehler("Objekttyp konnte nicht geändert werden.");
|
|
}
|
|
}
|
|
|
|
async function anlegen() {
|
|
if (bereichId === "") {
|
|
onFehler("Bereich auswählen.");
|
|
return;
|
|
}
|
|
setWirdAngelegt(true);
|
|
try {
|
|
await apiRequest("/objekttypen", {
|
|
method: "POST",
|
|
body: { name, bereich_id: bereichId, kategorie_id: kategorieId || null, ist_zugfahrzeug: istZugfahrzeug },
|
|
});
|
|
setName("");
|
|
setIstZugfahrzeug(false);
|
|
await onGeaendert();
|
|
} catch {
|
|
onFehler("Objekttyp konnte nicht angelegt werden.");
|
|
} finally {
|
|
setWirdAngelegt(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section className="section">
|
|
<h2>Objekttyp anlegen</h2>
|
|
<div className="card">
|
|
{bereiche.length === 0 ? (
|
|
<p className="text-muted">Erst einen Bereich anlegen.</p>
|
|
) : (
|
|
<div className="stack" style={{ marginBottom: objekttypen.length ? "1.25rem" : 0 }}>
|
|
<div className="row">
|
|
<select className="input" value={bereichId} onChange={(e) => setBereichId(Number(e.target.value) || "")}>
|
|
<option value="">Bereich wählen…</option>
|
|
{bereiche.map((b) => (
|
|
<option key={b.id} value={b.id}>
|
|
{b.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<select className="input" value={kategorieId} onChange={(e) => setKategorieId(Number(e.target.value) || "")}>
|
|
<option value="">Kategorie (optional)…</option>
|
|
{kategorien.map((k) => (
|
|
<option key={k.id} value={k.id}>
|
|
{k.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="row">
|
|
<input className="input" value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
|
|
<label className="row" style={{ alignItems: "center", gap: "0.4rem" }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={istZugfahrzeug}
|
|
onChange={(e) => setIstZugfahrzeug(e.target.checked)}
|
|
/>
|
|
Zugfahrzeug (koppelbar)
|
|
</label>
|
|
<button className="btn btn-primary" onClick={anlegen} disabled={!name || wirdAngelegt}>
|
|
Anlegen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{objekttypen.length > 0 && (
|
|
<ul className="card-list">
|
|
{objekttypen.map((o) => (
|
|
<li key={o.id} className="row-between" style={{ padding: "0.4rem 0" }}>
|
|
{bearbeiteteId === o.id ? (
|
|
<div className="row" style={{ flex: 1 }}>
|
|
<input
|
|
className="input"
|
|
value={bearbeitungsName}
|
|
onChange={(e) => setBearbeitungsName(e.target.value)}
|
|
/>
|
|
<label className="row" style={{ alignItems: "center", gap: "0.4rem" }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={bearbeitungsZugfahrzeug}
|
|
onChange={(e) => setBearbeitungsZugfahrzeug(e.target.checked)}
|
|
/>
|
|
Zugfahrzeug
|
|
</label>
|
|
<button className="btn btn-primary" onClick={() => speichern(o.id)}>
|
|
Speichern
|
|
</button>
|
|
<button className="btn btn-secondary" onClick={() => setBearbeiteteId(null)}>
|
|
Abbrechen
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<span>
|
|
{o.name}
|
|
{o.ist_zugfahrzeug && <span className="text-muted"> (Zugfahrzeug)</span>}
|
|
</span>
|
|
<button className="btn btn-secondary" onClick={() => bearbeiten(o)}>
|
|
Bearbeiten
|
|
</button>
|
|
</>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|