/* =====================================================================
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 `
${sicher(titel)}
${hat ? wert + " %" : "nie gemeldet"}
`;
}
/* ---- 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 `
${eintraege.map((e) => `
${sicher(e.text)}${e.zahl}
`).join("")}
`;
}
function kennzahlen(d) {
const zk = d.zaehler;
return `
Verfügbar
${zk.verfuegbar}
so eingetragen
Im Einsatz
${zk.imEinsatz}
${zk.zugewiesen} Fahrzeuge zugewiesen
Wartung offen
${zk.werkstatt}
Werkstatt oder fällig
Außer Dienst
${zk.ausserDienst}
fahren nicht
`;
}
/* ---- Übersicht ---------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
behaelter.innerHTML = `
Fuhrpark
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.
${d.rechte.pflegen
? `
+ Fahrzeug aufnehmen ` : ""}
${kennzahlen(d)}
${reiterleiste(d)}
Alle Fahrzeugarten
${d.arten.map((a) =>
`
${sicher(a.name)} `).join("")}
Eine Meldung älter als ${d.grenzen.meldung} Minuten
wird blass dargestellt.
${d.liste.length
? `${d.liste.map((f) => karte(f, d)).join("")}
`
: `Kein Fahrzeug in diesem Ordner.
`}
`;
verdrahteUebersicht(behaelter);
}
function karte(f, d) {
return `
${sicher(f.bezeichnung)}
${sicher(f.kennzeichen)}
${sicher(f.statusName)}
${sicher(f.artName)}
${f.wartung !== "ok"
? `${sicher(f.wartungName)} ` : ""}
${f.zugewiesenAn
? `${sicher(f.zugewiesenAn)} ` : ""}
${balken("Tank", f.tank, d.grenzen.tank, f.meldungVeraltet)}
${balken("Zustand", f.zustand, d.grenzen.zustand, f.meldungVeraltet)}
${f.gemeldet_am
? `gemeldet vor ${sicher(minuten(f.meldungAlter))}${
f.letzterFahrer ? " · " + sicher(f.letzterFahrer) : ""}`
: "noch nie gemeldet"}
${f.notiz ? ` · ${sicher(f.notiz)}` : ""}
`;
}
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" ? `
🔧
${sicher(f.wartungName)} ${f.notiz ? " — " + sicher(f.notiz) : ""}.
Eingetragen von Hand; ob das Fahrzeug fährt, steht im Einsatzstatus.
` : ""}
${f.meldungVeraltet ? `
🕓
Die letzte Meldung ist ${sicher(minuten(f.meldungAlter))} alt.
Tank und Zustand sagen nichts über den jetzigen Stand.
` : ""}
Einsatzstatus ${sicher(f.statusName)}
Wartung ${sicher(f.wartungName)}
Art ${sicher(f.artName)}
Modell ${sicher(f.modell)}
Station ${sicher(f.station || "—")}
Zugewiesen an
${sicher(f.zugewiesenAn || "niemand")}
Gemeldet
${f.gemeldetAm
? "vor " + sicher(minuten(f.meldungAlter)) + " von "
+ sicher(f.letzterFahrer || "unbekannt")
: "noch nie"}
${balken("Tank", f.tank, 20, f.meldungVeraltet)}
${balken("Zustand", f.zustand, 60, f.meldungVeraltet)}
${f.position
? `Wegpunkt zur letzten Position `
: `Keine Position gemeldet.
`}
Ausstattung
notiert, nicht geprüft
${ausstattung.length
? `${ausstattung.map((a) =>
`${sicher(a)} `).join("")}
`
: `Nichts eingetragen.
`}
${d.rechte.zuweisen
? `Status setzen
Zuweisen ` : ""}
${d.rechte.wartung
? `Wartung ` : ""}
${d.rechte.pflegen
? `Bearbeiten ` : ""}
${d.rechte.pflegen && f.status === "ausser_dienst"
? `Aus dem Fuhrpark nehmen ` : ""}
Eingetragene Vorgänge
aus dem Protokoll, unveränderlich
${d.spur.length ? d.spur.map((s) => `
${sicher(s.aktion)}
${sicher(s.autor)}
${s.grund ? " · " + sicher(s.grund) : ""}
${zeit(s.am)}
`).join("")
: `
Noch kein Eintrag.
`}
`,
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: `
Aktuell eingetragen: ${sicher(f.statusName)}
Neuer Status
${d.status.map((s) =>
`
${sicher(s.name)} `).join("")}
Werkstatt und Außer Dienst darf nur eintragen,
wer die Wartung führt — und beides braucht einen Grund.
Grund
${f.zugewiesenAn ? `
🧍
Das Fahrzeug ist ${sicher(f.zugewiesenAn)} zugewiesen.
Bei Werkstatt oder Außer Dienst wird das gemeldet — die Zuweisung
selbst bleibt bestehen.
` : ""}`,
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: `
Zugewiesen ist zurzeit: ${sicher(f.zugewiesenAn || "niemand")}
ℹ
Eine Zuweisung ist ein Eintrag im Fahrzeugbuch. Sie spawnt kein
Fahrzeug, schließt keines auf und gibt keinen Schlüssel aus.
Im Dienst
${k.liste.length ? k.liste.map((p) => `
${sicher(p.name)}
${p.selbst ? `du ` : ""}
${sicher(p.rolleName)} ·
${sicher(p.personalnummer || "")}
${sicher(p.funktion || "")}
`).join("")
: `
Niemand im Dienst.
`}
Zuweisung aufheben (niemand ist danach eingetragen)
Vermerk / Grund
`,
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: `
Aktuell eingetragen: ${sicher(f.wartungName)}
ℹ
Ein gemeldeter Schaden setzt diesen Status nicht. Was das Spiel
meldet, ist eine Beobachtung — ob gewartet wird, beurteilst du.
Wartungsstatus
${d.wartungsstatus.map((s) =>
`
${sicher(s.name)} `).join("")}
Notiz
Ein leeres Feld löscht die Notiz.
`,
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: `
Über das Kennzeichen findet die Telemetrie das
Fahrzeug wieder — es muss genau dem im Spiel entsprechen.
Station
keine Zuordnung
${d.stationen.map((s) =>
`
${sicher(s.name)} `).join("")}
Ausstattung
${d.ausstattung.map((a) => `
${sicher(a)} `).join("")}
Weitere Ausstattung (mit Komma trennen)
`,
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: `
Das Fahrzeug verschwindet aus dem Fuhrpark. Die Protokolleinträge
dazu bleiben erhalten.
Grund (Pflicht)
`,
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 = `Fuhrpark
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
App.registriereAnsicht("fahrzeug", {
rendern,
aktualisieren() {
// Kein offenes Fenster überschreiben
if (App.zustand.aktivesModul === "fahrzeug" && !document.querySelector(".fenster-hg")) {
rendern(App.$("inhalt"));
}
},
});
})();