Fahrten bearbeitbar, Inspektionsprognose, Bootstrap-Takt reisst nicht mehr ab
Sechs gemeldete Punkte plus ein Fund beim Nachlesen des Ladevorgangs: - HECKKLAPPENSCHLOSS_SENSOR ueberall entfernt (einstellungen.py, entitaeten.py-Katalog, _sicherheitscheck()). HECKKLAPPE_SENSOR bleibt. - Bild-Platzhalter in den Einstellungen war halb abgeschnitten: die bare .carfix-Regel der Desktop-Container-Query traf auch den Platzhalter-Div, dessen inset:0 von der festen Hoehe uebersteuert wurde. Auf .bildbox eingegrenzt. - Inspektion bekommt eine eigene "voraussichtlich am ..."-Zeile (nur Datum). oelwechselPrognose() dafuer auf den gemeinsamen Kern servicePrognose() zurueckgefuehrt. - "Neue Fahrt" bietet jetzt alle Felder, die die Einzelfahrt anzeigt, und die Einzelfahrt laesst sich bearbeiten - gemeinsames fahrtFelder()/ fahrtFormularWerte() nach dem Muster von tankFelder(). Backend: Service audi_dashboard_fahrt_aktualisieren, profil.fahrt_bearbeiten() (edited_fields schuetzt gegen die Automatik, nicht gegen den Nutzer). Die Art-Pille speichert jetzt ebenfalls statt nur lokal umzuschalten. - Titelzeile steht auf dem Handy buendig zu den Kacheln: .back:not(.on) gibt seine Breite und den Gap auf allen Breiten frei, .topbar links 16px. - Ladebildschirm: nachladeAnstossen() und datenLaden() stiegen bei fehlendem HASS aus, ohne eine naechste Runde zu planen - genau die Sackgasse, aus der bisher nur ein Menueklick half. Beide planen jetzt weiter, der Timer wird beim Abhaengen gestoppt. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -1123,24 +1123,24 @@ function fahrzeugMeldungRoh(art) {
|
||||
return { ts: null, km: null };
|
||||
}
|
||||
|
||||
/* App-eigene Ölwechsel-Prognose, eigenständig statt über die allgemeine
|
||||
tagesleistung()/prognose()-Mischrechnung (die für Ölwechsel zu träge ist,
|
||||
weil sie den langfristigen Durchschnitt seit dem allerersten Servicebuch-
|
||||
eintrag mit einfließen lässt): die Berechnung beginnt bewusst komplett neu
|
||||
ab dem Datum des letzten tatsächlich durchgeführten Ölwechsels.
|
||||
/* App-eigene Service-Prognose, eigenständig statt über die allgemeine
|
||||
tagesleistung()/prognose()-Mischrechnung (die dafür zu träge ist, weil sie
|
||||
den langfristigen Durchschnitt seit dem allerersten Servicebucheintrag mit
|
||||
einfließen lässt): die Berechnung beginnt bewusst komplett neu ab dem Datum
|
||||
der letzten tatsächlich durchgeführten Arbeit dieser Art.
|
||||
|
||||
Logik (wie vorgegeben): km und Tage seit dem letzten Ölwechsel ergeben die
|
||||
aktuelle Fahrleistung pro Tag; hochgerechnet auf die restlichen km bis zum
|
||||
eingestellten Intervall (Kundeneigene Einstellung, siehe CAR.oel) ergibt
|
||||
das ein Datum - gedeckelt auf spätestens das Intervall-Zeitlimit (z. B. 1
|
||||
Jahr) nach dem letzten Ölwechsel, falls seither wenig gefahren wurde. */
|
||||
function oelwechselPrognose() {
|
||||
Logik (wie vorgegeben): km und Tage seit diesem Eintrag ergeben die aktuelle
|
||||
Fahrleistung pro Tag; hochgerechnet auf die restlichen km bis zum
|
||||
eingestellten Intervall ergibt das ein Datum - gedeckelt auf spätestens das
|
||||
Intervall-Zeitlimit (z. B. 1 Jahr) nach dem letzten Eintrag, falls seither
|
||||
wenig gefahren wurde.
|
||||
|
||||
Gemeinsame Grundlage für Ölwechsel und Inspektion (siehe die beiden
|
||||
Aufrufer darunter) - beide rechnen identisch, nur mit anderem Intervall. */
|
||||
function servicePrognose(stichwort, intervallKm, intervallMonate) {
|
||||
if (!CAR.odoBekannt) return null;
|
||||
const letzter = letzterEintrag("öl");
|
||||
const letzter = letzterEintrag(stichwort);
|
||||
if (!letzter) return null;
|
||||
const oel = CAR.oel;
|
||||
const intervallKm = oel.modus === "hersteller" ? oel.herstellerKm : Math.min(oel.km, oel.herstellerKm);
|
||||
const intervallMonate = oel.modus === "hersteller" ? oel.herstellerMonate : Math.min(oel.monate, oel.herstellerMonate);
|
||||
const zielKm = letzter.km + intervallKm;
|
||||
const restKm = Math.max(0, zielKm - CAR.odo);
|
||||
const kappDatum = monatePlus(letzter.datum, intervallMonate);
|
||||
@@ -1151,13 +1151,13 @@ function oelwechselPrognose() {
|
||||
const tageSeit = (heute() - dat(letzter.datum)) / dTag;
|
||||
const kmSeit = CAR.odo - letzter.km;
|
||||
if (tageSeit <= 0 || kmSeit <= 0) {
|
||||
// Noch keine belastbare Fahrleistung seit dem Ölwechsel bekannt (z. B.
|
||||
// Noch keine belastbare Fahrleistung seit dem Eintrag bekannt (z. B.
|
||||
// direkt danach) - ohne Rate keine seriöse Km-Prognose, also das
|
||||
// Zeitlimit als einzig belastbare Grenze zeigen.
|
||||
datum = kappDatum;
|
||||
} else {
|
||||
const kmProTagSeitOel = kmSeit / tageSeit;
|
||||
const tageBisZiel = restKm / kmProTagSeitOel;
|
||||
const kmProTagSeither = kmSeit / tageSeit;
|
||||
const tageBisZiel = restKm / kmProTagSeither;
|
||||
const berechnet = new Date(heute().getTime() + tageBisZiel * dTag);
|
||||
datum = berechnet < kappDatum ? berechnet : kappDatum;
|
||||
}
|
||||
@@ -1165,6 +1165,27 @@ function oelwechselPrognose() {
|
||||
return { km: zielKm, restKm, datum };
|
||||
}
|
||||
|
||||
function oelwechselPrognose() {
|
||||
const oel = CAR.oel;
|
||||
return servicePrognose("öl",
|
||||
oel.modus === "hersteller" ? oel.herstellerKm : Math.min(oel.km, oel.herstellerKm),
|
||||
oel.modus === "hersteller" ? oel.herstellerMonate : Math.min(oel.monate, oel.herstellerMonate));
|
||||
}
|
||||
/* Inspektion: dieselben 30.000 km / 24 Monate wie in intervalle()/termine().
|
||||
Angezeigt wird davon bewusst nur das Datum ("voraussichtlich am …") - die
|
||||
Restkilometer stehen bei der Inspektion schon in der Zeile daneben. */
|
||||
function inspektionPrognose() { return servicePrognose("inspektion", 30000, 24); }
|
||||
|
||||
/* Kleine graue Zweitzeile unter dem Termin-Titel, in Service-Kachel und
|
||||
Service-Seite identisch aufgebaut. Leer, solange kein passender
|
||||
Servicebucheintrag vorliegt. */
|
||||
function prognoseZeile(art) {
|
||||
const p = art === "Ölwechsel" ? oelwechselPrognose() : art === "Inspektion" ? inspektionPrognose() : null;
|
||||
if (!p) return "";
|
||||
const km = art === "Ölwechsel" ? de(p.restKm) + " km, " : "";
|
||||
return `<br><span style="color:var(--fg3);font-size:11px">${km}voraussichtlich am ${dedat(p.datum)}</span>`;
|
||||
}
|
||||
|
||||
function terminText(t) {
|
||||
const restKm = t.km !== null && CAR.odoBekannt ? t.km - CAR.odo : null;
|
||||
const restKmAbs = restKm !== null ? Math.abs(restKm) : null;
|
||||
@@ -1318,22 +1339,25 @@ function vAudi() {
|
||||
// (Sensor nicht verfügbar), fällt sie auf dieselbe App-eigene
|
||||
// Servicebuch-Schätzung zurück wie Hauptuntersuchung.
|
||||
const fm = t.art === "Hauptuntersuchung" ? { ts: null, km: null } : fahrzeugMeldungRoh(t.art);
|
||||
// Zusätzlich zur realen Meldung klein die App-eigene Prognose
|
||||
// (Servicebuch + tatsächliche Fahrleistung seit der letzten Arbeit,
|
||||
// siehe servicePrognose()). Steht links unter dem Kachel-Titel statt
|
||||
// rechts bei "am"/"in", die beide gleichwertig (gleiche Schriftgröße
|
||||
// wie "am") untereinander in der rechten Spalte stehen. Die Inspektion
|
||||
// bekommt sie auf Wunsch auch ohne Fahrzeugmeldung und ohne den
|
||||
// km-Anteil - beim Ölwechsel stünde die Zahl dort doppelt, weil
|
||||
// terminText() den generischen Termin schon "voraussichtlich" nennt.
|
||||
const eigen = prognoseZeile(t.art);
|
||||
if (fm.ts == null && fm.km == null) {
|
||||
const x = terminText(t);
|
||||
return `<div class="row" ${letzte}>
|
||||
<dt>${t.art}</dt><dd>${x.haupt}${x.neben ? `<small style="text-align:left">${x.neben}</small>` : ""}</dd></div>`;
|
||||
<dt>${t.art}${t.art === "Inspektion" ? eigen : ""}</dt>
|
||||
<dd>${x.haupt}${x.neben ? `<small style="text-align:left">${x.neben}</small>` : ""}</dd></div>`;
|
||||
}
|
||||
const haupt = fm.ts ? dedat(new Date(fm.ts)) : "";
|
||||
const neben = fm.km != null ? "in " + de(fm.km) + " km" : "";
|
||||
// Zusätzlich zur realen Meldung bei Ölwechsel klein die App-eigene
|
||||
// Prognose (Servicebuch + tatsächliche Fahrleistung seit dem letzten
|
||||
// Wechsel, siehe oelwechselPrognose()) - bei Inspektion bewusst nicht,
|
||||
// wie gefordert. Steht links unter dem Kachel-Titel statt rechts bei
|
||||
// "am"/"in", die beide gleichwertig (gleiche Schriftgröße wie "am")
|
||||
// untereinander in der rechten Spalte stehen.
|
||||
const eigen = t.art === "Ölwechsel" ? oelwechselPrognose() : null;
|
||||
return `<div class="row" ${letzte}>
|
||||
<dt>${t.art}${eigen ? `<br><span style="color:var(--fg3);font-size:11px">${de(eigen.restKm)} km, voraussichtlich am ${dedat(eigen.datum)}</span>` : ""}</dt>
|
||||
<dt>${t.art}${eigen}</dt>
|
||||
<dd>${haupt}${neben ? `<span style="display:block">${neben}</span>` : ""}</dd></div>`;
|
||||
}).join("")}
|
||||
</dl></button>
|
||||
@@ -1628,11 +1652,12 @@ function vService() {
|
||||
const anzeige = iv.hersteller || iv.eigen;
|
||||
const fm = fahrzeugMeldungRoh(t.art);
|
||||
const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null;
|
||||
// Forecast-Zeile nur bei Ölwechsel (Kundeneigenes Intervall aus
|
||||
// "Einrichten", eigenständige Reset-nach-Service-Logik, siehe
|
||||
// oelwechselPrognose()) - bei Inspektion und Hauptuntersuchung
|
||||
// bewusst keine Forecast-Zeile mehr.
|
||||
// Forecast-Zeile aus dem kundeneigenen Intervall in "Einrichten" mit
|
||||
// eigenständiger Reset-nach-Service-Logik (servicePrognose()): beim
|
||||
// Ölwechsel mit Restkilometern, bei der Inspektion auf Wunsch nur mit
|
||||
// dem Datum. Hauptuntersuchung bleibt bewusst ohne.
|
||||
const eigenOel = t.art === "Ölwechsel" ? oelwechselPrognose() : null;
|
||||
const eigenInsp = t.art === "Inspektion" ? inspektionPrognose() : null;
|
||||
return `<div class="row" ${letzte}>
|
||||
<dt>${t.art}
|
||||
${t.vereinbart ? `<br><span style="color:var(--red);font-size:11px">Werkstatt am ${dedat(new Date(t.vereinbart))}</span>` : ""}
|
||||
@@ -1642,8 +1667,10 @@ function vService() {
|
||||
${fm.km != null || fmDatum ? `
|
||||
<span class="werte">${fm.km != null ? de(fm.km) + " km" : ""}${fm.km != null && fmDatum ? " / " : ""}${fmDatum || ""}</span>
|
||||
${eigenOel ? `<small style="text-align:left">${de(eigenOel.restKm)} km, voraussichtlich am ${dedat(eigenOel.datum)}</small>` : ""}
|
||||
${eigenInsp ? `<small style="text-align:left">voraussichtlich am ${dedat(eigenInsp.datum)}</small>` : ""}
|
||||
` : `
|
||||
<span class="werte">${eigenOel ? de(eigenOel.restKm) + " km · " + dedat(eigenOel.datum) : (anzeige.km !== null ? de(anzeige.km) + " km · " : "") + dedat(anzeige.datum)}</span>
|
||||
${eigenInsp ? `<small style="text-align:left">voraussichtlich am ${dedat(eigenInsp.datum)}</small>` : ""}
|
||||
`}
|
||||
</dd>
|
||||
</div>`; }).join("")}
|
||||
@@ -2440,17 +2467,69 @@ function vReifen() {
|
||||
${satz("winter")}`;
|
||||
}
|
||||
|
||||
/* Gemeinsames Formular für "Neue Fahrt" und das Bearbeiten einer bestehenden
|
||||
Fahrt - dieselben Felder an beiden Stellen, damit sie nicht auseinander-
|
||||
laufen können (gleiches Muster wie tankFelder()/tankFormularWerte()). Die
|
||||
Felder decken genau das ab, was die Einzelfahrt-Seite anzeigt; Verbrauch,
|
||||
Status und km-Quelle bleiben außen vor, die meldet das Fahrzeug bzw. leitet
|
||||
das Backend ab. */
|
||||
const FAHRT_ARTEN = ["privat", "arbeitsweg"];
|
||||
|
||||
/* Datum für ein <input type="date"> in Ortszeit. Bewusst nicht über
|
||||
toISOString(), das rechnet nach UTC um und liefert kurz nach Mitternacht
|
||||
den Vortag. */
|
||||
function datumsfeld(d) {
|
||||
const zwei = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${zwei(d.getMonth() + 1)}-${zwei(d.getDate())}`;
|
||||
}
|
||||
function zeitfeld(d) {
|
||||
const zwei = (n) => String(n).padStart(2, "0");
|
||||
return `${zwei(d.getHours())}:${zwei(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
function fahrtFelder(praefix, t) {
|
||||
const start = t && t.ts_start ? new Date(t.ts_start) : new Date();
|
||||
const dauer = t && t.duration_s != null ? Math.round(t.duration_s / 60) : "";
|
||||
const art = t && t.art ? t.art : FAHRT_ARTEN[0];
|
||||
const wert = (v) => (v == null ? "" : v);
|
||||
return `
|
||||
<div class="feld"><label for="${praefix}Datum">Datum</label><input type="date" id="${praefix}Datum" value="${datumsfeld(start)}"></div>
|
||||
<div class="feld"><label for="${praefix}Start">Startzeit</label><input type="time" id="${praefix}Start" value="${t ? zeitfeld(start) : "08:00"}"></div>
|
||||
<div class="feld"><label for="${praefix}Dauer">Dauer</label>
|
||||
<span class="mitEinheit"><input type="number" min="0" id="${praefix}Dauer" value="${dauer}" placeholder="0"><span>min</span></span></div>
|
||||
<div class="feld"><label for="${praefix}Art">Art</label>
|
||||
<select id="${praefix}Art">${FAHRT_ARTEN.map((a) => `<option ${a === art ? "selected" : ""}>${a}</option>`).join("")}</select></div>
|
||||
<div class="feld"><label for="${praefix}Startort">Startort</label><input id="${praefix}Startort" value="${esc(wert(t && t.start_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}Zielort">Zielort</label><input id="${praefix}Zielort" value="${esc(wert(t && t.end_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}OdoStart">Startkilometer</label><input type="number" id="${praefix}OdoStart" value="${wert(t && t.odo_start)}" placeholder="wird ergänzt"></div>
|
||||
<div class="feld"><label for="${praefix}OdoEnde">Endkilometer</label><input type="number" id="${praefix}OdoEnde" value="${wert(t && t.odo_end)}" placeholder="wird ergänzt"></div>
|
||||
<div class="feld" style="border-bottom:0"><label for="${praefix}Distanz">Gefahrene Distanz</label>
|
||||
<span class="mitEinheit"><input type="number" step="0.1" id="${praefix}Distanz" value="${wert(t && t.distance_km)}" placeholder="aus km-Stand"><span>km</span></span></div>`;
|
||||
}
|
||||
|
||||
function fahrtFormularWerte(praefix) {
|
||||
const zahl = (id) => { const v = $(`#${praefix}${id}`).value; return v === "" ? null : parseFloat(v.replace(",", ".")); };
|
||||
const text = (id) => $(`#${praefix}${id}`).value.trim() || null;
|
||||
const dauer = parseInt($(`#${praefix}Dauer`).value, 10) || 0;
|
||||
const tsStart = new Date(`${$(`#${praefix}Datum`).value}T${$(`#${praefix}Start`).value}:00`);
|
||||
return {
|
||||
ts_start: tsStart.toISOString(),
|
||||
ts_end: new Date(tsStart.getTime() + dauer * 60000).toISOString(),
|
||||
art: $(`#${praefix}Art`).value,
|
||||
start_ort: text("Startort"), ziel_ort: text("Zielort"),
|
||||
odo_start: zahl("OdoStart"), odo_end: zahl("OdoEnde"), distanz: zahl("Distanz"),
|
||||
};
|
||||
}
|
||||
|
||||
function vTripsFormular() {
|
||||
// eigene Funktion, damit sowohl der volle als auch der Leer-Zustand
|
||||
// (§11 Abnahmekriterium 1: eine Fahrt muss erfasst werden können) dasselbe
|
||||
// Formular zeigen - vorher fehlte es im Leer-Zustand komplett, der Knopf
|
||||
// wechselte zu "Abbrechen", aber es erschienen keine Felder.
|
||||
return `${fahrtOffen ? `<div class="tile"><span class="label">Neue Fahrt</span>
|
||||
<div class="feld"><label>Datum</label><input type="date" id="neuFahrtDatum" value="${new Date().toISOString().slice(0, 10)}"></div>
|
||||
<div class="feld"><label>Startzeit</label><input type="time" id="neuFahrtStart" value="08:00"></div>
|
||||
<div class="feld" style="border-bottom:0"><label>Dauer in Minuten</label><input type="number" id="neuFahrtDauer" placeholder="0"></div>
|
||||
<button class="aktion" data-fahrtspeichern>Speichern</button></div>` : ""}
|
||||
<button class="aktion primaer" data-fahrt>${fahrtOffen ? "Abbrechen" : "Fahrt erfassen"}</button>`;
|
||||
${fahrtFelder("neuFahrt", null)}
|
||||
<button class="aktion primaer" data-fahrtspeichern>Speichern</button></div>` : ""}
|
||||
<button class="aktion ${fahrtOffen ? "" : "primaer"}" data-fahrt>${fahrtOffen ? "Abbrechen" : "Fahrt erfassen"}</button>`;
|
||||
}
|
||||
function vTrips() {
|
||||
const jahre = [...new Set(TRIPS.map((t) => new Date(t.ts_start).getFullYear()))].sort((a, b) => b - a);
|
||||
@@ -2491,6 +2570,16 @@ function vTrips() {
|
||||
function vTrip(id) {
|
||||
const t = TRIPS.find((x) => x.trip_id === id);
|
||||
if (!t) return `<div class="tile">Fahrt nicht gefunden</div>`;
|
||||
|
||||
if (fahrtBearbeitenOffen) {
|
||||
return `
|
||||
<div class="tile"><span class="label">Fahrt bearbeiten</span>
|
||||
${fahrtFelder("bearbFahrt", t)}
|
||||
<button class="aktion primaer" data-fahrtaktualisieren="${t.trip_id}">Speichern</button>
|
||||
<button class="aktion" data-fahrtbearbeiten>Abbrechen</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
${mapBox()}
|
||||
<div class="tile flat" style="margin:18px 0 14px">
|
||||
@@ -2509,7 +2598,9 @@ function vTrip(id) {
|
||||
<div class="row"><dt>Dauer</dt><dd>${Math.round(t.duration_s / 60)} min</dd></div>
|
||||
<div class="row"><dt>Verbrauch</dt><dd>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : "liegt nicht vor"}<small>vom Fahrzeug gemeldet</small></dd></div>
|
||||
<div class="row"><dt>Status</dt><dd>${t.status}<small>Quelle: ${t.km_quelle || "–"}</small></dd></div>
|
||||
</dl></div>
|
||||
</dl>
|
||||
<button class="aktion" data-fahrtbearbeiten>Werte bearbeiten</button>
|
||||
</div>
|
||||
<button class="aktion loeschen" data-tripdel="${t.trip_id}">Fahrt löschen</button>`;
|
||||
}
|
||||
|
||||
@@ -2637,6 +2728,7 @@ function vStat() {
|
||||
let neuOffen = false;
|
||||
let fahrtOffen = false;
|
||||
let tankBearbeitenOffen = false;
|
||||
let fahrtBearbeitenOffen = false;
|
||||
// Ergebnis eines geparsten, noch nicht gespeicherten Belegs: { praefix, daten }
|
||||
// - überlagert in tankFelder() die Beleg-Felder des jeweils offenen Formulars.
|
||||
let belegEntwurf = null;
|
||||
@@ -3324,6 +3416,9 @@ const go = (name, id) => {
|
||||
entwurfIndex = null;
|
||||
}
|
||||
sheet = null;
|
||||
// Ein offenes Bearbeiten-Formular gehört zu genau der Fahrt, auf der es
|
||||
// geöffnet wurde - sonst begrüßt die nächste Fahrt einen mit Eingabefeldern.
|
||||
fahrtBearbeitenOffen = false;
|
||||
/* B3: echter Verlauf statt statischer ZURUECK-Tabelle. Ein Tabwechsel setzt
|
||||
den Stapel zurück, weil Tabs Wurzeln sind, keine Ebenen. */
|
||||
if (istTab(name)) routeStack = [];
|
||||
@@ -3333,6 +3428,7 @@ const go = (name, id) => {
|
||||
};
|
||||
function zurueck() {
|
||||
sheet = null;
|
||||
fahrtBearbeitenOffen = false;
|
||||
const ziel = routeStack.pop() || { name: ZURUECK[route.name] || "home" };
|
||||
navRichtung = "raus";
|
||||
route = ziel; render();
|
||||
@@ -3821,12 +3917,18 @@ function ereignisseVerdrahten() {
|
||||
}
|
||||
if (e.target.closest("[data-fahrt]")) { fahrtOffen = !fahrtOffen; render(); return; }
|
||||
if (e.target.closest("[data-fahrtspeichern]")) {
|
||||
const datum = $("#neuFahrtDatum").value, start = $("#neuFahrtStart").value, dauer = parseInt($("#neuFahrtDauer").value, 10) || 0;
|
||||
const tsStart = new Date(`${datum}T${start}:00`);
|
||||
const tsEnd = new Date(tsStart.getTime() + dauer * 60000);
|
||||
serviceRufen("audi_dashboard_fahrt_manuell_anlegen", { ts_start: tsStart.toISOString(), ts_end: tsEnd.toISOString(), art: "privat" });
|
||||
serviceRufen("audi_dashboard_fahrt_manuell_anlegen", fahrtFormularWerte("neuFahrt"));
|
||||
fahrtOffen = false; render(); return;
|
||||
}
|
||||
if (e.target.closest("[data-fahrtbearbeiten]")) {
|
||||
fahrtBearbeitenOffen = !fahrtBearbeitenOffen; render(); return;
|
||||
}
|
||||
const fahrtAkt = e.target.closest("[data-fahrtaktualisieren]");
|
||||
if (fahrtAkt) {
|
||||
serviceRufen("audi_dashboard_fahrt_aktualisieren",
|
||||
{ trip_id: fahrtAkt.dataset.fahrtaktualisieren, ...fahrtFormularWerte("bearbFahrt") });
|
||||
fahrtBearbeitenOffen = false; render(); return;
|
||||
}
|
||||
if (e.target.closest("[data-tankspeichern]")) {
|
||||
const w = tankFormularWerte("neuTank");
|
||||
if (w.liter > 0 && w.kosten > 0) {
|
||||
@@ -3987,7 +4089,14 @@ function ereignisseVerdrahten() {
|
||||
const a = e.target.closest("[data-acc]");
|
||||
if (a) { const b = ROOT.getElementById(a.dataset.acc); a.setAttribute("aria-expanded", b.classList.toggle("on")); return; }
|
||||
const art = e.target.closest("[data-art]");
|
||||
if (art) { const tr = TRIPS.find((x) => x.trip_id === art.dataset.art); tr.art = tr.art === "arbeitsweg" ? "privat" : "arbeitsweg"; render(); return; }
|
||||
if (art) {
|
||||
const tr = TRIPS.find((x) => x.trip_id === art.dataset.art);
|
||||
tr.art = tr.art === "arbeitsweg" ? "privat" : "arbeitsweg";
|
||||
// Bis 2026-08-17 blieb das Umschalten rein lokal und war nach dem
|
||||
// naechsten Backend-Push wieder weg - jetzt wandert es mit.
|
||||
serviceRufen("audi_dashboard_fahrt_aktualisieren", { trip_id: tr.trip_id, art: tr.art });
|
||||
render(); return;
|
||||
}
|
||||
// Standort (Vollbild) - Karten-/Menü-Bedienung manipuliert SMAP/das Menü-
|
||||
// DOM direkt statt über render() (siehe Kopfkommentar Standort-Abschnitt).
|
||||
if (e.target.closest("[data-standort-umschalten]")) { standortMenuUmschalten(); return; }
|
||||
@@ -4209,8 +4318,15 @@ function belegErgebnisVerarbeiten(daten) {
|
||||
Deshalb jetzt ohne festes Abbruchlimit: nach der anfänglichen schnellen
|
||||
Serie (siehe NACHLADE_INTERVALL_MS) läuft es mit einem größeren, aber
|
||||
endlosen Intervall weiter, bis Daten da sind oder das Element abgehängt
|
||||
wird (disconnectedCallback stoppt hier nichts explizit, aber ein
|
||||
getimeter Aufruf ohne HASS/callWS bricht oben ohnehin ab). */
|
||||
wird (disconnectedCallback stoppt den Timer, connectedCallback stößt ihn
|
||||
über datenLaden() wieder an).
|
||||
|
||||
2026-08-17: Der Takt darf an keiner Stelle abreißen. Vorher stieg sowohl
|
||||
nachladeAnstossen() (bei fehlendem HASS/callWS) als auch datenLaden() (bei
|
||||
fehlendem HASS) still aus, ohne eine nächste Runde zu planen - danach half
|
||||
nur noch ein zufälliger hass-Push oder ein Klick ins Menü (go() ruft
|
||||
datenLaden() mit auf). Genau das war das gemeldete "Lädt … bis man auf
|
||||
einen Menüpunkt klickt". */
|
||||
const NACHGELADEN = {};
|
||||
let nachladeTimer = null;
|
||||
let nachladeVersuche = 0;
|
||||
@@ -4219,7 +4335,7 @@ const NACHLADE_LANGSAM_MS = 10000;
|
||||
const NACHLADE_SCHNELLE_VERSUCHE = 15;
|
||||
|
||||
function nachladeAnstossen() {
|
||||
if (nachladeTimer || !HASS || !HASS.callWS) return;
|
||||
if (nachladeTimer) return;
|
||||
nachladeVersuche++;
|
||||
// Erster Versuch fast sofort statt erst nach vollem Intervall - der
|
||||
// Wettlauf mit pyscripts beim_start() (siehe Kopfkommentar) ist meist
|
||||
@@ -4232,26 +4348,40 @@ function nachladeAnstossen() {
|
||||
: NACHLADE_LANGSAM_MS;
|
||||
nachladeTimer = setTimeout(async () => {
|
||||
nachladeTimer = null;
|
||||
try {
|
||||
const states = await HASS.callWS({ type: "get_states" });
|
||||
const finden = (id) => states.find((s) => s.entity_id === id);
|
||||
NACHGELADEN.profil = finden("pyscript.audi_dashboard_profil") || NACHGELADEN.profil;
|
||||
NACHGELADEN.fahrten = finden("pyscript.audi_dashboard_fahrten") || NACHGELADEN.fahrten;
|
||||
NACHGELADEN.tank = finden("pyscript.audi_dashboard_tankvorgaenge") || NACHGELADEN.tank;
|
||||
NACHGELADEN.status = finden("pyscript.audi_dashboard_fahrzeugstatus") || NACHGELADEN.status;
|
||||
NACHGELADEN.batt = finden("pyscript.audi_dashboard_batterieverlauf") || NACHGELADEN.batt;
|
||||
// Ohne diese Zeile bleibt der Setup-Katalog nach einem HA-Neustart leer,
|
||||
// bis zufaellig ein Push kommt - siehe setupBereit().
|
||||
NACHGELADEN.entitaeten = finden("pyscript.audi_dashboard_entitaeten") || NACHGELADEN.entitaeten;
|
||||
} catch (e) {
|
||||
// wird beim naechsten Versuch erneut probiert
|
||||
// HASS kann hier noch fehlen: panel_custom erzeugt das Element und setzt
|
||||
// hass nicht immer in dieser Reihenfolge. Frueher brach die Schleife in so
|
||||
// einem Fall oben ganz ab (Bedingung !HASS || !HASS.callWS) und wurde nie
|
||||
// wieder angestossen - genau der dauerhaft haengende "Lädt …"-Bildschirm.
|
||||
// Jetzt wird die Runde nur uebersprungen, der Takt laeuft weiter.
|
||||
if (HASS && HASS.callWS) {
|
||||
try {
|
||||
const states = await HASS.callWS({ type: "get_states" });
|
||||
const finden = (id) => states.find((s) => s.entity_id === id);
|
||||
NACHGELADEN.profil = finden("pyscript.audi_dashboard_profil") || NACHGELADEN.profil;
|
||||
NACHGELADEN.fahrten = finden("pyscript.audi_dashboard_fahrten") || NACHGELADEN.fahrten;
|
||||
NACHGELADEN.tank = finden("pyscript.audi_dashboard_tankvorgaenge") || NACHGELADEN.tank;
|
||||
NACHGELADEN.status = finden("pyscript.audi_dashboard_fahrzeugstatus") || NACHGELADEN.status;
|
||||
NACHGELADEN.batt = finden("pyscript.audi_dashboard_batterieverlauf") || NACHGELADEN.batt;
|
||||
// Ohne diese Zeile bleibt der Setup-Katalog nach einem HA-Neustart leer,
|
||||
// bis zufaellig ein Push kommt - siehe setupBereit().
|
||||
NACHGELADEN.entitaeten = finden("pyscript.audi_dashboard_entitaeten") || NACHGELADEN.entitaeten;
|
||||
} catch (e) {
|
||||
// wird beim naechsten Versuch erneut probiert
|
||||
}
|
||||
}
|
||||
datenLaden(true);
|
||||
// Rueckversicherung: datenLaden() stoesst die naechste Runde selbst an,
|
||||
// aber nur, wenn es nicht vorher aussteigt. Der Takt darf an keiner
|
||||
// Stelle abreissen, solange noch keine Daten da sind.
|
||||
if (!DATEN_GELADEN) nachladeAnstossen();
|
||||
}, verzoegerung);
|
||||
}
|
||||
|
||||
async function datenLaden(erzwingen) {
|
||||
if (!HASS) return;
|
||||
if (!HASS) {
|
||||
if (!DATEN_GELADEN) nachladeAnstossen();
|
||||
return;
|
||||
}
|
||||
const profilState = HASS.states["pyscript.audi_dashboard_profil"] || NACHGELADEN.profil;
|
||||
const fahrtenState = HASS.states["pyscript.audi_dashboard_fahrten"] || NACHGELADEN.fahrten;
|
||||
const tankState = HASS.states["pyscript.audi_dashboard_tankvorgaenge"] || NACHGELADEN.tank;
|
||||
@@ -4355,6 +4485,9 @@ class AudiDashboardPanel extends HTMLElement {
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// Nachlade-Takt anhalten, solange das Panel nicht im Dokument haengt -
|
||||
// connectedCallback() stoesst ihn ueber datenLaden() wieder an.
|
||||
if (nachladeTimer) { clearTimeout(nachladeTimer); nachladeTimer = null; }
|
||||
// Alle drei Karten, nicht nur MAP: sonst bleibt beim Abhaengen des Panels
|
||||
// (Standort- oder Uebersichtsseite offen) eine Leaflet-Instanz samt ihrem
|
||||
// window-Resize-Horcher und laufenden Kachelabrufen am Leben.
|
||||
|
||||
Reference in New Issue
Block a user