refactor(admin): Objekte-Verwaltung entschlackt, Akte statt Redundanz
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { apiRequest } from "../../api/client";
|
||||
import type { Beladungsvorlage, Fahrzeugdetails, Material, Objekt, Objekttyp, Standort } from "../../api/types";
|
||||
import { DokumentePanel } from "../../components/DokumentePanel";
|
||||
import { ObjektPositionenPanel } from "./ObjektPositionenPanel";
|
||||
import type { Beladungsvorlage, Material, Objekt, Objekttyp, Standort } from "../../api/types";
|
||||
import { ObjektAnlegenFormular } from "./ObjektAnlegenFormular";
|
||||
|
||||
const STATUS_OPTIONEN = [
|
||||
{ wert: "aktiv", label: "Aktiv" },
|
||||
@@ -21,7 +21,8 @@ interface Props {
|
||||
// (Ablauf/Charge/SN/Prüfintervall/Geräte-Instanzen), nicht Objekte anlegen/
|
||||
// duplizieren/koppeln (require_roles(administration) dort) - Anlegen-Formular
|
||||
// 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;
|
||||
onGeaendert: () => Promise<void>;
|
||||
onFehler: (text: string) => void;
|
||||
@@ -32,20 +33,10 @@ export function ObjektSection({
|
||||
objekttypen,
|
||||
vorlagen,
|
||||
standorte,
|
||||
materialien,
|
||||
nurPflegen = false,
|
||||
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);
|
||||
const [ausgeklapptId, setAusgeklapptId] = useState<number | null>(null);
|
||||
const [dokumenteOffenId, setDokumenteOffenId] = useState<number | null>(null);
|
||||
const [duplizierId, setDuplizierId] = useState<number | null>(null);
|
||||
const [neuerPraefix, setNeuerPraefix] = useState("");
|
||||
const [neuerCode, setNeuerCode] = useState("");
|
||||
@@ -54,8 +45,6 @@ export function ObjektSection({
|
||||
const [fahrzeugBearbeitenId, setFahrzeugBearbeitenId] = useState<number | null>(null);
|
||||
const [fahrzeugAuswahl, setFahrzeugAuswahl] = useState<number | "">("");
|
||||
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 [zielStandortId, setZielStandortId] = useState<number | "">("");
|
||||
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 typ = objekttypen.find((t) => t.id === x.objekttyp_id);
|
||||
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() {
|
||||
if (!neuerPraefix) {
|
||||
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) {
|
||||
setDuplizierId(duplizierId === o.id ? null : o.id);
|
||||
setNeuerPraefix("");
|
||||
@@ -265,334 +169,171 @@ export function ObjektSection({
|
||||
<section className="section">
|
||||
<h2>Objekte</h2>
|
||||
<div className="split-layout">
|
||||
{!nurPflegen && (
|
||||
<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>
|
||||
{!nurPflegen && (
|
||||
<ObjektAnlegenFormular
|
||||
objekte={objekte}
|
||||
objekttypen={objekttypen}
|
||||
vorlagen={vorlagen}
|
||||
standorte={standorte}
|
||||
onGeaendert={onGeaendert}
|
||||
onFehler={onFehler}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="split-layout-content">
|
||||
<input
|
||||
className="input input-search"
|
||||
style={{ marginBottom: "1rem" }}
|
||||
placeholder={`Suche in ${objekte.length} Objekten…`}
|
||||
value={suche}
|
||||
onChange={(e) => setSuche(e.target.value)}
|
||||
/>
|
||||
{gefilterteObjekte.length === 0 && <p className="text-muted">Keine Treffer.</p>}
|
||||
{gefilterteObjekte.length > 0 && (
|
||||
<ul className="card-list">
|
||||
{gefilterteObjekte.map((o) => (
|
||||
<li key={o.id}>
|
||||
<div className="row-between" style={{ padding: "0.4rem 0" }}>
|
||||
<span>
|
||||
{o.name} <span className="text-muted">({o.code})</span>
|
||||
{o.fahrzeug_id !== null && (
|
||||
<span className="text-muted"> – Fahrzeug: {objektName(o.fahrzeug_id)}</span>
|
||||
)}
|
||||
<span className="text-muted">
|
||||
{" "}
|
||||
–{" "}
|
||||
{o.letzte_kontrolle_am
|
||||
? `zuletzt kontrolliert ${new Date(o.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
|
||||
: "noch nie kontrolliert"}
|
||||
<div className="split-layout-content">
|
||||
<input
|
||||
className="input input-search"
|
||||
style={{ marginBottom: "1rem" }}
|
||||
placeholder={`Suche in ${objekte.length} Objekten…`}
|
||||
value={suche}
|
||||
onChange={(e) => setSuche(e.target.value)}
|
||||
/>
|
||||
{gefilterteObjekte.length === 0 && <p className="text-muted">Keine Treffer.</p>}
|
||||
{gefilterteObjekte.length > 0 && (
|
||||
<ul className="card-list">
|
||||
{gefilterteObjekte.map((o) => (
|
||||
<li key={o.id}>
|
||||
<div className="row-between" style={{ padding: "0.4rem 0" }}>
|
||||
<span>
|
||||
{o.name} <span className="text-muted">({o.code})</span>
|
||||
{o.fahrzeug_id !== null && (
|
||||
<span className="text-muted"> – Fahrzeug: {objektName(o.fahrzeug_id)}</span>
|
||||
)}
|
||||
<span className="text-muted">
|
||||
{" "}
|
||||
–{" "}
|
||||
{o.letzte_kontrolle_am
|
||||
? `zuletzt kontrolliert ${new Date(o.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
|
||||
: "noch nie kontrolliert"}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<div className="row">
|
||||
{!nurPflegen && (
|
||||
<div className="row">
|
||||
<Link to={`/akte/objekt/${o.id}`} className="btn btn-secondary">
|
||||
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
|
||||
className="input"
|
||||
value={o.status}
|
||||
onChange={(e) => statusAendern(o, e.target.value)}
|
||||
value={zielStandortId}
|
||||
onChange={(e) => setZielStandortId(Number(e.target.value) || "")}
|
||||
>
|
||||
{STATUS_OPTIONEN.map((s) => (
|
||||
<option key={s.wert} value={s.wert}>
|
||||
{s.label}
|
||||
</option>
|
||||
))}
|
||||
<option value="">Zielstandort wählen…</option>
|
||||
{standorte
|
||||
.filter((s) => s.id !== o.standort_id)
|
||||
.map((s) => (
|
||||
<option key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</option>
|
||||
))}
|
||||
</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
|
||||
className="input"
|
||||
type="number"
|
||||
placeholder="Baujahr"
|
||||
value={details.baujahr ?? ""}
|
||||
onChange={(e) => setDetails((d) => ({ ...d, baujahr: Number(e.target.value) || undefined }))}
|
||||
value={verschiebenGrund}
|
||||
onChange={(e) => setVerschiebenGrund(e.target.value)}
|
||||
placeholder="Grund (optional)"
|
||||
/>
|
||||
<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
|
||||
className="input"
|
||||
type="number"
|
||||
placeholder="Kilometerstand"
|
||||
value={details.kilometerstand ?? ""}
|
||||
onChange={(e) =>
|
||||
setDetails((d) => ({ ...d, kilometerstand: Number(e.target.value) || undefined }))
|
||||
}
|
||||
value={neuerName}
|
||||
onChange={(e) => setNeuerName(e.target.value)}
|
||||
placeholder="Neuer Name"
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="field">
|
||||
<label>Nächste HU</label>
|
||||
<input
|
||||
className="input"
|
||||
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) => (
|
||||
<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>
|
||||
<input
|
||||
className="input"
|
||||
value={verschiebenGrund}
|
||||
onChange={(e) => setVerschiebenGrund(e.target.value)}
|
||||
placeholder="Grund (optional)"
|
||||
/>
|
||||
<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>
|
||||
)}
|
||||
{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>
|
||||
</select>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => duplizieren(o.id)}
|
||||
disabled={!neuerCode || !neuerName}
|
||||
>
|
||||
Duplizieren
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user