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:
2026-08-16 23:16:08 +02:00
parent 8b3e5b2b8f
commit 6bf7435434
7 changed files with 273 additions and 10 deletions
+45
View File
@@ -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
+34
View File
@@ -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")
+61 -5
View File
@@ -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;
+19
View File
@@ -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