- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark, Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI) - aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource, an aether-core gekoppelt (Rechte über aether_users.admin_group), mit WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU) - Prototypen admin/ vervollständigt - SQL-Schema aether_admin.sql - Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
622 lines
24 KiB
JavaScript
622 lines
24 KiB
JavaScript
/* =====================================================================
|
||
aether-medical / Ansicht: Fuhrpark
|
||
|
||
Zwei Sorten von Angaben, streng getrennt dargestellt:
|
||
GEMELDET (Tank, Zustand, Position) kommt aus dem Spiel und trägt
|
||
immer sein Alter mit sich. EINGETRAGEN (Status, Wartung, Zuweisung)
|
||
stammt von Menschen. Kein gemeldeter Wert ändert einen eingetragenen.
|
||
===================================================================== */
|
||
"use strict";
|
||
|
||
(() => {
|
||
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen, anClient } = App;
|
||
|
||
const z = {
|
||
ordner: "dienst", // dienst | werkstatt | ausser
|
||
art: "", // Filter auf eine Fahrzeugart
|
||
daten: null,
|
||
};
|
||
|
||
// Marker-Art je Einsatzstatus
|
||
const statusArt = {
|
||
verfuegbar: "gut", im_einsatz: "warn",
|
||
werkstatt: "info", ausser_dienst: "neutral",
|
||
};
|
||
|
||
/* ---- Kleine Helfer ---------------------------------------------------- */
|
||
|
||
/** Anzeigename aus einer Konfigurationsliste */
|
||
function nameAus(liste, kennung) {
|
||
const treffer = (liste || []).find((e) => e.id === kennung);
|
||
return treffer ? treffer.name : kennung;
|
||
}
|
||
|
||
/** Farbe eines Balkens nach seinem Wert */
|
||
function balkenFarbe(wert, grenze) {
|
||
if (wert === null || wert === undefined) return "var(--text-leise)";
|
||
if (wert <= grenze / 2) return "var(--kritisch)";
|
||
if (wert <= grenze) return "var(--warn)";
|
||
return "var(--gut)";
|
||
}
|
||
|
||
/**
|
||
* Ein Balken für einen gemeldeten Wert.
|
||
* Ist die Meldung alt, wird der Balken blass — der Wert sagt dann
|
||
* nichts über den jetzigen Zustand.
|
||
*/
|
||
function balken(titel, wert, grenze, veraltet) {
|
||
const hat = wert !== null && wert !== undefined;
|
||
|
||
return `<div class="fz-wert ${veraltet ? "alt" : ""}">
|
||
<div class="fz-wert-kopf">
|
||
<span>${sicher(titel)}</span>
|
||
<b>${hat ? wert + " %" : "nie gemeldet"}</b>
|
||
</div>
|
||
<div class="fz-balken">
|
||
<i style="width:${hat ? wert : 0}%; background:${balkenFarbe(wert, grenze)}"></i>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ---- Gerüst ------------------------------------------------------------ */
|
||
|
||
function reiterleiste(d) {
|
||
const eintraege = [
|
||
{ id: "dienst", text: "Im Dienst", zahl: d.zaehler.verfuegbar + d.zaehler.imEinsatz },
|
||
{ id: "werkstatt", text: "Wartung", zahl: d.zaehler.werkstatt },
|
||
{ id: "ausser", text: "Außer Dienst", zahl: d.zaehler.ausserDienst },
|
||
];
|
||
|
||
return `<div class="reiter">
|
||
${eintraege.map((e) => `
|
||
<button class="${z.ordner === e.id ? "aktiv" : ""}" data-reiter="${e.id}">
|
||
${sicher(e.text)}<span class="mdt-zahl">${e.zahl}</span>
|
||
</button>`).join("")}
|
||
</div>`;
|
||
}
|
||
|
||
function kennzahlen(d) {
|
||
const zk = d.zaehler;
|
||
|
||
return `<div class="raster k4">
|
||
<div class="kennzahl ${zk.verfuegbar === 0 ? "hoch" : "gut"}">
|
||
<div class="kz-titel">Verfügbar</div>
|
||
<div class="kz-wert">${zk.verfuegbar}</div>
|
||
<div class="kz-zusatz">so eingetragen</div>
|
||
</div>
|
||
<div class="kennzahl">
|
||
<div class="kz-titel">Im Einsatz</div>
|
||
<div class="kz-wert">${zk.imEinsatz}</div>
|
||
<div class="kz-zusatz">${zk.zugewiesen} Fahrzeuge zugewiesen</div>
|
||
</div>
|
||
<div class="kennzahl ${zk.werkstatt > 0 ? "warn" : ""}">
|
||
<div class="kz-titel">Wartung offen</div>
|
||
<div class="kz-wert">${zk.werkstatt}</div>
|
||
<div class="kz-zusatz">Werkstatt oder fällig</div>
|
||
</div>
|
||
<div class="kennzahl">
|
||
<div class="kz-titel">Außer Dienst</div>
|
||
<div class="kz-wert">${zk.ausserDienst}</div>
|
||
<div class="kz-zusatz">fahren nicht</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ---- Übersicht ---------------------------------------------------------- */
|
||
|
||
function uebersicht(behaelter) {
|
||
const d = z.daten;
|
||
|
||
behaelter.innerHTML = `
|
||
<div class="seiten-kopf">
|
||
<div>
|
||
<h2>Fuhrpark</h2>
|
||
<p>Tank, Zustand und Position werden gemeldet, während jemand fährt.
|
||
Status, Wartung und Zuweisung trägt ein Mensch ein — das eine ändert
|
||
das andere nie.</p>
|
||
</div>
|
||
${d.rechte.pflegen
|
||
? `<button class="knopf" data-neu="1">+ Fahrzeug aufnehmen</button>` : ""}
|
||
</div>
|
||
|
||
${kennzahlen(d)}
|
||
${reiterleiste(d)}
|
||
|
||
<div class="filterleiste">
|
||
<select data-artfilter="1">
|
||
<option value="">Alle Fahrzeugarten</option>
|
||
${d.arten.map((a) =>
|
||
`<option value="${sicher(a.id)}" ${a.id === z.art ? "selected" : ""}>
|
||
${sicher(a.name)}</option>`).join("")}
|
||
</select>
|
||
<span class="feld-hinweis">Eine Meldung älter als ${d.grenzen.meldung} Minuten
|
||
wird blass dargestellt.</span>
|
||
</div>
|
||
|
||
${d.liste.length
|
||
? `<div class="fz-raster">${d.liste.map((f) => karte(f, d)).join("")}</div>`
|
||
: `<div class="karte"><div class="liste-leer">Kein Fahrzeug in diesem Ordner.</div></div>`}
|
||
`;
|
||
|
||
verdrahteUebersicht(behaelter);
|
||
}
|
||
|
||
function karte(f, d) {
|
||
return `<div class="fz-karte status-${sicher(f.status)}" data-oeffne="${f.id}">
|
||
<div class="fz-kopf">
|
||
<div>
|
||
<div class="fz-name">${sicher(f.bezeichnung)}</div>
|
||
<div class="fz-kennzeichen">${sicher(f.kennzeichen)}</div>
|
||
</div>
|
||
<span class="marker ${statusArt[f.status] || "neutral"}">
|
||
${sicher(f.statusName)}</span>
|
||
</div>
|
||
|
||
<div class="fz-zeile">
|
||
<span class="marker neutral">${sicher(f.artName)}</span>
|
||
${f.wartung !== "ok"
|
||
? `<span class="marker warn">${sicher(f.wartungName)}</span>` : ""}
|
||
${f.zugewiesenAn
|
||
? `<span class="marker info">${sicher(f.zugewiesenAn)}</span>` : ""}
|
||
</div>
|
||
|
||
<div class="fz-werte">
|
||
${balken("Tank", f.tank, d.grenzen.tank, f.meldungVeraltet)}
|
||
${balken("Zustand", f.zustand, d.grenzen.zustand, f.meldungVeraltet)}
|
||
</div>
|
||
|
||
<div class="fz-fuss">
|
||
${f.gemeldet_am
|
||
? `gemeldet vor ${sicher(minuten(f.meldungAlter))}${
|
||
f.letzterFahrer ? " · " + sicher(f.letzterFahrer) : ""}`
|
||
: "noch nie gemeldet"}
|
||
${f.notiz ? ` · ${sicher(f.notiz)}` : ""}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function verdrahteUebersicht(behaelter) {
|
||
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
z.ordner = el.dataset.reiter;
|
||
rendern(behaelter);
|
||
});
|
||
});
|
||
|
||
behaelter.querySelector("[data-artfilter]").addEventListener("change", (e) => {
|
||
z.art = e.target.value;
|
||
rendern(behaelter);
|
||
});
|
||
|
||
behaelter.querySelectorAll("[data-oeffne]").forEach((el) => {
|
||
el.addEventListener("click", () => fahrzeugFenster(behaelter, +el.dataset.oeffne));
|
||
});
|
||
|
||
const neu = behaelter.querySelector("[data-neu]");
|
||
if (neu) neu.addEventListener("click", () => bearbeitenFenster(behaelter, null));
|
||
}
|
||
|
||
/* ---- Einzelnes Fahrzeug ---------------------------------------------------- */
|
||
|
||
async function fahrzeugFenster(behaelter, fahrzeugId) {
|
||
let d;
|
||
try {
|
||
d = await aufruf("fahrzeug", "oeffnen", { id: fahrzeugId });
|
||
} catch (f) { return meldung(f.message, "fehler"); }
|
||
|
||
const f = d.fahrzeug;
|
||
const ausstattung = String(f.ausstattung || "")
|
||
.split(",").map((t) => t.trim()).filter(Boolean);
|
||
|
||
const dialog = fenster({
|
||
titel: `${f.bezeichnung} · ${f.kennzeichen}`,
|
||
inhalt: `
|
||
${f.wartung !== "ok" ? `
|
||
<div class="warnung warn">
|
||
<span class="w-symbol">🔧</span>
|
||
<div><b>${sicher(f.wartungName)}</b>${f.notiz ? " — " + sicher(f.notiz) : ""}.
|
||
Eingetragen von Hand; ob das Fahrzeug fährt, steht im Einsatzstatus.</div>
|
||
</div>` : ""}
|
||
|
||
${f.meldungVeraltet ? `
|
||
<div class="warnung">
|
||
<span class="w-symbol">🕓</span>
|
||
<div>Die letzte Meldung ist ${sicher(minuten(f.meldungAlter))} alt.
|
||
Tank und Zustand sagen nichts über den jetzigen Stand.</div>
|
||
</div>` : ""}
|
||
|
||
<div class="werte-liste">
|
||
<div><span>Einsatzstatus</span><b>${sicher(f.statusName)}</b></div>
|
||
<div><span>Wartung</span><b>${sicher(f.wartungName)}</b></div>
|
||
<div><span>Art</span><b>${sicher(f.artName)}</b></div>
|
||
<div><span>Modell</span><b>${sicher(f.modell)}</b></div>
|
||
<div><span>Station</span><b>${sicher(f.station || "—")}</b></div>
|
||
<div><span>Zugewiesen an</span>
|
||
<b>${sicher(f.zugewiesenAn || "niemand")}</b></div>
|
||
</div>
|
||
|
||
<div class="karte-kopf" style="margin-top:14px">
|
||
<span class="karte-titel">Gemeldet</span>
|
||
<span class="feld-hinweis">${f.gemeldetAm
|
||
? "vor " + sicher(minuten(f.meldungAlter)) + " von "
|
||
+ sicher(f.letzterFahrer || "unbekannt")
|
||
: "noch nie"}</span>
|
||
</div>
|
||
<div class="fz-werte ${f.meldungVeraltet ? "alt" : ""}">
|
||
${balken("Tank", f.tank, 20, f.meldungVeraltet)}
|
||
${balken("Zustand", f.zustand, 60, f.meldungVeraltet)}
|
||
</div>
|
||
${f.position
|
||
? `<button class="knopf leise klein" data-wegpunkt="1"
|
||
style="margin-top:8px">Wegpunkt zur letzten Position</button>`
|
||
: `<div class="feld-hinweis" style="margin-top:8px">Keine Position gemeldet.</div>`}
|
||
|
||
<div class="karte-kopf" style="margin-top:14px">
|
||
<span class="karte-titel">Ausstattung</span>
|
||
<span class="feld-hinweis">notiert, nicht geprüft</span>
|
||
</div>
|
||
${ausstattung.length
|
||
? `<div class="fz-ausstattung">${ausstattung.map((a) =>
|
||
`<span class="fz-teil">${sicher(a)}</span>`).join("")}</div>`
|
||
: `<div class="beh-text leer">Nichts eingetragen.</div>`}
|
||
|
||
<div class="feld-reihe" style="margin-top:14px">
|
||
${d.rechte.zuweisen
|
||
? `<button class="knopf klein" data-status="1">Status setzen</button>
|
||
<button class="knopf leise klein" data-zuweisen="1">Zuweisen</button>` : ""}
|
||
${d.rechte.wartung
|
||
? `<button class="knopf leise klein" data-wartung="1">Wartung</button>` : ""}
|
||
${d.rechte.pflegen
|
||
? `<button class="knopf leise klein" data-bearbeiten="1">Bearbeiten</button>` : ""}
|
||
${d.rechte.pflegen && f.status === "ausser_dienst"
|
||
? `<button class="knopf gefahr klein" data-entfernen="1">Aus dem Fuhrpark nehmen</button>` : ""}
|
||
</div>
|
||
|
||
<div class="karte-kopf" style="margin-top:14px">
|
||
<span class="karte-titel">Eingetragene Vorgänge</span>
|
||
<span class="feld-hinweis">aus dem Protokoll, unveränderlich</span>
|
||
</div>
|
||
<div class="liste">
|
||
${d.spur.length ? d.spur.map((s) => `
|
||
<div class="liste-zeile">
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(s.aktion)}</div>
|
||
<div class="lz-unter">${sicher(s.autor)}
|
||
${s.grund ? " · " + sicher(s.grund) : ""}</div>
|
||
</div>
|
||
<span class="db-zeit">${zeit(s.am)}</span>
|
||
</div>`).join("")
|
||
: `<div class="liste-leer">Noch kein Eintrag.</div>`}
|
||
</div>
|
||
`,
|
||
nachOeffnen: (hg) => {
|
||
const wegpunkt = hg.querySelector("[data-wegpunkt]");
|
||
if (wegpunkt) {
|
||
wegpunkt.addEventListener("click", () => {
|
||
anClient("wegpunkt", { x: f.position.x, y: f.position.y });
|
||
meldung("Wegpunkt zur letzten gemeldeten Position gesetzt.");
|
||
});
|
||
}
|
||
|
||
const knopf = (name, funktion) => {
|
||
const el = hg.querySelector(`[data-${name}]`);
|
||
if (el) el.addEventListener("click", () => { dialog.schliessen(); funktion(); });
|
||
};
|
||
|
||
knopf("status", () => statusFenster(behaelter, f, d));
|
||
knopf("zuweisen", () => zuweisenFenster(behaelter, f));
|
||
knopf("wartung", () => wartungFenster(behaelter, f, d));
|
||
knopf("bearbeiten", () => bearbeitenFenster(behaelter, f));
|
||
knopf("entfernen", () => entfernenFenster(behaelter, f));
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Einsatzstatus ----------------------------------------------------------- */
|
||
|
||
function statusFenster(behaelter, f, d) {
|
||
bestaetigen({
|
||
titel: "Einsatzstatus setzen",
|
||
inhalt: `
|
||
<p>Aktuell eingetragen: <b>${sicher(f.statusName)}</b></p>
|
||
|
||
<div class="feld"><label>Neuer Status</label>
|
||
<select name="status">
|
||
${d.status.map((s) =>
|
||
`<option value="${sicher(s.id)}" ${s.id === f.status ? "selected" : ""}>
|
||
${sicher(s.name)}</option>`).join("")}
|
||
</select>
|
||
<div class="feld-hinweis">Werkstatt und Außer Dienst darf nur eintragen,
|
||
wer die Wartung führt — und beides braucht einen Grund.</div></div>
|
||
|
||
<div class="feld"><label>Grund</label>
|
||
<input name="grund" maxlength="255"
|
||
placeholder="Warum ändert sich der Status?"></div>
|
||
|
||
${f.zugewiesenAn ? `
|
||
<div class="warnung warn">
|
||
<span class="w-symbol">🧍</span>
|
||
<div>Das Fahrzeug ist <b>${sicher(f.zugewiesenAn)}</b> zugewiesen.
|
||
Bei Werkstatt oder Außer Dienst wird das gemeldet — die Zuweisung
|
||
selbst bleibt bestehen.</div>
|
||
</div>` : ""}`,
|
||
bestaetigenText: "Status setzen",
|
||
bestaetigt: async (el, zu) => {
|
||
try {
|
||
await aufruf("fahrzeug", "status", {
|
||
id: f.id,
|
||
status: el.querySelector("[name=status]").value,
|
||
grund: el.querySelector("[name=grund]").value.trim(),
|
||
});
|
||
zu();
|
||
meldung("Status gesetzt.", "gut");
|
||
rendern(behaelter);
|
||
} catch (fehler) { meldung(fehler.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Zuweisung ------------------------------------------------------------------ */
|
||
|
||
async function zuweisenFenster(behaelter, f) {
|
||
let k;
|
||
try {
|
||
k = await aufruf("fahrzeug", "kollegen", {});
|
||
} catch (fehler) { return meldung(fehler.message, "fehler"); }
|
||
|
||
let gewaehlt = f.zugewiesen || null;
|
||
let aufheben = false;
|
||
|
||
bestaetigen({
|
||
titel: `${f.bezeichnung} zuweisen`,
|
||
inhalt: `
|
||
<p>Zugewiesen ist zurzeit: <b>${sicher(f.zugewiesenAn || "niemand")}</b></p>
|
||
|
||
<div class="warnung">
|
||
<span class="w-symbol">ℹ</span>
|
||
<div>Eine Zuweisung ist ein Eintrag im Fahrzeugbuch. Sie spawnt kein
|
||
Fahrzeug, schließt keines auf und gibt keinen Schlüssel aus.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Im Dienst</label>
|
||
<div class="liste pat-trefferliste" data-kollegen="1">
|
||
${k.liste.length ? k.liste.map((p) => `
|
||
<div class="liste-zeile ${p.charakterId === f.zugewiesen ? "gewaehlt" : ""}"
|
||
data-person="${p.charakterId}">
|
||
<div class="lz-haupt">
|
||
<div class="lz-titel">${sicher(p.name)}
|
||
${p.selbst ? `<span class="marker info">du</span>` : ""}</div>
|
||
<div class="lz-unter">${sicher(p.rolleName)} ·
|
||
${sicher(p.personalnummer || "")}</div>
|
||
</div>
|
||
<span class="marker neutral">${sicher(p.funktion || "")}</span>
|
||
</div>`).join("")
|
||
: `<div class="liste-leer">Niemand im Dienst.</div>`}
|
||
</div></div>
|
||
|
||
<div class="feld"><label>
|
||
<input type="checkbox" class="haken" name="aufheben">
|
||
Zuweisung aufheben (niemand ist danach eingetragen)</label></div>
|
||
|
||
<div class="feld"><label>Vermerk / Grund</label>
|
||
<input name="grund" maxlength="255"
|
||
placeholder="Schichtübergabe, Fahrzeugwechsel…"></div>`,
|
||
bestaetigenText: "Eintragen",
|
||
bestaetigt: async (el, zu) => {
|
||
const grund = el.querySelector("[name=grund]").value.trim();
|
||
|
||
if (aufheben && !grund) {
|
||
return meldung("Bitte angeben, warum die Zuweisung entfällt.", "fehler");
|
||
}
|
||
if (!aufheben && !gewaehlt) {
|
||
return meldung("Bitte eine Person wählen.", "fehler");
|
||
}
|
||
|
||
try {
|
||
await aufruf("fahrzeug", "zuweisen", {
|
||
id: f.id,
|
||
charakter: aufheben ? null : gewaehlt,
|
||
grund,
|
||
});
|
||
zu();
|
||
meldung(aufheben ? "Zuweisung aufgehoben." : "Fahrzeug zugewiesen.", "gut");
|
||
rendern(behaelter);
|
||
} catch (fehler) { meldung(fehler.message, "fehler"); }
|
||
},
|
||
nachOeffnen: (hg) => {
|
||
hg.querySelectorAll("[data-person]").forEach((el) => {
|
||
el.addEventListener("click", () => {
|
||
gewaehlt = +el.dataset.person;
|
||
hg.querySelectorAll("[data-person]")
|
||
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
|
||
el.classList.add("gewaehlt");
|
||
});
|
||
});
|
||
|
||
const haken = hg.querySelector("[name=aufheben]");
|
||
haken.addEventListener("change", () => {
|
||
aufheben = haken.checked;
|
||
hg.querySelector("[data-kollegen]").style.opacity = aufheben ? ".45" : "";
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Wartung ---------------------------------------------------------------------- */
|
||
|
||
function wartungFenster(behaelter, f, d) {
|
||
bestaetigen({
|
||
titel: "Wartungsstatus",
|
||
inhalt: `
|
||
<p>Aktuell eingetragen: <b>${sicher(f.wartungName)}</b></p>
|
||
|
||
<div class="warnung">
|
||
<span class="w-symbol">ℹ</span>
|
||
<div>Ein gemeldeter Schaden setzt diesen Status nicht. Was das Spiel
|
||
meldet, ist eine Beobachtung — ob gewartet wird, beurteilst du.</div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Wartungsstatus</label>
|
||
<select name="wartung">
|
||
${d.wartungsstatus.map((s) =>
|
||
`<option value="${sicher(s.id)}" ${s.id === f.wartung ? "selected" : ""}>
|
||
${sicher(s.name)}</option>`).join("")}
|
||
</select></div>
|
||
|
||
<div class="feld"><label>Notiz</label>
|
||
<input name="notiz" maxlength="255" value="${sicher(f.notiz || "")}"
|
||
placeholder="Was ist zu tun, was wurde gemacht?"></div>
|
||
<div class="feld-hinweis">Ein leeres Feld löscht die Notiz.</div>`,
|
||
bestaetigenText: "Eintragen",
|
||
bestaetigt: async (el, zu) => {
|
||
try {
|
||
await aufruf("fahrzeug", "wartung", {
|
||
id: f.id,
|
||
wartung: el.querySelector("[name=wartung]").value,
|
||
notiz: el.querySelector("[name=notiz]").value.trim(),
|
||
});
|
||
zu();
|
||
meldung("Wartungsstatus eingetragen.", "gut");
|
||
rendern(behaelter);
|
||
} catch (fehler) { meldung(fehler.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Fahrzeug anlegen und bearbeiten ------------------------------------------------ */
|
||
|
||
function bearbeitenFenster(behaelter, f) {
|
||
const d = z.daten;
|
||
const neu = !f;
|
||
const vorhanden = String((f && f.ausstattung) || "")
|
||
.split(",").map((t) => t.trim()).filter(Boolean);
|
||
const eigene = vorhanden.filter((a) => !d.ausstattung.includes(a));
|
||
|
||
bestaetigen({
|
||
titel: neu ? "Fahrzeug aufnehmen" : `${f.bezeichnung} bearbeiten`,
|
||
inhalt: `
|
||
<div class="feld-reihe">
|
||
<div class="feld"><label>Kennzeichen</label>
|
||
<input name="kennzeichen" maxlength="12" placeholder="LSMC 003"
|
||
value="${neu ? "" : sicher(f.kennzeichen)}"></div>
|
||
<div class="feld"><label>Bezeichnung</label>
|
||
<input name="bezeichnung" maxlength="64" placeholder="RTW 3"
|
||
value="${neu ? "" : sicher(f.bezeichnung)}"></div>
|
||
</div>
|
||
<div class="feld-hinweis">Über das Kennzeichen findet die Telemetrie das
|
||
Fahrzeug wieder — es muss genau dem im Spiel entsprechen.</div>
|
||
|
||
<div class="feld-reihe" style="margin-top:10px">
|
||
<div class="feld"><label>Modell (Spawnname)</label>
|
||
<input name="modell" maxlength="48" placeholder="ambulance"
|
||
value="${neu ? "" : sicher(f.modell)}"></div>
|
||
<div class="feld"><label>Art</label>
|
||
<select name="art">
|
||
${d.arten.map((a) =>
|
||
`<option value="${sicher(a.id)}" ${!neu && a.id === f.art ? "selected" : ""}>
|
||
${sicher(a.name)}</option>`).join("")}
|
||
</select></div>
|
||
</div>
|
||
|
||
<div class="feld"><label>Station</label>
|
||
<select name="station">
|
||
<option value="">keine Zuordnung</option>
|
||
${d.stationen.map((s) =>
|
||
`<option value="${sicher(s.id)}" ${!neu && s.id === f.station ? "selected" : ""}>
|
||
${sicher(s.name)}</option>`).join("")}
|
||
</select></div>
|
||
|
||
<div class="feld"><label>Ausstattung</label>
|
||
<div class="fz-ausstattungswahl">
|
||
${d.ausstattung.map((a) => `
|
||
<button class="fz-teil-knopf ${vorhanden.includes(a) ? "gewaehlt" : ""}"
|
||
data-teil="${sicher(a)}">${sicher(a)}</button>`).join("")}
|
||
</div></div>
|
||
|
||
<div class="feld"><label>Weitere Ausstattung (mit Komma trennen)</label>
|
||
<input name="eigene" maxlength="512" value="${sicher(eigene.join(", "))}"></div>`,
|
||
bestaetigenText: neu ? "Aufnehmen" : "Speichern",
|
||
bestaetigt: async (el, zu) => {
|
||
const gewaehlt = Array.from(el.querySelectorAll(".fz-teil-knopf.gewaehlt"))
|
||
.map((k) => k.dataset.teil);
|
||
const weitere = el.querySelector("[name=eigene]").value
|
||
.split(",").map((t) => t.trim()).filter(Boolean);
|
||
|
||
try {
|
||
await aufruf("fahrzeug", "speichern", {
|
||
id: neu ? null : f.id,
|
||
kennzeichen: el.querySelector("[name=kennzeichen]").value.trim(),
|
||
bezeichnung: el.querySelector("[name=bezeichnung]").value.trim(),
|
||
modell: el.querySelector("[name=modell]").value.trim(),
|
||
art: el.querySelector("[name=art]").value,
|
||
station: el.querySelector("[name=station]").value,
|
||
ausstattung: gewaehlt.concat(weitere).join(", "),
|
||
});
|
||
zu();
|
||
meldung(neu ? "Fahrzeug aufgenommen." : "Fahrzeug gespeichert.", "gut");
|
||
rendern(behaelter);
|
||
} catch (fehler) { meldung(fehler.message, "fehler"); }
|
||
},
|
||
nachOeffnen: (hg) => {
|
||
hg.querySelectorAll("[data-teil]").forEach((el) => {
|
||
el.addEventListener("click", () => el.classList.toggle("gewaehlt"));
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
function entfernenFenster(behaelter, f) {
|
||
bestaetigen({
|
||
titel: `${f.bezeichnung} aus dem Fuhrpark nehmen`,
|
||
gefahr: true,
|
||
inhalt: `
|
||
<p>Das Fahrzeug verschwindet aus dem Fuhrpark. Die Protokolleinträge
|
||
dazu bleiben erhalten.</p>
|
||
<div class="feld"><label>Grund (Pflicht)</label>
|
||
<input name="grund" maxlength="255"
|
||
placeholder="Verkauft, verschrottet, ersetzt…"></div>`,
|
||
bestaetigenText: "Entfernen",
|
||
bestaetigt: async (el, zu) => {
|
||
const grund = el.querySelector("[name=grund]").value.trim();
|
||
if (!grund) return meldung("Bitte einen Grund angeben.", "fehler");
|
||
try {
|
||
await aufruf("fahrzeug", "entfernen", { id: f.id, grund });
|
||
zu();
|
||
meldung("Fahrzeug entfernt.", "gut");
|
||
rendern(behaelter);
|
||
} catch (fehler) { meldung(fehler.message, "fehler"); }
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---- Laden ---------------------------------------------------------------------- */
|
||
|
||
async function rendern(behaelter) {
|
||
try {
|
||
z.daten = await aufruf("fahrzeug", "uebersicht", { ordner: z.ordner, art: z.art });
|
||
|
||
uebersicht(behaelter);
|
||
|
||
// Sprungziel aus einem Hinweis, z. B. „Tankstand gemeldet"
|
||
const ziel = App.zustand.modulZiel;
|
||
if (ziel && ziel.fahrzeug) {
|
||
App.zustand.modulZiel = null;
|
||
return fahrzeugFenster(behaelter, ziel.fahrzeug);
|
||
}
|
||
} catch (f) {
|
||
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Fuhrpark</h2>
|
||
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
|
||
}
|
||
}
|
||
|
||
App.registriereAnsicht("fahrzeug", {
|
||
rendern,
|
||
aktualisieren() {
|
||
// Kein offenes Fenster überschreiben
|
||
if (App.zustand.aktivesModul === "fahrzeug" && !document.querySelector(".fenster-hg")) {
|
||
rendern(App.$("inhalt"));
|
||
}
|
||
},
|
||
});
|
||
})();
|