Add real Audi CI battery/edit icons, manual km-correction for tire sets
Replaced the hand-drawn placeholder battery icon with the real Audi CI battery-12v-l/-s (user supplied the actual SVGs) next to "Ruhespannung", matching the existing poi-l/poi-s size-variant convention. Added a km-correction control to the Sommerräder/Winterräder tiles: a pencil icon (Audi CI edit-s) opens an inline field to manually correct the tire-set's accumulated km. The tricky part was persistence - configCarZuProfil() deliberately never writes km back (a stale browser copy could otherwise clobber a since-elapsed automatic increment), so this needed its own backend service, audi_dashboard_reifen_km_setzen, that overwrites only the stored counter and leaves referenz_odo_km alone - the existing odometer-delta tracking in reifenzaehler.py then continues accumulating from the corrected value on its own, no other backend change required. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1037,6 +1037,51 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
|
||||
left:auto` (added last round to right-align it under its old block-flow parent) was removed since
|
||||
`.radkopf-row`'s flex layout does that job now. Deployed to `audi_ha_test`, confirmed live via
|
||||
screenshot for both Sommerräder (mounted) and Winterräder (unmounted) tiles.
|
||||
- [x] Ninth round the same day (2026-08-16) — "Mein Audi" image-cycle tap, still broken after the
|
||||
seventh round's `bildWeiter()` crash fix: reproduced with a genuine mouse click (not a JS
|
||||
`.click()` call) via screenshot and found the actual cause was one level upstream. This docker
|
||||
test container has zero uploaded car photos, so every gallery image is in `bild-fehlt` state,
|
||||
and `.bildbox.bild-fehlt img{display:none}` makes the `<img id="carimg">` itself un-hit-testable
|
||||
— a real tap lands on the `.platzhalter` div instead, which the old handler
|
||||
(`e.target.id === "carimg"`) never matched, so nothing happened. First fix wrapped the gallery
|
||||
image in a `data-bildgalerie` container that opened the upload picker when empty and advanced
|
||||
when a photo was loaded; the user then clarified uploads should stay exclusively in Einstellungen
|
||||
("Bild der Übersicht") — Mein Audi should only ever cycle. Settled on: the wrapper stays (needed
|
||||
so taps land on something clickable regardless of `bild-fehlt` state) but always calls
|
||||
`bildWeiter()`, no upload branch. Verified live with a real click (screenshot → click → recheck
|
||||
`.dots`): the active dot correctly advances, no console errors beyond this environment's
|
||||
pre-existing service-worker/404 noise.
|
||||
Also this round: replaced the "Farbskala Ladezustand (AGM, Ruhespannung)" text-only label above
|
||||
the battery voltage color scale with a small battery icon + "Ruhespannung" (new `.bv-skala-kopf`
|
||||
flex row, same geometry as the header's `.sync` icon+text pattern). First attempt hand-drew a
|
||||
generic outline battery glyph since no "battery" icon exists in this repo's local CI subset
|
||||
(`design/assets/icons/`); the user then supplied the real `battery-12v-l.svg`/`battery-12v-s.svg`
|
||||
files from the actual Audi CI icon set (not something this session has fetch access to — asked
|
||||
the user rather than guessing a URL or fabricating an icon and calling it authentic). Added both
|
||||
as `CI.battery12vL`/`CI.battery12vS` (verbatim paths, `l` on the 48-grid, `s` on the 24-grid,
|
||||
matching the existing `poi-l`/`poi-s` size-variant convention); only the `s` variant is used here
|
||||
via `ciSVG()` since this is a small 15px inline label icon.
|
||||
Deployed to `audi_ha_test`, confirmed live via screenshot.
|
||||
Also this round (user then supplied the real `edit-l.svg`/`edit-s.svg` too): added a manual
|
||||
km-correction control to the Reifen tiles, per explicit request — a small pencil icon (Audi CI
|
||||
`edit-s`, new `CI.editS`/`CI.editL`) to the left of each Sommerräder/Winterräder km figure, "the
|
||||
counter shall add continuously to these km even if the user changes the km". The tricky part
|
||||
was persistence: `configCarZuProfil()` *deliberately* strips `km` before every profile write
|
||||
(see its own comment - km is "vom Backend fortgeschrieben", a stale browser copy must never
|
||||
clobber a since-elapsed automatic increment), so simply mutating `CAR.reifen[k].km` and calling
|
||||
`profilSpeichern()` like every other Reifen field does would silently drop the edit. Added a
|
||||
dedicated backend service instead, `audi_dashboard_reifen_km_setzen(satz, km)` in
|
||||
`reifenzaehler.py`, that only overwrites the stored counter itself and leaves
|
||||
`reifen.referenz_odo_km` (the last-processed odometer reference) untouched - the existing
|
||||
`kilometerstand_geaendert()` trigger always reads the counter fresh off disk before adding the
|
||||
next delta, so continuing to accumulate from the corrected value required no other backend
|
||||
change at all. New `kmOffen` state (which satz's correction field is open, mirroring the
|
||||
existing `nmOffen`/`bildMenuOffen` single-open-popup pattern) plus `data-kmauf`/
|
||||
`data-kmspeichern` handlers, styled like the existing `.montiert`/`.zahnrad` controls (44×44pt
|
||||
invisible tap target via `::before`). Verified end-to-end: edited Sommerräder from 150→4200 km,
|
||||
confirmed the pyscript service ran via the container's HA log, then did a full page reload
|
||||
(forces a real backend re-fetch, not just optimistic local UI state) and the corrected value
|
||||
was still there.
|
||||
- [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) —
|
||||
text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed
|
||||
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
|
||||
|
||||
@@ -105,3 +105,37 @@ def audi_dashboard_reifen_wechseln(satz=None):
|
||||
return
|
||||
_fortschreiben_und_veroeffentlichen(_aktueller_km(), neuer_aktiv=satz)
|
||||
log.info(f"audi_dashboard: aktiver Reifensatz auf '{satz}' umgeschaltet")
|
||||
|
||||
|
||||
@service
|
||||
def audi_dashboard_reifen_km_setzen(satz=None, km=None):
|
||||
"""Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes
|
||||
(Editier-Symbol neben der km-Anzeige in der App). Überschreibt nur den
|
||||
gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete
|
||||
Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste
|
||||
Fortschreibung (kilometerstand_geaendert oben) weiterhin korrekt von hier
|
||||
aus weiterzählt, statt auf den neuen Wert einen erneuten Delta seit dem
|
||||
alten Stand aufzuschlagen. configCarZuProfil() im Frontend schreibt km nie
|
||||
selbst mit (siehe Kommentar dort) - genau deshalb braucht es diesen
|
||||
eigenen Service statt eines einfachen Profil-Überschreibens."""
|
||||
if satz not in ("sommer", "winter"):
|
||||
log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'")
|
||||
return
|
||||
try:
|
||||
km = round(float(km), 1)
|
||||
except (TypeError, ValueError):
|
||||
log.warning(f"audi_dashboard: ungültiger km-Wert '{km}'")
|
||||
return
|
||||
if km < 0:
|
||||
log.warning(f"audi_dashboard: negativer km-Wert '{km}' abgelehnt")
|
||||
return
|
||||
p = profil.profil_lesen()
|
||||
if p is None:
|
||||
return
|
||||
reifen = p.setdefault("reifen", {})
|
||||
saetze = reifen.setdefault("saetze", {})
|
||||
saetze.setdefault(satz, {})["km"] = km
|
||||
profil.profil_schreiben(p)
|
||||
frontend_veroeffentlichung.profil_veroeffentlichen()
|
||||
_zustaende_veroeffentlichen(p)
|
||||
log.info(f"audi_dashboard: km-Stand '{satz}' manuell auf {km} gesetzt")
|
||||
|
||||
@@ -255,8 +255,17 @@ function carImg(modus) {
|
||||
return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein");
|
||||
}
|
||||
const info = bildInfo(bild);
|
||||
return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg") +
|
||||
`<div class="dots" id="dots">${BILDER.map((_, i) => `<i class="${i === bild ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
/* data-bildgalerie: ein Tipp irgendwo auf die Kachel (Foto oder Platzhalter)
|
||||
schaltet zum naechsten Bild weiter - Hochladen/Ersetzen/Loeschen bleibt
|
||||
bewusst allein den Einstellungen vorbehalten (Bild der Übersicht),
|
||||
deshalb hier kein data-bildupload/data-bildklick, nur bildWeiter().
|
||||
Der Wrapper ist noetig, weil das <img> selbst bei fehlendem Foto per
|
||||
.bildbox.bild-fehlt img{display:none} unsichtbar und damit unklickbar
|
||||
ist - der bisherige Handler (id="carimg") griff dann ins Leere. */
|
||||
return `<div data-bildgalerie>
|
||||
${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg")}
|
||||
</div>
|
||||
<div class="dots" id="dots">${BILDER.map((_, i) => `<i class="${i === bild ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
}
|
||||
/* Bildmarke des Kraftstoffanbieters (Shell). Anders als bei den Fahrzeug-
|
||||
fotos ist hier kein generischer Platzhalter-Rahmen sinnvoll, weil das Logo
|
||||
@@ -947,6 +956,14 @@ const CI = {
|
||||
upload: '<path fill="currentColor" d="M13 20h-1V9.7l-3.65 3.65-.7-.7 4.85-4.86 4.85 4.86-.7.7L13 9.71zM6 5V4h13v1z"/>',
|
||||
fuelRange: '<path fill="currentColor" d="M2 3.5A2.5 2.5 0 0 1 4.5 1h6A2.5 2.5 0 0 1 13 3.5V10h.5a2.5 2.5 0 0 1 2.5 2.5v5a1.5 1.5 0 0 0 3 0V8.07a16.4 16.4 0 0 0-2.89-5.76l.78-.62a17.4 17.4 0 0 1 3.1 6.17l.01.07v9.57a2.5 2.5 0 0 1-5 0v-5c0-.83-.67-1.5-1.5-1.5H13v7h-1V3.5c0-.83-.67-1.5-1.5-1.5h-6C3.67 2 3 2.67 3 3.5V22h4v1H1v-1h1zM4 10h7v1H4zm5 14v-5h1v2h13v-2h1v5h-1v-2H10v2z"/>',
|
||||
parking: '<path fill="currentColor" d="M0 2.5A2.5 2.5 0 0 1 2.5 0H23v20.5a2.5 2.5 0 0 1-2.5 2.5H0zM2.5 1C1.67 1 1 1.67 1 2.5V22h19.5c.83 0 1.5-.67 1.5-1.5V1zm11.59 6.35C13.6 7 13.02 7 12.5 7H8v6h4.5c.52 0 1.11-.01 1.59-.35.44-.3.91-1 .91-2.65 0-1.66-.47-2.34-.91-2.65M8 14v3H7V6h5.53c.5 0 1.38 0 2.13.53C15.47 7.09 16 8.16 16 10s-.53 2.9-1.34 3.47a3.6 3.6 0 0 1-2.13.53z"/>',
|
||||
/* battery-12v-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert,
|
||||
verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */
|
||||
battery12vL: '<path fill="currentColor" d="M11 11h7v4h13v-4h7v4h6v23H5V15h6zm1 4h5v-3h-5zm-6 1v21h37V16zm31-1v-3h-5v3zm-3 11v4.5h-1V26h-4.5v-1H33v-4.5h1V25h4.5v1zm-15.5 0h-8v-1h8z"/>',
|
||||
battery12vS: '<path fill="currentColor" d="M5 5h4v2h6V5h4v2h3v12H2V7h3zm1 2h2V6H6zM3 8v10h18V8zm15-1V6h-2v1zm-1 6v2h-1v-2h-2v-1h2v-2h1v2h2v1zm-8 0H5v-1h4z"/>',
|
||||
/* edit-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert,
|
||||
verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */
|
||||
editL: '<path fill="currentColor" d="M28.99 3.6c.34-.1.73-.11 1.1.04h.02l3.61 1.81c.35.18.58.5.68.84s.11.73-.04 1.1v.01l-2.01 4.06 4.02 2.02-6.72 13.44-.9-.44 6.28-12.56-3.13-1.56-13.6 27.47-6.3 4.66v-7.8L28.15 4.27c.18-.35.5-.58.84-.68Zm2.46 7.42 2-4.02a.65.65 0 0 0 0-.41c-.05-.14-.12-.22-.17-.24l-3.58-1.8a.65.65 0 0 0-.41 0c-.14.05-.22.12-.24.17L27.02 8.8zm-4.88-1.33-13.4 26.89 4.5 2.25L31.01 11.9l-4.44-2.2ZM13 37.61v4.9l3.95-2.93z"/>',
|
||||
editS: '<path fill="currentColor" d="m14.14 1.06 2.4 1.1.04.02c.23.16.42.39.52.65.1.27.13.6-.06.91l-.91 1.94 2.94 1.38-2.82 5.95-.9-.42 2.38-5.05-2.02-.95L9.5 19.8 5 23.57v-5.88l7.56-16.14.02-.03c.16-.23.39-.42.65-.52a1 1 0 0 1 .9.06Zm1.08 4.2.94-2 .02-.03v-.04a.4.4 0 0 0-.13-.16l-2.4-1.1-.02-.01h-.04a.4.4 0 0 0-.16.13L12.53 4l2.7 1.27Zm-3.12-.37L6.16 17.56l2.7 1.27L14.8 6.16 12.1 4.9ZM6 18.6v2.84l2.18-1.81L6 18.59Z"/>',
|
||||
};
|
||||
function ciSVG(pfad, px = 16) {
|
||||
return `<svg viewBox="0 0 24 24" width="${px}" height="${px}" fill="none" aria-hidden="true">${pfad}</svg>`;
|
||||
@@ -1481,7 +1498,10 @@ function vBatterieverlauf() {
|
||||
` : `
|
||||
<span class="label" style="margin-top:14px;color:var(--fg3)">${leerText}</span>
|
||||
`}
|
||||
<span class="label" style="margin-top:16px">Farbskala Ladezustand (AGM, Ruhespannung)</span>
|
||||
<div class="label bv-skala-kopf" style="margin-top:16px">
|
||||
${ciSVG(CI.battery12vS, 15)}
|
||||
<span>Ruhespannung</span>
|
||||
</div>
|
||||
<div style="margin-top:8px;height:8px;border-radius:var(--r-pill);
|
||||
background:linear-gradient(to right, ${punktFarbe("bad")}, ${punktFarbe("warn")}, ${punktFarbe("ok")})"></div>
|
||||
<div style="display:flex;justify-content:space-between;margin-top:6px">
|
||||
@@ -2109,6 +2129,9 @@ let nmOffen = false;
|
||||
let einrichtenOffen = false;
|
||||
let bildMenuOffen = null;
|
||||
let smartdealPopupOffen = false;
|
||||
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
|
||||
// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist.
|
||||
let kmOffen = null;
|
||||
|
||||
/* --------------------------------------------------------- Setup-Popup
|
||||
Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten ->
|
||||
@@ -2335,12 +2358,27 @@ function vReifen() {
|
||||
<div class="radkopf-row">
|
||||
<div class="radkopf">
|
||||
<span class="tile-headline">${satzLabel}</span>
|
||||
<div class="fig" style="font-size:32px;margin-top:14px">${t.km != null ? de(t.km) : "–"}<span class="unit">km</span></div>
|
||||
<div class="fig" style="font-size:32px;margin-top:14px;display:flex;align-items:center;gap:8px">
|
||||
<button type="button" class="km-edit" data-kmauf="${k}" aria-label="${satzLabel} Kilometerstand ändern">${ciSVG(CI.editS, 16)}</button>
|
||||
${t.km != null ? de(t.km) : "–"}<span class="unit">km</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bildklick="${radDatei}">
|
||||
${bildMitPlatzhalter(`/local/bilder/${radDatei}?v=${bildVersion}`, radDatei, radLabel, "radbild")}
|
||||
</div>
|
||||
</div>
|
||||
${kmOffen === k ? `
|
||||
<div style="margin-top:14px;border-top:1px solid var(--line);padding-top:16px">
|
||||
<div class="feld" style="border-bottom:0"><label for="km-${k}">Neuer Wert</label>
|
||||
<span class="mitEinheit"><input id="km-${k}" type="number" step="1" min="0"
|
||||
value="${t.km ?? ""}"><span>km</span></span></div>
|
||||
<span class="label" style="margin-top:6px;color:var(--fg2);letter-spacing:0;
|
||||
text-transform:none;font-size:12.5px;line-height:1.6">
|
||||
Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem
|
||||
Satz zählen ab hier weiter dazu.</span>
|
||||
<button class="aktion" data-kmspeichern="${k}">Speichern</button>
|
||||
<button class="aktion" data-kmauf="${k}">Abbrechen</button>
|
||||
</div>` : ""}
|
||||
<input type="file" accept="image/*" hidden data-bildupload="${radDatei}">
|
||||
${bildMenuOffen === radDatei ? `
|
||||
<div class="bildmenu-catcher" data-bildmenu-schliessen></div>
|
||||
@@ -3583,7 +3621,7 @@ function ereignisseVerdrahten() {
|
||||
smartdealPopupOffen = false; profilSpeichern(); render();
|
||||
return;
|
||||
}
|
||||
if (e.target.id === "carimg") { bildWeiter(); return; }
|
||||
if (e.target.closest("[data-bildgalerie]")) { bildWeiter(); return; }
|
||||
if (e.target.closest("[data-sbneu]")) {
|
||||
CAR.service.buch.unshift({ datum: dedat(heute()), km: CAR.odoBekannt ? CAR.odo : 0, art: SERVICE_ARTEN[0], werkstatt: CAR.service.autohaus.name, kosten: null, arbeiten: "" });
|
||||
entwurfIndex = 0; go("sbuch", "0"); return;
|
||||
@@ -3598,6 +3636,24 @@ function ereignisseVerdrahten() {
|
||||
if (!isNaN(wert) && wert > 0) { CAR.reifen.anzugsmoment = wert; profilSpeichern(); }
|
||||
nmOffen = false; render(); return;
|
||||
}
|
||||
const kmauf = e.target.closest("[data-kmauf]");
|
||||
if (kmauf) { kmOffen = kmOffen === kmauf.dataset.kmauf ? null : kmauf.dataset.kmauf; render(); return; }
|
||||
const kmspeichern = e.target.closest("[data-kmspeichern]");
|
||||
if (kmspeichern) {
|
||||
const satz = kmspeichern.dataset.kmspeichern;
|
||||
const wert = parseFloat($(`#km-${satz}`).value);
|
||||
if (!isNaN(wert) && wert >= 0) {
|
||||
const km = Math.round(wert * 10) / 10;
|
||||
CAR.reifen[satz].km = km;
|
||||
// km wird bewusst nicht ueber profilSpeichern() geschrieben (siehe
|
||||
// Kommentar in configCarZuProfil) - eigener Service, der nur den
|
||||
// Zaehler selbst setzt und referenz_odo_km unangetastet laesst, damit
|
||||
// die naechste automatische Fortschreibung ab diesem neuen Stand
|
||||
// weiterzaehlt statt ihn zu ueberschreiben.
|
||||
serviceRufen("audi_dashboard_reifen_km_setzen", { satz, km });
|
||||
}
|
||||
kmOffen = null; render(); return;
|
||||
}
|
||||
if (e.target.closest("[data-neu]")) {
|
||||
neuOffen = !neuOffen;
|
||||
if (belegEntwurf && belegEntwurf.praefix === "neuTank") belegEntwurf = null;
|
||||
|
||||
@@ -340,6 +340,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
color: var(--fg);
|
||||
display: block;
|
||||
}
|
||||
/* Icon + Text statt reinem Fließtext-Label (z.B. Batterie-Symbol vor
|
||||
"Ruhespannung") - dieselbe Zeilen-Geometrie wie .sync in der Kopfzeile. */
|
||||
.bv-skala-kopf { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
/* A4: Messwerte erben nie mehr das uppercase der Label-Klasse. */
|
||||
.wert {
|
||||
font-size: 14px;
|
||||
@@ -907,6 +911,21 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
.radkopf-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); }
|
||||
.radkopf { display: flex; flex-direction: column; }
|
||||
|
||||
/* Editier-Symbol links neben der km-Anzeige (Audi-CI edit-s) - oeffnet das
|
||||
Korrekturfeld fuer den manuell gepflegten Kilometerstand des Reifensatzes.
|
||||
44x44pt Tippflaeche per unsichtbarem ::before wie bei .montiert/.zahnrad,
|
||||
optisch bleibt nur das kleine Symbol sichtbar. */
|
||||
.km-edit {
|
||||
position: relative;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px;
|
||||
border: none; background: none; color: var(--fg3);
|
||||
padding: 0; cursor: pointer; flex: 0 0 auto;
|
||||
}
|
||||
.km-edit::before { content: ""; position: absolute; inset: -11px; }
|
||||
.km-edit:hover { color: var(--fg); }
|
||||
.km-edit:active { color: var(--fg); transform: scale(.92); }
|
||||
|
||||
/* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): normale
|
||||
Flusslage unter der Kachel-Überschrift/km, nicht mehr über dem Foto
|
||||
überlagert (das saß nur dort, solange Überschrift+km diesen Platz noch
|
||||
|
||||
@@ -105,3 +105,37 @@ def audi_dashboard_reifen_wechseln(satz=None):
|
||||
return
|
||||
_fortschreiben_und_veroeffentlichen(_aktueller_km(), neuer_aktiv=satz)
|
||||
log.info(f"audi_dashboard: aktiver Reifensatz auf '{satz}' umgeschaltet")
|
||||
|
||||
|
||||
@service
|
||||
def audi_dashboard_reifen_km_setzen(satz=None, km=None):
|
||||
"""Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes
|
||||
(Editier-Symbol neben der km-Anzeige in der App). Überschreibt nur den
|
||||
gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete
|
||||
Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste
|
||||
Fortschreibung (kilometerstand_geaendert oben) weiterhin korrekt von hier
|
||||
aus weiterzählt, statt auf den neuen Wert einen erneuten Delta seit dem
|
||||
alten Stand aufzuschlagen. configCarZuProfil() im Frontend schreibt km nie
|
||||
selbst mit (siehe Kommentar dort) - genau deshalb braucht es diesen
|
||||
eigenen Service statt eines einfachen Profil-Überschreibens."""
|
||||
if satz not in ("sommer", "winter"):
|
||||
log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'")
|
||||
return
|
||||
try:
|
||||
km = round(float(km), 1)
|
||||
except (TypeError, ValueError):
|
||||
log.warning(f"audi_dashboard: ungültiger km-Wert '{km}'")
|
||||
return
|
||||
if km < 0:
|
||||
log.warning(f"audi_dashboard: negativer km-Wert '{km}' abgelehnt")
|
||||
return
|
||||
p = profil.profil_lesen()
|
||||
if p is None:
|
||||
return
|
||||
reifen = p.setdefault("reifen", {})
|
||||
saetze = reifen.setdefault("saetze", {})
|
||||
saetze.setdefault(satz, {})["km"] = km
|
||||
profil.profil_schreiben(p)
|
||||
frontend_veroeffentlichung.profil_veroeffentlichen()
|
||||
_zustaende_veroeffentlichen(p)
|
||||
log.info(f"audi_dashboard: km-Stand '{satz}' manuell auf {km} gesetzt")
|
||||
|
||||
@@ -255,8 +255,17 @@ function carImg(modus) {
|
||||
return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein");
|
||||
}
|
||||
const info = bildInfo(bild);
|
||||
return bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg") +
|
||||
`<div class="dots" id="dots">${BILDER.map((_, i) => `<i class="${i === bild ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
/* data-bildgalerie: ein Tipp irgendwo auf die Kachel (Foto oder Platzhalter)
|
||||
schaltet zum naechsten Bild weiter - Hochladen/Ersetzen/Loeschen bleibt
|
||||
bewusst allein den Einstellungen vorbehalten (Bild der Übersicht),
|
||||
deshalb hier kein data-bildupload/data-bildklick, nur bildWeiter().
|
||||
Der Wrapper ist noetig, weil das <img> selbst bei fehlendem Foto per
|
||||
.bildbox.bild-fehlt img{display:none} unsichtbar und damit unklickbar
|
||||
ist - der bisherige Handler (id="carimg") griff dann ins Leere. */
|
||||
return `<div data-bildgalerie>
|
||||
${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg")}
|
||||
</div>
|
||||
<div class="dots" id="dots">${BILDER.map((_, i) => `<i class="${i === bild ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
}
|
||||
/* Bildmarke des Kraftstoffanbieters (Shell). Anders als bei den Fahrzeug-
|
||||
fotos ist hier kein generischer Platzhalter-Rahmen sinnvoll, weil das Logo
|
||||
@@ -947,6 +956,14 @@ const CI = {
|
||||
upload: '<path fill="currentColor" d="M13 20h-1V9.7l-3.65 3.65-.7-.7 4.85-4.86 4.85 4.86-.7.7L13 9.71zM6 5V4h13v1z"/>',
|
||||
fuelRange: '<path fill="currentColor" d="M2 3.5A2.5 2.5 0 0 1 4.5 1h6A2.5 2.5 0 0 1 13 3.5V10h.5a2.5 2.5 0 0 1 2.5 2.5v5a1.5 1.5 0 0 0 3 0V8.07a16.4 16.4 0 0 0-2.89-5.76l.78-.62a17.4 17.4 0 0 1 3.1 6.17l.01.07v9.57a2.5 2.5 0 0 1-5 0v-5c0-.83-.67-1.5-1.5-1.5H13v7h-1V3.5c0-.83-.67-1.5-1.5-1.5h-6C3.67 2 3 2.67 3 3.5V22h4v1H1v-1h1zM4 10h7v1H4zm5 14v-5h1v2h13v-2h1v5h-1v-2H10v2z"/>',
|
||||
parking: '<path fill="currentColor" d="M0 2.5A2.5 2.5 0 0 1 2.5 0H23v20.5a2.5 2.5 0 0 1-2.5 2.5H0zM2.5 1C1.67 1 1 1.67 1 2.5V22h19.5c.83 0 1.5-.67 1.5-1.5V1zm11.59 6.35C13.6 7 13.02 7 12.5 7H8v6h4.5c.52 0 1.11-.01 1.59-.35.44-.3.91-1 .91-2.65 0-1.66-.47-2.34-.91-2.65M8 14v3H7V6h5.53c.5 0 1.38 0 2.13.53C15.47 7.09 16 8.16 16 10s-.53 2.9-1.34 3.47a3.6 3.6 0 0 1-2.13.53z"/>',
|
||||
/* battery-12v-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert,
|
||||
verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */
|
||||
battery12vL: '<path fill="currentColor" d="M11 11h7v4h13v-4h7v4h6v23H5V15h6zm1 4h5v-3h-5zm-6 1v21h37V16zm31-1v-3h-5v3zm-3 11v4.5h-1V26h-4.5v-1H33v-4.5h1V25h4.5v1zm-15.5 0h-8v-1h8z"/>',
|
||||
battery12vS: '<path fill="currentColor" d="M5 5h4v2h6V5h4v2h3v12H2V7h3zm1 2h2V6H6zM3 8v10h18V8zm15-1V6h-2v1zm-1 6v2h-1v-2h-2v-1h2v-2h1v2h2v1zm-8 0H5v-1h4z"/>',
|
||||
/* edit-l/-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG geliefert,
|
||||
verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */
|
||||
editL: '<path fill="currentColor" d="M28.99 3.6c.34-.1.73-.11 1.1.04h.02l3.61 1.81c.35.18.58.5.68.84s.11.73-.04 1.1v.01l-2.01 4.06 4.02 2.02-6.72 13.44-.9-.44 6.28-12.56-3.13-1.56-13.6 27.47-6.3 4.66v-7.8L28.15 4.27c.18-.35.5-.58.84-.68Zm2.46 7.42 2-4.02a.65.65 0 0 0 0-.41c-.05-.14-.12-.22-.17-.24l-3.58-1.8a.65.65 0 0 0-.41 0c-.14.05-.22.12-.24.17L27.02 8.8zm-4.88-1.33-13.4 26.89 4.5 2.25L31.01 11.9l-4.44-2.2ZM13 37.61v4.9l3.95-2.93z"/>',
|
||||
editS: '<path fill="currentColor" d="m14.14 1.06 2.4 1.1.04.02c.23.16.42.39.52.65.1.27.13.6-.06.91l-.91 1.94 2.94 1.38-2.82 5.95-.9-.42 2.38-5.05-2.02-.95L9.5 19.8 5 23.57v-5.88l7.56-16.14.02-.03c.16-.23.39-.42.65-.52a1 1 0 0 1 .9.06Zm1.08 4.2.94-2 .02-.03v-.04a.4.4 0 0 0-.13-.16l-2.4-1.1-.02-.01h-.04a.4.4 0 0 0-.16.13L12.53 4l2.7 1.27Zm-3.12-.37L6.16 17.56l2.7 1.27L14.8 6.16 12.1 4.9ZM6 18.6v2.84l2.18-1.81L6 18.59Z"/>',
|
||||
};
|
||||
function ciSVG(pfad, px = 16) {
|
||||
return `<svg viewBox="0 0 24 24" width="${px}" height="${px}" fill="none" aria-hidden="true">${pfad}</svg>`;
|
||||
@@ -1481,7 +1498,10 @@ function vBatterieverlauf() {
|
||||
` : `
|
||||
<span class="label" style="margin-top:14px;color:var(--fg3)">${leerText}</span>
|
||||
`}
|
||||
<span class="label" style="margin-top:16px">Farbskala Ladezustand (AGM, Ruhespannung)</span>
|
||||
<div class="label bv-skala-kopf" style="margin-top:16px">
|
||||
${ciSVG(CI.battery12vS, 15)}
|
||||
<span>Ruhespannung</span>
|
||||
</div>
|
||||
<div style="margin-top:8px;height:8px;border-radius:var(--r-pill);
|
||||
background:linear-gradient(to right, ${punktFarbe("bad")}, ${punktFarbe("warn")}, ${punktFarbe("ok")})"></div>
|
||||
<div style="display:flex;justify-content:space-between;margin-top:6px">
|
||||
@@ -2109,6 +2129,9 @@ let nmOffen = false;
|
||||
let einrichtenOffen = false;
|
||||
let bildMenuOffen = null;
|
||||
let smartdealPopupOffen = false;
|
||||
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
|
||||
// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist.
|
||||
let kmOffen = null;
|
||||
|
||||
/* --------------------------------------------------------- Setup-Popup
|
||||
Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten ->
|
||||
@@ -2335,12 +2358,27 @@ function vReifen() {
|
||||
<div class="radkopf-row">
|
||||
<div class="radkopf">
|
||||
<span class="tile-headline">${satzLabel}</span>
|
||||
<div class="fig" style="font-size:32px;margin-top:14px">${t.km != null ? de(t.km) : "–"}<span class="unit">km</span></div>
|
||||
<div class="fig" style="font-size:32px;margin-top:14px;display:flex;align-items:center;gap:8px">
|
||||
<button type="button" class="km-edit" data-kmauf="${k}" aria-label="${satzLabel} Kilometerstand ändern">${ciSVG(CI.editS, 16)}</button>
|
||||
${t.km != null ? de(t.km) : "–"}<span class="unit">km</span>
|
||||
</div>
|
||||
</div>
|
||||
<div data-bildklick="${radDatei}">
|
||||
${bildMitPlatzhalter(`/local/bilder/${radDatei}?v=${bildVersion}`, radDatei, radLabel, "radbild")}
|
||||
</div>
|
||||
</div>
|
||||
${kmOffen === k ? `
|
||||
<div style="margin-top:14px;border-top:1px solid var(--line);padding-top:16px">
|
||||
<div class="feld" style="border-bottom:0"><label for="km-${k}">Neuer Wert</label>
|
||||
<span class="mitEinheit"><input id="km-${k}" type="number" step="1" min="0"
|
||||
value="${t.km ?? ""}"><span>km</span></span></div>
|
||||
<span class="label" style="margin-top:6px;color:var(--fg2);letter-spacing:0;
|
||||
text-transform:none;font-size:12.5px;line-height:1.6">
|
||||
Korrigiert nur den bisher gefahrenen Stand - weitere Fahrten mit diesem
|
||||
Satz zählen ab hier weiter dazu.</span>
|
||||
<button class="aktion" data-kmspeichern="${k}">Speichern</button>
|
||||
<button class="aktion" data-kmauf="${k}">Abbrechen</button>
|
||||
</div>` : ""}
|
||||
<input type="file" accept="image/*" hidden data-bildupload="${radDatei}">
|
||||
${bildMenuOffen === radDatei ? `
|
||||
<div class="bildmenu-catcher" data-bildmenu-schliessen></div>
|
||||
@@ -3583,7 +3621,7 @@ function ereignisseVerdrahten() {
|
||||
smartdealPopupOffen = false; profilSpeichern(); render();
|
||||
return;
|
||||
}
|
||||
if (e.target.id === "carimg") { bildWeiter(); return; }
|
||||
if (e.target.closest("[data-bildgalerie]")) { bildWeiter(); return; }
|
||||
if (e.target.closest("[data-sbneu]")) {
|
||||
CAR.service.buch.unshift({ datum: dedat(heute()), km: CAR.odoBekannt ? CAR.odo : 0, art: SERVICE_ARTEN[0], werkstatt: CAR.service.autohaus.name, kosten: null, arbeiten: "" });
|
||||
entwurfIndex = 0; go("sbuch", "0"); return;
|
||||
@@ -3598,6 +3636,24 @@ function ereignisseVerdrahten() {
|
||||
if (!isNaN(wert) && wert > 0) { CAR.reifen.anzugsmoment = wert; profilSpeichern(); }
|
||||
nmOffen = false; render(); return;
|
||||
}
|
||||
const kmauf = e.target.closest("[data-kmauf]");
|
||||
if (kmauf) { kmOffen = kmOffen === kmauf.dataset.kmauf ? null : kmauf.dataset.kmauf; render(); return; }
|
||||
const kmspeichern = e.target.closest("[data-kmspeichern]");
|
||||
if (kmspeichern) {
|
||||
const satz = kmspeichern.dataset.kmspeichern;
|
||||
const wert = parseFloat($(`#km-${satz}`).value);
|
||||
if (!isNaN(wert) && wert >= 0) {
|
||||
const km = Math.round(wert * 10) / 10;
|
||||
CAR.reifen[satz].km = km;
|
||||
// km wird bewusst nicht ueber profilSpeichern() geschrieben (siehe
|
||||
// Kommentar in configCarZuProfil) - eigener Service, der nur den
|
||||
// Zaehler selbst setzt und referenz_odo_km unangetastet laesst, damit
|
||||
// die naechste automatische Fortschreibung ab diesem neuen Stand
|
||||
// weiterzaehlt statt ihn zu ueberschreiben.
|
||||
serviceRufen("audi_dashboard_reifen_km_setzen", { satz, km });
|
||||
}
|
||||
kmOffen = null; render(); return;
|
||||
}
|
||||
if (e.target.closest("[data-neu]")) {
|
||||
neuOffen = !neuOffen;
|
||||
if (belegEntwurf && belegEntwurf.praefix === "neuTank") belegEntwurf = null;
|
||||
|
||||
@@ -340,6 +340,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
color: var(--fg);
|
||||
display: block;
|
||||
}
|
||||
/* Icon + Text statt reinem Fließtext-Label (z.B. Batterie-Symbol vor
|
||||
"Ruhespannung") - dieselbe Zeilen-Geometrie wie .sync in der Kopfzeile. */
|
||||
.bv-skala-kopf { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
/* A4: Messwerte erben nie mehr das uppercase der Label-Klasse. */
|
||||
.wert {
|
||||
font-size: 14px;
|
||||
@@ -907,6 +911,21 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
.radkopf-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); }
|
||||
.radkopf { display: flex; flex-direction: column; }
|
||||
|
||||
/* Editier-Symbol links neben der km-Anzeige (Audi-CI edit-s) - oeffnet das
|
||||
Korrekturfeld fuer den manuell gepflegten Kilometerstand des Reifensatzes.
|
||||
44x44pt Tippflaeche per unsichtbarem ::before wie bei .montiert/.zahnrad,
|
||||
optisch bleibt nur das kleine Symbol sichtbar. */
|
||||
.km-edit {
|
||||
position: relative;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 22px; height: 22px;
|
||||
border: none; background: none; color: var(--fg3);
|
||||
padding: 0; cursor: pointer; flex: 0 0 auto;
|
||||
}
|
||||
.km-edit::before { content: ""; position: absolute; inset: -11px; }
|
||||
.km-edit:hover { color: var(--fg); }
|
||||
.km-edit:active { color: var(--fg); transform: scale(.92); }
|
||||
|
||||
/* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): normale
|
||||
Flusslage unter der Kachel-Überschrift/km, nicht mehr über dem Foto
|
||||
überlagert (das saß nur dort, solange Überschrift+km diesen Platz noch
|
||||
|
||||
Reference in New Issue
Block a user