refactor(admin): Objekte-Verwaltung entschlackt, Akte statt Redundanz
CI / backend-tests (push) Failing after 2m7s
CI / frontend-build (push) Successful in 18s

Fahrzeugdetails/Dokumente/Positionen-Pflege liefen hier inline parallel
zur neuen Akte-Ansicht (Epic 21) - doppelte Pflege, unübersichtlich
(599 Zeilen, 6 Buttons je Zeile). Jetzt Link "Akte ansehen" statt
eigenem Fahrzeugdetails-Formular/Dokumente-Panel/Positionen-Panel hier.
Verbleibende Verwaltungsaufgaben (Anlegen/Status/Verschieben/Fahrzeug
koppeln/Duplizieren) bleiben. Anlegen-Formular in eigene Komponente
ausgelagert (599 → 340 Zeilen + 145 ausgelagert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-07 00:36:12 +02:00
co-authored by Claude Sonnet 5
parent 456fc2963e
commit 1c2f2d38e0
2 changed files with 298 additions and 412 deletions
@@ -0,0 +1,145 @@
import { useState } from "react";
import { apiRequest } from "../../api/client";
import type { Beladungsvorlage, Objekt, Objekttyp, Standort } from "../../api/types";
interface Props {
objekte: Objekt[];
objekttypen: Objekttyp[];
vorlagen: Beladungsvorlage[];
standorte: Standort[];
onGeaendert: () => Promise<void>;
onFehler: (text: string) => void;
}
export function ObjektAnlegenFormular({ objekte, objekttypen, vorlagen, standorte, onGeaendert, onFehler }: Props) {
const [praefix, setPraefix] = useState("");
const [code, setCode] = useState("");
const [name, setName] = useState("");
const [vorlageId, setVorlageId] = useState<number | "">("");
const [standortId, setStandortId] = useState<number | "">("");
const [fahrzeugId, setFahrzeugId] = useState<number | "">("");
const [wirdAngelegt, setWirdAngelegt] = useState(false);
// Option A+C (Karte 11 Nachtrag): Dropdown zeigt nur Zugfahrzeuge (Objekttyp-
// Flag ist_zugfahrzeug) ohne eigene Kopplung - serverseitig zusätzlich per
// 422 erzwungen, das ist hier nur die Vorauswahl fürs Formular.
const zugfahrzeuge = objekte.filter((x) => {
const typ = objekttypen.find((t) => t.id === x.objekttyp_id);
return typ?.ist_zugfahrzeug && x.fahrzeug_id === null;
});
async function codeVorschlagen() {
if (!praefix) {
onFehler("Präfix eingeben (z. B. RK, FZ).");
return;
}
try {
const antwort = await apiRequest<{ code: string }>(
`/objekte/naechster-code?praefix=${encodeURIComponent(praefix.toUpperCase())}`
);
setCode(antwort.code);
} catch {
onFehler("Code-Vorschlag fehlgeschlagen.");
}
}
async function anlegen() {
if (vorlageId === "" || standortId === "") {
onFehler("Vorlage und Standort auswählen.");
return;
}
setWirdAngelegt(true);
const vorlage = vorlagen.find((v) => v.id === vorlageId);
try {
await apiRequest("/objekte", {
method: "POST",
body: {
code,
name,
objekttyp_id: vorlage?.objekttyp_id,
vorlage_id: vorlageId,
standort_id: standortId,
fahrzeug_id: fahrzeugId === "" ? null : fahrzeugId,
},
});
setPraefix("");
setCode("");
setName("");
setFahrzeugId("");
await onGeaendert();
} catch {
onFehler("Objekt konnte nicht angelegt werden (Code evtl. schon vergeben).");
} finally {
setWirdAngelegt(false);
}
}
return (
<div className="card split-layout-sidebar">
<h3 style={{ marginTop: 0 }}>Objekt aus Vorlage anlegen</h3>
{vorlagen.length === 0 ? (
<p className="text-muted">Keine aktive Vorlage vorhanden erst Excel-Import oder Vorlage per API anlegen.</p>
) : (
<div className="stack">
<div className="field">
<label>Vorlage</label>
<select className="input" value={vorlageId} onChange={(e) => setVorlageId(Number(e.target.value) || "")}>
<option value="">Vorlage wählen</option>
{vorlagen.map((v) => (
<option key={v.id} value={v.id}>
{v.name} (v{v.version})
</option>
))}
</select>
</div>
<div className="field">
<label>Standort</label>
<select className="input" value={standortId} onChange={(e) => setStandortId(Number(e.target.value) || "")}>
<option value="">Standort wählen</option>
{standorte.map((s) => (
<option key={s.id} value={s.id}>
{s.name}
</option>
))}
</select>
</div>
<div className="field">
<label>Code (eindeutig)</label>
<div className="row" style={{ flexWrap: "wrap" }}>
<input
className="input"
style={{ maxWidth: "6rem" }}
value={praefix}
onChange={(e) => setPraefix(e.target.value)}
placeholder="Präfix (RK, FZ…)"
/>
<button type="button" className="btn btn-secondary" onClick={codeVorschlagen}>
Code vorschlagen
</button>
<input className="input" value={code} onChange={(e) => setCode(e.target.value)} />
</div>
</div>
<div className="field">
<label>Name</label>
<input className="input" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="field">
<label>Fahrzeug (optional)</label>
<select className="input" value={fahrzeugId} onChange={(e) => setFahrzeugId(Number(e.target.value) || "")}>
<option value="">Kein Fahrzeug</option>
{zugfahrzeuge.map((x) => (
<option key={x.id} value={x.id}>
{x.name} ({x.code})
</option>
))}
</select>
</div>
<button className="btn btn-primary" onClick={anlegen} disabled={!code || !name || wirdAngelegt}>
Anlegen
</button>
</div>
)}
</div>
);
}
+153 -412
View File
@@ -1,9 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom";
import { apiRequest } from "../../api/client"; import { apiRequest } from "../../api/client";
import type { Beladungsvorlage, Fahrzeugdetails, Material, Objekt, Objekttyp, Standort } from "../../api/types"; import type { Beladungsvorlage, Material, Objekt, Objekttyp, Standort } from "../../api/types";
import { DokumentePanel } from "../../components/DokumentePanel"; import { ObjektAnlegenFormular } from "./ObjektAnlegenFormular";
import { ObjektPositionenPanel } from "./ObjektPositionenPanel";
const STATUS_OPTIONEN = [ const STATUS_OPTIONEN = [
{ wert: "aktiv", label: "Aktiv" }, { wert: "aktiv", label: "Aktiv" },
@@ -21,7 +21,8 @@ interface Props {
// (Ablauf/Charge/SN/Prüfintervall/Geräte-Instanzen), nicht Objekte anlegen/ // (Ablauf/Charge/SN/Prüfintervall/Geräte-Instanzen), nicht Objekte anlegen/
// duplizieren/koppeln (require_roles(administration) dort) - Anlegen-Formular // duplizieren/koppeln (require_roles(administration) dort) - Anlegen-Formular
// und Duplizieren/Kopplung blenden wir entsprechend aus statt sie ins Leere // und Duplizieren/Kopplung blenden wir entsprechend aus statt sie ins Leere
// laufen zu lassen (403). // laufen zu lassen (403). Feinpflege (Ablauf/Charge/SN) läuft jetzt über die
// Akte, nicht mehr hier - "nurPflegen" hat daher hier nichts mehr zu tun.
nurPflegen?: boolean; nurPflegen?: boolean;
onGeaendert: () => Promise<void>; onGeaendert: () => Promise<void>;
onFehler: (text: string) => void; onFehler: (text: string) => void;
@@ -32,20 +33,10 @@ export function ObjektSection({
objekttypen, objekttypen,
vorlagen, vorlagen,
standorte, standorte,
materialien,
nurPflegen = false, nurPflegen = false,
onGeaendert, onGeaendert,
onFehler, onFehler,
}: Props) { }: Props) {
const [praefix, setPraefix] = useState("");
const [code, setCode] = useState("");
const [name, setName] = useState("");
const [vorlageId, setVorlageId] = useState<number | "">("");
const [standortId, setStandortId] = useState<number | "">("");
const [fahrzeugId, setFahrzeugId] = useState<number | "">("");
const [wirdAngelegt, setWirdAngelegt] = useState(false);
const [ausgeklapptId, setAusgeklapptId] = useState<number | null>(null);
const [dokumenteOffenId, setDokumenteOffenId] = useState<number | null>(null);
const [duplizierId, setDuplizierId] = useState<number | null>(null); const [duplizierId, setDuplizierId] = useState<number | null>(null);
const [neuerPraefix, setNeuerPraefix] = useState(""); const [neuerPraefix, setNeuerPraefix] = useState("");
const [neuerCode, setNeuerCode] = useState(""); const [neuerCode, setNeuerCode] = useState("");
@@ -54,8 +45,6 @@ export function ObjektSection({
const [fahrzeugBearbeitenId, setFahrzeugBearbeitenId] = useState<number | null>(null); const [fahrzeugBearbeitenId, setFahrzeugBearbeitenId] = useState<number | null>(null);
const [fahrzeugAuswahl, setFahrzeugAuswahl] = useState<number | "">(""); const [fahrzeugAuswahl, setFahrzeugAuswahl] = useState<number | "">("");
const [suche, setSuche] = useState(""); const [suche, setSuche] = useState("");
const [detailsOffenId, setDetailsOffenId] = useState<number | null>(null);
const [details, setDetails] = useState<Partial<Fahrzeugdetails>>({});
const [verschiebenId, setVerschiebenId] = useState<number | null>(null); const [verschiebenId, setVerschiebenId] = useState<number | null>(null);
const [zielStandortId, setZielStandortId] = useState<number | "">(""); const [zielStandortId, setZielStandortId] = useState<number | "">("");
const [verschiebenGrund, setVerschiebenGrund] = useState(""); const [verschiebenGrund, setVerschiebenGrund] = useState("");
@@ -96,45 +85,6 @@ export function ObjektSection({
} }
} }
async function fahrzeugdetailsOeffnen(o: Objekt) {
if (detailsOffenId === o.id) {
setDetailsOffenId(null);
return;
}
setDetailsOffenId(o.id);
try {
const daten = await apiRequest<Fahrzeugdetails>(`/objekte/${o.id}/fahrzeugdetails`);
setDetails(daten);
} catch {
setDetails({});
}
}
async function fahrzeugdetailsSpeichern(objektId: number) {
try {
await apiRequest(`/objekte/${objektId}/fahrzeugdetails`, {
method: "PUT",
body: {
kennzeichen: details.kennzeichen || null,
funkrufname: details.funkrufname || null,
funkkenner: details.funkkenner || null,
baujahr: details.baujahr || null,
kilometerstand: details.kilometerstand || null,
betriebsstunden: details.betriebsstunden || null,
naechste_hu: details.naechste_hu || null,
naechste_uvv: details.naechste_uvv || null,
},
});
setDetailsOffenId(null);
onFehler("");
} catch {
onFehler("Fahrzeugdetails konnten nicht gespeichert werden.");
}
}
// Option A+C (Karte 11 Nachtrag): Dropdown zeigt nur Zugfahrzeuge (Objekttyp-
// Flag ist_zugfahrzeug) ohne eigene Kopplung - serverseitig zusätzlich per
// 422 erzwungen, das ist hier nur die Vorauswahl fürs Formular.
const zugfahrzeuge = objekte.filter((x) => { const zugfahrzeuge = objekte.filter((x) => {
const typ = objekttypen.find((t) => t.id === x.objekttyp_id); const typ = objekttypen.find((t) => t.id === x.objekttyp_id);
return typ?.ist_zugfahrzeug && x.fahrzeug_id === null; return typ?.ist_zugfahrzeug && x.fahrzeug_id === null;
@@ -168,21 +118,6 @@ export function ObjektSection({
} }
} }
async function codeVorschlagen() {
if (!praefix) {
onFehler("Präfix eingeben (z. B. RK, FZ).");
return;
}
try {
const antwort = await apiRequest<{ code: string }>(
`/objekte/naechster-code?praefix=${encodeURIComponent(praefix.toUpperCase())}`
);
setCode(antwort.code);
} catch {
onFehler("Code-Vorschlag fehlgeschlagen.");
}
}
async function neuerCodeVorschlagen() { async function neuerCodeVorschlagen() {
if (!neuerPraefix) { if (!neuerPraefix) {
onFehler("Präfix eingeben (z. B. RK, FZ)."); onFehler("Präfix eingeben (z. B. RK, FZ).");
@@ -198,37 +133,6 @@ export function ObjektSection({
} }
} }
async function anlegen() {
if (vorlageId === "" || standortId === "") {
onFehler("Vorlage und Standort auswählen.");
return;
}
setWirdAngelegt(true);
const vorlage = vorlagen.find((v) => v.id === vorlageId);
try {
await apiRequest("/objekte", {
method: "POST",
body: {
code,
name,
objekttyp_id: vorlage?.objekttyp_id,
vorlage_id: vorlageId,
standort_id: standortId,
fahrzeug_id: fahrzeugId === "" ? null : fahrzeugId,
},
});
setPraefix("");
setCode("");
setName("");
setFahrzeugId("");
await onGeaendert();
} catch {
onFehler("Objekt konnte nicht angelegt werden (Code evtl. schon vergeben).");
} finally {
setWirdAngelegt(false);
}
}
function duplizierenStarten(o: Objekt) { function duplizierenStarten(o: Objekt) {
setDuplizierId(duplizierId === o.id ? null : o.id); setDuplizierId(duplizierId === o.id ? null : o.id);
setNeuerPraefix(""); setNeuerPraefix("");
@@ -265,334 +169,171 @@ export function ObjektSection({
<section className="section"> <section className="section">
<h2>Objekte</h2> <h2>Objekte</h2>
<div className="split-layout"> <div className="split-layout">
{!nurPflegen && ( {!nurPflegen && (
<div className="card split-layout-sidebar"> <ObjektAnlegenFormular
<h3 style={{ marginTop: 0 }}>Objekt aus Vorlage anlegen</h3> objekte={objekte}
{vorlagen.length === 0 ? ( objekttypen={objekttypen}
<p className="text-muted"> vorlagen={vorlagen}
Keine aktive Vorlage vorhanden erst Excel-Import oder Vorlage per API anlegen. standorte={standorte}
</p> onGeaendert={onGeaendert}
) : ( onFehler={onFehler}
<div className="stack"> />
<div className="field">
<label>Vorlage</label>
<select className="input" value={vorlageId} onChange={(e) => setVorlageId(Number(e.target.value) || "")}>
<option value="">Vorlage wählen</option>
{vorlagen.map((v) => (
<option key={v.id} value={v.id}>
{v.name} (v{v.version})
</option>
))}
</select>
</div>
<div className="field">
<label>Standort</label>
<select
className="input"
value={standortId}
onChange={(e) => setStandortId(Number(e.target.value) || "")}
>
<option value="">Standort wählen</option>
{standorte.map((s) => (
<option key={s.id} value={s.id}>
{s.name}
</option>
))}
</select>
</div>
<div className="field">
<label>Code (eindeutig)</label>
<div className="row" style={{ flexWrap: "wrap" }}>
<input
className="input"
style={{ maxWidth: "6rem" }}
value={praefix}
onChange={(e) => setPraefix(e.target.value)}
placeholder="Präfix (RK, FZ…)"
/>
<button type="button" className="btn btn-secondary" onClick={codeVorschlagen}>
Code vorschlagen
</button>
<input className="input" value={code} onChange={(e) => setCode(e.target.value)} />
</div>
</div>
<div className="field">
<label>Name</label>
<input className="input" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="field">
<label>Fahrzeug (optional)</label>
<select
className="input"
value={fahrzeugId}
onChange={(e) => setFahrzeugId(Number(e.target.value) || "")}
>
<option value="">Kein Fahrzeug</option>
{zugfahrzeuge.map((x) => (
<option key={x.id} value={x.id}>
{x.name} ({x.code})
</option>
))}
</select>
</div>
<button className="btn btn-primary" onClick={anlegen} disabled={!code || !name || wirdAngelegt}>
Anlegen
</button>
</div>
)} )}
</div>
)}
<div className="split-layout-content"> <div className="split-layout-content">
<input <input
className="input input-search" className="input input-search"
style={{ marginBottom: "1rem" }} style={{ marginBottom: "1rem" }}
placeholder={`Suche in ${objekte.length} Objekten…`} placeholder={`Suche in ${objekte.length} Objekten…`}
value={suche} value={suche}
onChange={(e) => setSuche(e.target.value)} onChange={(e) => setSuche(e.target.value)}
/> />
{gefilterteObjekte.length === 0 && <p className="text-muted">Keine Treffer.</p>} {gefilterteObjekte.length === 0 && <p className="text-muted">Keine Treffer.</p>}
{gefilterteObjekte.length > 0 && ( {gefilterteObjekte.length > 0 && (
<ul className="card-list"> <ul className="card-list">
{gefilterteObjekte.map((o) => ( {gefilterteObjekte.map((o) => (
<li key={o.id}> <li key={o.id}>
<div className="row-between" style={{ padding: "0.4rem 0" }}> <div className="row-between" style={{ padding: "0.4rem 0" }}>
<span> <span>
{o.name} <span className="text-muted">({o.code})</span> {o.name} <span className="text-muted">({o.code})</span>
{o.fahrzeug_id !== null && ( {o.fahrzeug_id !== null && (
<span className="text-muted"> Fahrzeug: {objektName(o.fahrzeug_id)}</span> <span className="text-muted"> Fahrzeug: {objektName(o.fahrzeug_id)}</span>
)} )}
<span className="text-muted"> <span className="text-muted">
{" "} {" "}
{" "} {" "}
{o.letzte_kontrolle_am {o.letzte_kontrolle_am
? `zuletzt kontrolliert ${new Date(o.letzte_kontrolle_am).toLocaleDateString("de-DE")}` ? `zuletzt kontrolliert ${new Date(o.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
: "noch nie kontrolliert"} : "noch nie kontrolliert"}
</span>
</span> </span>
</span> <div className="row">
<div className="row"> <Link to={`/akte/objekt/${o.id}`} className="btn btn-secondary">
{!nurPflegen && ( Akte ansehen
</Link>
{!nurPflegen && (
<select className="input" value={o.status} onChange={(e) => statusAendern(o, e.target.value)}>
{STATUS_OPTIONEN.map((s) => (
<option key={s.wert} value={s.wert}>
{s.label}
</option>
))}
</select>
)}
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => verschiebenOeffnen(o)}>
{verschiebenId === o.id ? "Schließen" : "Verschieben"}
</button>
)}
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => fahrzeugBearbeiten(o)}>
{fahrzeugBearbeitenId === o.id ? "Schließen" : "Fahrzeug zuordnen"}
</button>
)}
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => duplizierenStarten(o)}>
{duplizierId === o.id ? "Schließen" : "Duplizieren"}
</button>
)}
</div>
</div>
{verschiebenId === o.id && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<select <select
className="input" className="input"
value={o.status} value={zielStandortId}
onChange={(e) => statusAendern(o, e.target.value)} onChange={(e) => setZielStandortId(Number(e.target.value) || "")}
> >
{STATUS_OPTIONEN.map((s) => ( <option value="">Zielstandort wählen</option>
<option key={s.wert} value={s.wert}> {standorte
{s.label} .filter((s) => s.id !== o.standort_id)
</option> .map((s) => (
))} <option key={s.id} value={s.id}>
{s.name}
</option>
))}
</select> </select>
)}
{objekttypen.find((t) => t.id === o.objekttyp_id)?.ist_zugfahrzeug && (
<button className="btn btn-secondary" onClick={() => fahrzeugdetailsOeffnen(o)}>
{detailsOffenId === o.id ? "Schließen" : "Fahrzeugdetails"}
</button>
)}
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => verschiebenOeffnen(o)}>
{verschiebenId === o.id ? "Schließen" : "Verschieben"}
</button>
)}
<button
className="btn btn-secondary"
onClick={() => setAusgeklapptId(ausgeklapptId === o.id ? null : o.id)}
>
{ausgeklapptId === o.id ? "Positionen ausblenden" : "Ablauf/Charge/SN pflegen"}
</button>
<button
className="btn btn-secondary"
onClick={() => setDokumenteOffenId(dokumenteOffenId === o.id ? null : o.id)}
>
{dokumenteOffenId === o.id ? "Dokumente schließen" : "Dokumente"}
</button>
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => fahrzeugBearbeiten(o)}>
{fahrzeugBearbeitenId === o.id ? "Schließen" : "Fahrzeug zuordnen"}
</button>
)}
{!nurPflegen && (
<button className="btn btn-secondary" onClick={() => duplizierenStarten(o)}>
{duplizierId === o.id ? "Schließen" : "Duplizieren"}
</button>
)}
</div>
</div>
{detailsOffenId === o.id && (
<div className="stack" style={{ marginTop: "0.5rem", maxWidth: "24rem" }}>
<input
className="input"
placeholder="Kennzeichen"
value={details.kennzeichen ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, kennzeichen: e.target.value }))}
/>
<input
className="input"
placeholder="Funkrufname"
value={details.funkrufname ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, funkrufname: e.target.value }))}
/>
<input
className="input"
placeholder="Funkkenner (BOS-Digitalfunk)"
value={details.funkkenner ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, funkkenner: e.target.value }))}
/>
<div className="row">
<input <input
className="input" className="input"
type="number" value={verschiebenGrund}
placeholder="Baujahr" onChange={(e) => setVerschiebenGrund(e.target.value)}
value={details.baujahr ?? ""} placeholder="Grund (optional)"
onChange={(e) => setDetails((d) => ({ ...d, baujahr: Number(e.target.value) || undefined }))} />
<button className="btn btn-primary" onClick={() => verschieben(o.id)}>
Verschieben
</button>
</div>
)}
{fahrzeugBearbeitenId === o.id && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<select
className="input"
value={fahrzeugAuswahl}
onChange={(e) => setFahrzeugAuswahl(Number(e.target.value) || "")}
>
<option value="">Kein Fahrzeug</option>
{zugfahrzeuge
.filter((x) => x.id !== o.id)
.map((x) => (
<option key={x.id} value={x.id}>
{x.name} ({x.code})
</option>
))}
</select>
<button className="btn btn-primary" onClick={() => fahrzeugSpeichern(o.id)}>
Speichern
</button>
</div>
)}
{duplizierId === o.id && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<input
className="input"
style={{ maxWidth: "6rem" }}
value={neuerPraefix}
onChange={(e) => setNeuerPraefix(e.target.value)}
placeholder="Präfix (RK, FZ…)"
/>
<button type="button" className="btn btn-secondary" onClick={neuerCodeVorschlagen}>
Code vorschlagen
</button>
<input
className="input"
value={neuerCode}
onChange={(e) => setNeuerCode(e.target.value)}
placeholder="Neuer Code (eindeutig)"
/> />
<input <input
className="input" className="input"
type="number" value={neuerName}
placeholder="Kilometerstand" onChange={(e) => setNeuerName(e.target.value)}
value={details.kilometerstand ?? ""} placeholder="Neuer Name"
onChange={(e) =>
setDetails((d) => ({ ...d, kilometerstand: Number(e.target.value) || undefined }))
}
/> />
</div> <select
<div className="row"> className="input"
<div className="field"> value={neuerStandortId}
<label>Nächste HU</label> onChange={(e) => setNeuerStandortId(Number(e.target.value) || "")}
<input >
className="input" {standorte.map((s) => (
type="date"
value={details.naechste_hu ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, naechste_hu: e.target.value }))}
/>
</div>
<div className="field">
<label>Nächste UVV</label>
<input
className="input"
type="date"
value={details.naechste_uvv ?? ""}
onChange={(e) => setDetails((d) => ({ ...d, naechste_uvv: e.target.value }))}
/>
</div>
</div>
<button className="btn btn-primary" onClick={() => fahrzeugdetailsSpeichern(o.id)}>
Speichern
</button>
</div>
)}
{verschiebenId === o.id && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<select
className="input"
value={zielStandortId}
onChange={(e) => setZielStandortId(Number(e.target.value) || "")}
>
<option value="">Zielstandort wählen</option>
{standorte
.filter((s) => s.id !== o.standort_id)
.map((s) => (
<option key={s.id} value={s.id}> <option key={s.id} value={s.id}>
{s.name} {s.name}
</option> </option>
))} ))}
</select> </select>
<input <button
className="input" className="btn btn-primary"
value={verschiebenGrund} onClick={() => duplizieren(o.id)}
onChange={(e) => setVerschiebenGrund(e.target.value)} disabled={!neuerCode || !neuerName}
placeholder="Grund (optional)" >
/> Duplizieren
<button className="btn btn-primary" onClick={() => verschieben(o.id)}> </button>
Verschieben </div>
</button> )}
</div> </li>
)} ))}
{fahrzeugBearbeitenId === o.id && ( </ul>
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}> )}
<select </div>
className="input"
value={fahrzeugAuswahl}
onChange={(e) => setFahrzeugAuswahl(Number(e.target.value) || "")}
>
<option value="">Kein Fahrzeug</option>
{zugfahrzeuge
.filter((x) => x.id !== o.id)
.map((x) => (
<option key={x.id} value={x.id}>
{x.name} ({x.code})
</option>
))}
</select>
<button className="btn btn-primary" onClick={() => fahrzeugSpeichern(o.id)}>
Speichern
</button>
</div>
)}
{ausgeklapptId === o.id && (
<div style={{ marginTop: "0.5rem" }}>
<ObjektPositionenPanel
objektId={o.id}
vorlageId={o.vorlage_id}
materialien={materialien}
onFehler={onFehler}
/>
</div>
)}
{dokumenteOffenId === o.id && (
<div style={{ marginTop: "0.5rem" }}>
<DokumentePanel entitaetTyp="objekt" entitaetId={String(o.id)} onFehler={onFehler} />
</div>
)}
{duplizierId === o.id && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<input
className="input"
style={{ maxWidth: "6rem" }}
value={neuerPraefix}
onChange={(e) => setNeuerPraefix(e.target.value)}
placeholder="Präfix (RK, FZ…)"
/>
<button type="button" className="btn btn-secondary" onClick={neuerCodeVorschlagen}>
Code vorschlagen
</button>
<input
className="input"
value={neuerCode}
onChange={(e) => setNeuerCode(e.target.value)}
placeholder="Neuer Code (eindeutig)"
/>
<input
className="input"
value={neuerName}
onChange={(e) => setNeuerName(e.target.value)}
placeholder="Neuer Name"
/>
<select
className="input"
value={neuerStandortId}
onChange={(e) => setNeuerStandortId(Number(e.target.value) || "")}
>
{standorte.map((s) => (
<option key={s.id} value={s.id}>
{s.name}
</option>
))}
</select>
<button
className="btn btn-primary"
onClick={() => duplizieren(o.id)}
disabled={!neuerCode || !neuerName}
>
Duplizieren
</button>
</div>
)}
</li>
))}
</ul>
)}
</div>
</div> </div>
</section> </section>
); );