diff --git a/AGENTS.md b/AGENTS.md
index 4ac58c4..5a53c0f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -804,6 +804,38 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
un-ignoring. **Keep it in sync whenever `pyscript/` or `www/` changes** — that was already the
working rule, but it is now visible in the repo when it is forgotten.
Deployed to `audi_ha_test` (version `1786766000`).
+- [x] Fifth round the same day (2026-08-16) — one-click installer and the Einzelbeleg map:
+ (1) **`installationspaket/Installieren.cmd` + `install.ps1`** automate steps 1–3 of
+ `ANLEITUNG.md`. The `.cmd` wrapper exists because double-clicking a `.ps1` opens it in an
+ editor and the execution policy blocks it; the bypass is per-call, the machine policy is not
+ touched. Safe to re-run: never overwrites `fahrzeugprofil.json`, `fahrten.jsonl`,
+ `tankvorgaenge.jsonl`, `entitaeten.json`, `ha_token.txt`; backs `configuration.yaml` up to
+ `.bak` and replaces its own marker-delimited block instead of appending twice; refuses to
+ touch the file at all when `pyscript:`/`panel_custom:` are already there from another source
+ (duplicate top-level keys are invalid YAML — HA would not start); aborts before writing if
+ the target has no `configuration.yaml`. `-Pruefen` is a dry run. **`install.ps1` is stored
+ UTF-8 *with* BOM on purpose** — PowerShell 5.1 reads scripts as ANSI otherwise and mangles
+ every umlaut; the files it *writes* stay BOM-less (YAML). Verified against a fake config
+ tree: fresh install, re-run idempotency, data preservation, foreign-key refusal,
+ wrong-directory abort, and the merged YAML parses.
+ (2) **Tankstellenposition im Einzelbeleg.** The red `circleMarker` is now the CI `poi` pin
+ (`CI.poiL`/`CI.poiS` from `poi-l.svg`/`poi-s.svg`, via `tankstellenMarkerSVG()`) — deliberately
+ the plain pin, not `poi-car`, so vehicle and station stay distinguishable.
+ **Found while testing: nothing in the backend ever writes `station_lat`/`station_lon`**, so
+ this marker had never actually been reachable. Fixed by geocoding the receipt address in the
+ frontend (`adresseAufloesen()`, Nominatim `/search` — the forward counterpart to the existing
+ `standortAdresseAufloesen()`), with results cached in `localStorage` (`audi_dashboard_geocache`)
+ including negative results, since a station address does not move and Nominatim's terms ask
+ for caching. Confirmed live: `Shell, SÖLDEN` → 46.9756/11.0111.
+ (3) **Zentrieren-Knopf** in the Einzelbeleg map card (`mapBoxMitZentrieren()`, `CI.gps` —
+ byte-identical to the delivered `gps-s.svg`), disabled until the coordinates arrive. `.mapbox`
+ gained `position:relative` so the control anchors to the card. Only there, per explicit
+ request — the Standort fullscreen already has its own centre controls.
+ Deployed to `audi_ha_test` (version `1786769000`).
+ Note for testing map behaviour in this app: `initMap()` runs on **every** `render()`, and the
+ app re-renders on each backend push (~30 s), rebuilding the map and re-centring it. Panning
+ the map from the console and checking later therefore proves nothing — spy on
+ `L.Map.prototype.flyTo` instead.
- [ ] 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`
diff --git a/homeassistant/installationspaket/www/audi-dashboard-app.js b/homeassistant/installationspaket/www/audi-dashboard-app.js
index da54ea4..42ef544 100644
--- a/homeassistant/installationspaket/www/audi-dashboard-app.js
+++ b/homeassistant/installationspaket/www/audi-dashboard-app.js
@@ -386,6 +386,8 @@ function ring(pct) {
/* ------------------------------------------------------------------ Karte
Leaflet weiterhin per CDN geladen (siehe Kopfkommentar). */
let MAP = null, TILE = null, LEAFLET_LADEND = null;
+// Zielpunkt des Zentrieren-Knopfs im Einzelbeleg (siehe mapBoxMitZentrieren).
+let BELEG_STATION_POS = null;
/* Bewusst themeunabhaengig hell: Kartendienste (Google Maps & Co.) zeigen die
Standard-Strassenkarte immer im hellen Stil, weil Strassen, Beschriftungen
und Wegfarben darauf lesbar bleiben. Die frueher bei Nacht eingeblendeten
@@ -441,6 +443,21 @@ function leafletLaden() {
function mapBox() { return `
`; }
+/* Wie mapBox(), aber mit Zentrieren-Knopf - nur im Einzelbeleg. Der Knopf
+ holt die Tankstelle zurück in die Mitte, nachdem man auf der Karte
+ herumgezogen hat. Er sitzt im .mapbox (position:relative), damit er über
+ der Karte schwebt, und ist bis zum Eintreffen der Koordinaten deaktiviert -
+ die kommen bei Belegen ohne station_lat erst aus dem Nominatim-Abruf
+ (siehe adresseAufloesen). */
+function mapBoxMitZentrieren() {
+ return `
+
+
+
+
+
`;
+}
+
/* Kartenmittelpunkt ohne eigene Positionsdaten (z. B. Fahrt ohne GPS-Punkte):
die Werkstatt-Koordinaten aus dem Profil statt einer im Code fest
hinterlegten Region - die liegen ohnehin meist in der Nähe und kommen
@@ -487,10 +504,21 @@ async function initMap() {
MAP.setView(kartenMittelpunkt(), 11); // kein Track ohne GPS-Daten (§7a-Backend-Lücke)
}
} else if (route.name === "fill") {
+ BELEG_STATION_POS = null; // gehört zum vorherigen Beleg, sonst zentriert der Knopf falsch
const f = FILLS.find((x) => x.tank_id === route.id);
if (f && f.station_lat != null) {
- MAP.setView([f.station_lat, f.station_lon], 14);
- window.L.circleMarker([f.station_lat, f.station_lon], { radius: 7, color: rot, weight: 2, fillColor: rot, fillOpacity: 0.85 }).addTo(MAP).bindPopup((f.station_name || "") + " " + (f.station_address || ""));
+ tankstelleAufKarteZeigen(MAP, { lat: f.station_lat, lon: f.station_lon }, f);
+ } else if (f && (f.station_address || f.station_name)) {
+ /* Ohne Koordinaten die Anschrift vom Beleg nachschlagen. Bis die Antwort
+ da ist, bleibt der neutrale Ausschnitt stehen - und die Karte könnte
+ inzwischen zu einer anderen Ansicht gehören, deshalb vor dem Zeichnen
+ prüfen, ob wir noch auf demselben Beleg sind. */
+ MAP.setView(kartenMittelpunkt(), 11);
+ const karte = MAP, belegId = route.id;
+ adresseAufloesen(f.station_address || f.station_name).then((pos) => {
+ if (!pos || MAP !== karte || route.name !== "fill" || route.id !== belegId) return;
+ tankstelleAufKarteZeigen(karte, pos, f);
+ });
} else {
MAP.setView(kartenMittelpunkt(), 11);
}
@@ -535,6 +563,15 @@ function fahrzeugMarkerSVG(px = 34) {
return ``;
}
+/* Tankstellenposition im Einzelbeleg - dieselbe Nadelform wie beim Fahrzeug,
+ nur ohne Auto-Glyph (CI.poiL/poiS), damit beide Marker sofort auseinander-
+ zuhalten sind. Spitze unten wie bei poi-car, gleiche Ankerrechnung. */
+function tankstellenMarkerSVG(px = 32) {
+ const gross = px >= 34;
+ const box = gross ? 48 : 24;
+ return ``;
+}
+
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
@@ -641,6 +678,76 @@ function standortAdresseLaden() {
});
}
+/* Gegenrichtung zu standortAdresseAufloesen: Adresse -> Koordinaten, für die
+ Tankstellennadel im Einzelbeleg. Nötig, weil der Beleg nur eine Anschrift
+ trägt und nichts im Backend station_lat/station_lon je befüllt - ohne diesen
+ Schritt bliebe die Karte dort dauerhaft ohne Nadel.
+
+ Ergebnisse landen in localStorage statt nur im RAM: eine Tankstellenadresse
+ ändert ihre Lage nicht, und der Einzelbeleg wird oft genug erneut geöffnet,
+ dass ein Cache nur für die laufende Sitzung Nominatim unnötig oft anfassen
+ würde (deren Nutzungsbedingungen bitten ausdrücklich darum, Ergebnisse zu
+ cachen). Auch ein Fehlschlag wird vermerkt - als null -, damit eine Adresse,
+ die Nominatim nicht kennt, nicht bei jedem Öffnen erneut abgefragt wird. */
+const GEO_CACHE_KEY = "audi_dashboard_geocache";
+let GEO_CACHE = null;
+function geoCacheLesen() {
+ if (GEO_CACHE) return GEO_CACHE;
+ try { GEO_CACHE = JSON.parse(localStorage.getItem(GEO_CACHE_KEY)) || {}; }
+ catch (e) { GEO_CACHE = {}; }
+ return GEO_CACHE;
+}
+function geoCacheSchreiben(schluessel, wert) {
+ const cache = geoCacheLesen();
+ cache[schluessel] = wert;
+ try { localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cache)); }
+ catch (e) { /* voller/gesperrter Speicher: Cache bleibt dann RAM-only */ }
+}
+async function adresseAufloesen(adresse) {
+ const suche = (adresse || "").trim();
+ if (!suche) return null;
+ const cache = geoCacheLesen();
+ if (Object.prototype.hasOwnProperty.call(cache, suche)) return cache[suche];
+ let treffer = null;
+ try {
+ const res = await fetch(
+ `https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=${encodeURIComponent(suche)}`,
+ { headers: { "Accept-Language": "de" } }
+ );
+ const daten = await res.json();
+ if (Array.isArray(daten) && daten.length) {
+ treffer = { lat: parseFloat(daten[0].lat), lon: parseFloat(daten[0].lon) };
+ }
+ } catch (e) {
+ // Netzfehler ist kein "Adresse gibt es nicht" - nichts merken, damit der
+ // nächste Aufruf es erneut versucht (vgl. standortAdresseAufloesen).
+ return null;
+ }
+ geoCacheSchreiben(suche, treffer);
+ return treffer;
+}
+
+/* Setzt Nadel und Ausschnitt, sobald die Koordinaten da sind. Getrennt von
+ initMap(), weil der Abruf asynchron ist und die Karte bis dahin schon
+ steht. */
+function tankstelleAufKarteZeigen(karte, pos, f) {
+ if (!karte || !window.L || !pos) return;
+ karte.setView([pos.lat, pos.lon], 14);
+ const icon = window.L.divIcon({ className: "fahrzeug-pin-wrap", html: `
${tankstellenMarkerSVG(32)}
`, iconSize: [32, 32], iconAnchor: [15, 31] });
+ window.L.marker([pos.lat, pos.lon], { icon }).addTo(karte)
+ .bindPopup((f.station_name || "") + " " + (f.station_address || ""));
+ // Position merken und den Knopf scharf schalten - er kann erst jetzt etwas
+ // Sinnvolles tun (bei Adressauflösung kommt das hier verzögert, siehe
+ // Aufrufstelle in initMap).
+ BELEG_STATION_POS = pos;
+ const knopf = ROOT && ROOT.getElementById("belegZentrieren");
+ if (knopf) knopf.disabled = false;
+}
+function belegAufTankstelleZentrieren() {
+ if (!MAP || !BELEG_STATION_POS) return;
+ MAP.flyTo([BELEG_STATION_POS.lat, BELEG_STATION_POS.lon], Math.max(MAP.getZoom(), 14));
+}
+
function standortKartenlink() {
return standortBekannt() ? `https://www.google.com/maps/dir/?api=1&destination=${CAR.standortLat},${CAR.standortLon}` : null;
}
@@ -814,6 +921,12 @@ const CI = {
poiCar: '',
/* poi-car-l - grosse Variante desselben CI-Symbols (48er-Raster), fuer die
Fahrzeugnadel auf der Vollbildkarte. Siehe fahrzeugMarkerSVG(). */
+ /* poi-l/poi-s aus dem CI-Satz: neutrale Kartennadel mit Kreis, fuer die
+ Tankstellenposition im Einzelbeleg (poi-car bleibt dem Fahrzeug
+ vorbehalten, sonst waeren beide Nadeln auf einer Karte nicht zu
+ unterscheiden). Siehe tankstellenMarkerSVG(). */
+ poiL: '',
+ poiS: '',
poiCarL: '',
selectAll: '',
planRoute: '',
@@ -2735,7 +2848,7 @@ function vFill(id) {
}
return `
- ${mapBox()}
+ ${mapBoxMitZentrieren()}
Getankt
${f.liters != null ? de(f.liters, 2) : "–"}l
@@ -3669,6 +3782,9 @@ function ereignisseVerdrahten() {
if (e.target.closest("[data-standort-beide]")) { standortBeideZeigen(); return; }
if (e.target.closest("[data-standort-schliessen]")) { standortMenuSchliessen(); return; }
if (e.target.closest("[data-standort-teilen]")) { standortTeilen(); return; }
+ // Einzelbeleg: zurück auf die Tankstelle (manipuliert MAP direkt, kein
+ // render() - das würde die Karte neu aufbauen und den Ausschnitt verlieren).
+ if (e.target.closest("#belegZentrieren")) { belegAufTankstelleZentrieren(); return; }
const g = e.target.closest("[data-go]");
if (g) { const [n, i] = g.dataset.go.split(":"); go(n, i); }
});
diff --git a/homeassistant/installationspaket/www/audi-dashboard-version.json b/homeassistant/installationspaket/www/audi-dashboard-version.json
index b271edc..3adbe44 100644
--- a/homeassistant/installationspaket/www/audi-dashboard-version.json
+++ b/homeassistant/installationspaket/www/audi-dashboard-version.json
@@ -1 +1 @@
-{"version": 1786766000}
+{"version": 1786769000}
diff --git a/homeassistant/installationspaket/www/audi-dashboard.css b/homeassistant/installationspaket/www/audi-dashboard.css
index 7894b76..ad1ab74 100644
--- a/homeassistant/installationspaket/www/audi-dashboard.css
+++ b/homeassistant/installationspaket/www/audi-dashboard.css
@@ -688,7 +688,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.swipe-wrap.swiped .swipe-content { transform: translateX(-84px); }
/* -------------------------------------------------------------- Karte */
-.mapbox { border-radius: var(--r-tile); overflow: hidden; height: 210px; background: var(--tile); }
+/* position:relative, damit der Zentrieren-Knopf im Einzelbeleg (absolut
+ positioniert, siehe .kartensteuerung-klein) sich an dieser Kachel ausrichtet
+ und nicht am naechsten Vorfahren weiter oben. */
+.mapbox { position: relative; border-radius: var(--r-tile); overflow: hidden; height: 210px; background: var(--tile); }
.mapbox #map { width: 100%; height: 100%; }
/* --------------------------------------------------------- Standort
@@ -757,6 +760,12 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
}
.kartensteuerung button:active { transform: scale(.94); }
:host([data-theme="tag"]) .kartensteuerung button:active { background: #fff; }
+/* Variante in der Belegkarte: die Karte ist dort nur 210px hoch, ein 48er-
+ Knopf mit 16px Abstand wuerde ein Viertel davon verdecken. */
+.kartensteuerung-klein { top: 10px; right: 10px; }
+.kartensteuerung-klein button { width: 36px; height: 36px; }
+.kartensteuerung-klein button:disabled { opacity: .45; cursor: default; }
+.kartensteuerung-klein button:disabled:active { transform: none; }
/* Sichtbarer Anteil im geschlossenen Zustand (STANDORT_PEEK_PX im Frontend) -
beide Werte müssen zusammenpassen, siehe Kommentar dort. Der Rest des
diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js
index da54ea4..42ef544 100644
--- a/homeassistant/www/audi-dashboard-app.js
+++ b/homeassistant/www/audi-dashboard-app.js
@@ -386,6 +386,8 @@ function ring(pct) {
/* ------------------------------------------------------------------ Karte
Leaflet weiterhin per CDN geladen (siehe Kopfkommentar). */
let MAP = null, TILE = null, LEAFLET_LADEND = null;
+// Zielpunkt des Zentrieren-Knopfs im Einzelbeleg (siehe mapBoxMitZentrieren).
+let BELEG_STATION_POS = null;
/* Bewusst themeunabhaengig hell: Kartendienste (Google Maps & Co.) zeigen die
Standard-Strassenkarte immer im hellen Stil, weil Strassen, Beschriftungen
und Wegfarben darauf lesbar bleiben. Die frueher bei Nacht eingeblendeten
@@ -441,6 +443,21 @@ function leafletLaden() {
function mapBox() { return `
`; }
+/* Wie mapBox(), aber mit Zentrieren-Knopf - nur im Einzelbeleg. Der Knopf
+ holt die Tankstelle zurück in die Mitte, nachdem man auf der Karte
+ herumgezogen hat. Er sitzt im .mapbox (position:relative), damit er über
+ der Karte schwebt, und ist bis zum Eintreffen der Koordinaten deaktiviert -
+ die kommen bei Belegen ohne station_lat erst aus dem Nominatim-Abruf
+ (siehe adresseAufloesen). */
+function mapBoxMitZentrieren() {
+ return `
+
+
+
+
+
`;
+}
+
/* Kartenmittelpunkt ohne eigene Positionsdaten (z. B. Fahrt ohne GPS-Punkte):
die Werkstatt-Koordinaten aus dem Profil statt einer im Code fest
hinterlegten Region - die liegen ohnehin meist in der Nähe und kommen
@@ -487,10 +504,21 @@ async function initMap() {
MAP.setView(kartenMittelpunkt(), 11); // kein Track ohne GPS-Daten (§7a-Backend-Lücke)
}
} else if (route.name === "fill") {
+ BELEG_STATION_POS = null; // gehört zum vorherigen Beleg, sonst zentriert der Knopf falsch
const f = FILLS.find((x) => x.tank_id === route.id);
if (f && f.station_lat != null) {
- MAP.setView([f.station_lat, f.station_lon], 14);
- window.L.circleMarker([f.station_lat, f.station_lon], { radius: 7, color: rot, weight: 2, fillColor: rot, fillOpacity: 0.85 }).addTo(MAP).bindPopup((f.station_name || "") + " " + (f.station_address || ""));
+ tankstelleAufKarteZeigen(MAP, { lat: f.station_lat, lon: f.station_lon }, f);
+ } else if (f && (f.station_address || f.station_name)) {
+ /* Ohne Koordinaten die Anschrift vom Beleg nachschlagen. Bis die Antwort
+ da ist, bleibt der neutrale Ausschnitt stehen - und die Karte könnte
+ inzwischen zu einer anderen Ansicht gehören, deshalb vor dem Zeichnen
+ prüfen, ob wir noch auf demselben Beleg sind. */
+ MAP.setView(kartenMittelpunkt(), 11);
+ const karte = MAP, belegId = route.id;
+ adresseAufloesen(f.station_address || f.station_name).then((pos) => {
+ if (!pos || MAP !== karte || route.name !== "fill" || route.id !== belegId) return;
+ tankstelleAufKarteZeigen(karte, pos, f);
+ });
} else {
MAP.setView(kartenMittelpunkt(), 11);
}
@@ -535,6 +563,15 @@ function fahrzeugMarkerSVG(px = 34) {
return ``;
}
+/* Tankstellenposition im Einzelbeleg - dieselbe Nadelform wie beim Fahrzeug,
+ nur ohne Auto-Glyph (CI.poiL/poiS), damit beide Marker sofort auseinander-
+ zuhalten sind. Spitze unten wie bei poi-car, gleiche Ankerrechnung. */
+function tankstellenMarkerSVG(px = 32) {
+ const gross = px >= 34;
+ const box = gross ? 48 : 24;
+ return ``;
+}
+
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
@@ -641,6 +678,76 @@ function standortAdresseLaden() {
});
}
+/* Gegenrichtung zu standortAdresseAufloesen: Adresse -> Koordinaten, für die
+ Tankstellennadel im Einzelbeleg. Nötig, weil der Beleg nur eine Anschrift
+ trägt und nichts im Backend station_lat/station_lon je befüllt - ohne diesen
+ Schritt bliebe die Karte dort dauerhaft ohne Nadel.
+
+ Ergebnisse landen in localStorage statt nur im RAM: eine Tankstellenadresse
+ ändert ihre Lage nicht, und der Einzelbeleg wird oft genug erneut geöffnet,
+ dass ein Cache nur für die laufende Sitzung Nominatim unnötig oft anfassen
+ würde (deren Nutzungsbedingungen bitten ausdrücklich darum, Ergebnisse zu
+ cachen). Auch ein Fehlschlag wird vermerkt - als null -, damit eine Adresse,
+ die Nominatim nicht kennt, nicht bei jedem Öffnen erneut abgefragt wird. */
+const GEO_CACHE_KEY = "audi_dashboard_geocache";
+let GEO_CACHE = null;
+function geoCacheLesen() {
+ if (GEO_CACHE) return GEO_CACHE;
+ try { GEO_CACHE = JSON.parse(localStorage.getItem(GEO_CACHE_KEY)) || {}; }
+ catch (e) { GEO_CACHE = {}; }
+ return GEO_CACHE;
+}
+function geoCacheSchreiben(schluessel, wert) {
+ const cache = geoCacheLesen();
+ cache[schluessel] = wert;
+ try { localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cache)); }
+ catch (e) { /* voller/gesperrter Speicher: Cache bleibt dann RAM-only */ }
+}
+async function adresseAufloesen(adresse) {
+ const suche = (adresse || "").trim();
+ if (!suche) return null;
+ const cache = geoCacheLesen();
+ if (Object.prototype.hasOwnProperty.call(cache, suche)) return cache[suche];
+ let treffer = null;
+ try {
+ const res = await fetch(
+ `https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=${encodeURIComponent(suche)}`,
+ { headers: { "Accept-Language": "de" } }
+ );
+ const daten = await res.json();
+ if (Array.isArray(daten) && daten.length) {
+ treffer = { lat: parseFloat(daten[0].lat), lon: parseFloat(daten[0].lon) };
+ }
+ } catch (e) {
+ // Netzfehler ist kein "Adresse gibt es nicht" - nichts merken, damit der
+ // nächste Aufruf es erneut versucht (vgl. standortAdresseAufloesen).
+ return null;
+ }
+ geoCacheSchreiben(suche, treffer);
+ return treffer;
+}
+
+/* Setzt Nadel und Ausschnitt, sobald die Koordinaten da sind. Getrennt von
+ initMap(), weil der Abruf asynchron ist und die Karte bis dahin schon
+ steht. */
+function tankstelleAufKarteZeigen(karte, pos, f) {
+ if (!karte || !window.L || !pos) return;
+ karte.setView([pos.lat, pos.lon], 14);
+ const icon = window.L.divIcon({ className: "fahrzeug-pin-wrap", html: `
${tankstellenMarkerSVG(32)}
`, iconSize: [32, 32], iconAnchor: [15, 31] });
+ window.L.marker([pos.lat, pos.lon], { icon }).addTo(karte)
+ .bindPopup((f.station_name || "") + " " + (f.station_address || ""));
+ // Position merken und den Knopf scharf schalten - er kann erst jetzt etwas
+ // Sinnvolles tun (bei Adressauflösung kommt das hier verzögert, siehe
+ // Aufrufstelle in initMap).
+ BELEG_STATION_POS = pos;
+ const knopf = ROOT && ROOT.getElementById("belegZentrieren");
+ if (knopf) knopf.disabled = false;
+}
+function belegAufTankstelleZentrieren() {
+ if (!MAP || !BELEG_STATION_POS) return;
+ MAP.flyTo([BELEG_STATION_POS.lat, BELEG_STATION_POS.lon], Math.max(MAP.getZoom(), 14));
+}
+
function standortKartenlink() {
return standortBekannt() ? `https://www.google.com/maps/dir/?api=1&destination=${CAR.standortLat},${CAR.standortLon}` : null;
}
@@ -814,6 +921,12 @@ const CI = {
poiCar: '',
/* poi-car-l - grosse Variante desselben CI-Symbols (48er-Raster), fuer die
Fahrzeugnadel auf der Vollbildkarte. Siehe fahrzeugMarkerSVG(). */
+ /* poi-l/poi-s aus dem CI-Satz: neutrale Kartennadel mit Kreis, fuer die
+ Tankstellenposition im Einzelbeleg (poi-car bleibt dem Fahrzeug
+ vorbehalten, sonst waeren beide Nadeln auf einer Karte nicht zu
+ unterscheiden). Siehe tankstellenMarkerSVG(). */
+ poiL: '',
+ poiS: '',
poiCarL: '',
selectAll: '',
planRoute: '',
@@ -2735,7 +2848,7 @@ function vFill(id) {
}
return `
- ${mapBox()}
+ ${mapBoxMitZentrieren()}
Getankt
${f.liters != null ? de(f.liters, 2) : "–"}l
@@ -3669,6 +3782,9 @@ function ereignisseVerdrahten() {
if (e.target.closest("[data-standort-beide]")) { standortBeideZeigen(); return; }
if (e.target.closest("[data-standort-schliessen]")) { standortMenuSchliessen(); return; }
if (e.target.closest("[data-standort-teilen]")) { standortTeilen(); return; }
+ // Einzelbeleg: zurück auf die Tankstelle (manipuliert MAP direkt, kein
+ // render() - das würde die Karte neu aufbauen und den Ausschnitt verlieren).
+ if (e.target.closest("#belegZentrieren")) { belegAufTankstelleZentrieren(); return; }
const g = e.target.closest("[data-go]");
if (g) { const [n, i] = g.dataset.go.split(":"); go(n, i); }
});
diff --git a/homeassistant/www/audi-dashboard-version.json b/homeassistant/www/audi-dashboard-version.json
index b271edc..3adbe44 100644
--- a/homeassistant/www/audi-dashboard-version.json
+++ b/homeassistant/www/audi-dashboard-version.json
@@ -1 +1 @@
-{"version": 1786766000}
+{"version": 1786769000}
diff --git a/homeassistant/www/audi-dashboard.css b/homeassistant/www/audi-dashboard.css
index 7894b76..ad1ab74 100644
--- a/homeassistant/www/audi-dashboard.css
+++ b/homeassistant/www/audi-dashboard.css
@@ -688,7 +688,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.swipe-wrap.swiped .swipe-content { transform: translateX(-84px); }
/* -------------------------------------------------------------- Karte */
-.mapbox { border-radius: var(--r-tile); overflow: hidden; height: 210px; background: var(--tile); }
+/* position:relative, damit der Zentrieren-Knopf im Einzelbeleg (absolut
+ positioniert, siehe .kartensteuerung-klein) sich an dieser Kachel ausrichtet
+ und nicht am naechsten Vorfahren weiter oben. */
+.mapbox { position: relative; border-radius: var(--r-tile); overflow: hidden; height: 210px; background: var(--tile); }
.mapbox #map { width: 100%; height: 100%; }
/* --------------------------------------------------------- Standort
@@ -757,6 +760,12 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
}
.kartensteuerung button:active { transform: scale(.94); }
:host([data-theme="tag"]) .kartensteuerung button:active { background: #fff; }
+/* Variante in der Belegkarte: die Karte ist dort nur 210px hoch, ein 48er-
+ Knopf mit 16px Abstand wuerde ein Viertel davon verdecken. */
+.kartensteuerung-klein { top: 10px; right: 10px; }
+.kartensteuerung-klein button { width: 36px; height: 36px; }
+.kartensteuerung-klein button:disabled { opacity: .45; cursor: default; }
+.kartensteuerung-klein button:disabled:active { transform: none; }
/* Sichtbarer Anteil im geschlossenen Zustand (STANDORT_PEEK_PX im Frontend) -
beide Werte müssen zusammenpassen, siehe Kommentar dort. Der Rest des