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 { 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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user