Einzelbeleg: CI poi pin from the receipt address, plus a centre button
The station marker was a red circleMarker. It is now the CI poi pin (poi-l/poi-s via tankstellenMarkerSVG) - the plain pin rather than poi-car, so the vehicle and the station stay distinguishable on a map. While testing it turned out nothing in the backend ever writes station_lat / station_lon, so that marker had never been reachable in practice. The receipt address is now geocoded in the frontend via Nominatim /search (adresseAufloesen, the forward counterpart to the existing reverse lookup), with results cached in localStorage - negative ones too, since a station address does not move and Nominatim's terms ask for caching. Verified live: "Shell, SÖLDEN" resolves to 46.9756 / 11.0111. The map card gained a centre button (CI.gps, which is byte-identical to the delivered gps-s.svg), disabled until the coordinates arrive. .mapbox needed position:relative so the control anchors to the card instead of an ancestor. Only in the Einzelbeleg, as requested - the Standort fullscreen already has its own centre controls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<div class="mapbox"><div id="map"></div></div>`; }
|
||||
|
||||
/* 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 `<div class="mapbox">
|
||||
<div id="map"></div>
|
||||
<div class="kartensteuerung kartensteuerung-klein">
|
||||
<button type="button" id="belegZentrieren" aria-label="Auf die Tankstelle zentrieren" disabled>${ciSVG(CI.gps, 20)}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* 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 || "") + "<br>" + (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 `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiCarL : CI.poiCar}</svg>`;
|
||||
}
|
||||
|
||||
/* 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 `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiL : CI.poiS}</svg>`;
|
||||
}
|
||||
|
||||
/* 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: `<div class="fahrzeug-pin">${tankstellenMarkerSVG(32)}</div>`, iconSize: [32, 32], iconAnchor: [15, 31] });
|
||||
window.L.marker([pos.lat, pos.lon], { icon }).addTo(karte)
|
||||
.bindPopup((f.station_name || "") + "<br>" + (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: '<path fill="currentColor" d="M3.8 8.6C3.8 4.33 7.32.8 11.5.8s7.7 3.53 7.7 7.8c0 1.11-.5 2.58-1.17 4.1a48 48 0 0 1-2.5 4.7 90 90 0 0 1-3.54 5.47l-.06.09-.02.02V23l-.41-.29-.4.29h-.01l-.02-.03-.06-.09a70 70 0 0 1-1.05-1.53 90 90 0 0 1-2.49-3.93 48 48 0 0 1-2.5-4.72C4.29 11.18 3.8 9.71 3.8 8.6m7.7 14.1-.4.29.4.57.4-.57zm0-.87a88 88 0 0 0 3.17-4.94 47 47 0 0 0 2.45-4.6A10.3 10.3 0 0 0 18.2 8.6c0-3.73-3.08-6.8-6.7-6.8a6.82 6.82 0 0 0-6.7 6.8c0 .89.4 2.18 1.08 3.69.67 1.48 1.55 3.1 2.45 4.6a89 89 0 0 0 3.17 4.94M8.77 4h5.47L17 7.35V11a1 1 0 0 1-1 1h-1.5a1 1 0 0 1-1-1h-4a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7.32zm.46 1L7 7.68V8h2v1H7v2h1.5v-1h6v1H16V9h-2V8h2v-.29L13.76 5z"/>',
|
||||
/* 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: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M16 17.5a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0m7.5-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13"/>',
|
||||
poiS: '<path fill="currentColor" d="M4 8.5C4 4.32 7.32 1 11.5 1S19 4.32 19 8.5c0 1.09-.48 2.53-1.14 4.03a49 49 0 0 1-2.43 4.68A92 92 0 0 1 12 22.67l-.06.08-.01.03-.42-.28-.41.28-.02-.03-.06-.08A66 66 0 0 1 10 21.13a92 92 0 0 1-2.42-3.92 49 49 0 0 1-2.43-4.68C4.48 11.03 4 9.6 4 8.5m7.5 14-.41.28.41.6.41-.6zm0-.89a92 92 0 0 0 3.07-4.9c.87-1.5 1.73-3.1 2.38-4.58A10.4 10.4 0 0 0 18 8.5C18 4.88 15.12 2 11.5 2A6.46 6.46 0 0 0 5 8.5c0 .86.4 2.14 1.05 3.63a48 48 0 0 0 2.38 4.58 91 91 0 0 0 3.07 4.9M8 8.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0M11.5 6a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5"/>',
|
||||
poiCarL: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M17.96 7.98A63.3 63.3 0 0 1 29.05 8l.2.02.13.16c.6.7 1.1 1.36 1.46 1.94l1.31 2.08L34 14.31v7.19c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5V21H18v.5c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5v-7.19l1.85-2.11 1.3-2.08c.37-.58.86-1.24 1.47-1.96l.13-.16.2-.02ZM15.48 13 14 14.69V15h3v1h-3v5.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V20h13v1.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V16h-3v-1h3v-.31L31.52 13zm15.36-1L30 10.65a16 16 0 0 0-1.25-1.67 62.4 62.4 0 0 0-10.5-.02 17 17 0 0 0-1.25 1.7L16.16 12zM19.3 15h8.42l1.4 1.4-.16.31c-.34.74-.65 1.32-.92 1.72-.14.2-.28.38-.42.5a.92.92 0 0 1-.61.27h-7a.92.92 0 0 1-.6-.27 2.4 2.4 0 0 1-.43-.5c-.27-.4-.58-.98-.92-1.72l-.15-.32zm.42 1-.6.6c.26.57.5.99.69 1.27.11.17.2.27.26.32l.01.01h6.86v-.01c.07-.05.16-.15.27-.32.2-.28.43-.7.7-1.27l-.6-.6h-7.6Z"/>',
|
||||
selectAll: '<path fill="currentColor" d="M2.8 2H5v1H3v2H2V2.8a.76.76 0 0 1 .43-.71.9.9 0 0 1 .35-.09zv.5zM9 3H7V2h2zm4 0h-2V2h2zm4 0h-2V2h2zm4 0h-2V2h2.2v.5V2h.02a1 1 0 0 1 .1.01c.07.01.16.03.25.08a.76.76 0 0 1 .43.71V5h-1zM6 6h12v12H6zm1 1v10h10V7zM3 7v2H2V7zm19 0v2h-1V7zM3 11v2H2v-2zm19 0v2h-1v-2zM3 15v2H2v-2zm19 0v2h-1v-2zM3 21v-2H2v2.2a.76.76 0 0 0 .43.71.9.9 0 0 0 .35.09h.01l.01-.5v.5H5v-1zm19-2v2.2a.76.76 0 0 1-.43.71.9.9 0 0 1-.35.09h-.01l-.01-.49V22H19v-1h2v-2zM9 22H7v-1h2zm4 0h-2v-1h2zm4 0h-2v-1h2z"/>',
|
||||
planRoute: '<path fill="currentColor" d="m18.3 7-3.65-3.65.7-.7 4.86 4.85-4.86 4.85-.7-.7L18.29 8H9.5C8.67 8 8 8.67 8 9.5V21H7V9.5A2.5 2.5 0 0 1 9.5 7z"/>',
|
||||
@@ -2735,7 +2848,7 @@ function vFill(id) {
|
||||
}
|
||||
|
||||
return `
|
||||
${mapBox()}
|
||||
${mapBoxMitZentrieren()}
|
||||
<div class="tile flat" style="margin:18px 0 14px">
|
||||
<span class="label">Getankt</span>
|
||||
<div class="fig" style="font-size:52px;margin-top:10px">${f.liters != null ? de(f.liters, 2) : "–"}<span class="unit">l</span></div>
|
||||
@@ -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); }
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1786766000}
|
||||
{"version": 1786769000}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 `<div class="mapbox"><div id="map"></div></div>`; }
|
||||
|
||||
/* 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 `<div class="mapbox">
|
||||
<div id="map"></div>
|
||||
<div class="kartensteuerung kartensteuerung-klein">
|
||||
<button type="button" id="belegZentrieren" aria-label="Auf die Tankstelle zentrieren" disabled>${ciSVG(CI.gps, 20)}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* 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 || "") + "<br>" + (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 `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiCarL : CI.poiCar}</svg>`;
|
||||
}
|
||||
|
||||
/* 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 `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiL : CI.poiS}</svg>`;
|
||||
}
|
||||
|
||||
/* 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: `<div class="fahrzeug-pin">${tankstellenMarkerSVG(32)}</div>`, iconSize: [32, 32], iconAnchor: [15, 31] });
|
||||
window.L.marker([pos.lat, pos.lon], { icon }).addTo(karte)
|
||||
.bindPopup((f.station_name || "") + "<br>" + (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: '<path fill="currentColor" d="M3.8 8.6C3.8 4.33 7.32.8 11.5.8s7.7 3.53 7.7 7.8c0 1.11-.5 2.58-1.17 4.1a48 48 0 0 1-2.5 4.7 90 90 0 0 1-3.54 5.47l-.06.09-.02.02V23l-.41-.29-.4.29h-.01l-.02-.03-.06-.09a70 70 0 0 1-1.05-1.53 90 90 0 0 1-2.49-3.93 48 48 0 0 1-2.5-4.72C4.29 11.18 3.8 9.71 3.8 8.6m7.7 14.1-.4.29.4.57.4-.57zm0-.87a88 88 0 0 0 3.17-4.94 47 47 0 0 0 2.45-4.6A10.3 10.3 0 0 0 18.2 8.6c0-3.73-3.08-6.8-6.7-6.8a6.82 6.82 0 0 0-6.7 6.8c0 .89.4 2.18 1.08 3.69.67 1.48 1.55 3.1 2.45 4.6a89 89 0 0 0 3.17 4.94M8.77 4h5.47L17 7.35V11a1 1 0 0 1-1 1h-1.5a1 1 0 0 1-1-1h-4a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7.32zm.46 1L7 7.68V8h2v1H7v2h1.5v-1h6v1H16V9h-2V8h2v-.29L13.76 5z"/>',
|
||||
/* 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: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M16 17.5a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0m7.5-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13"/>',
|
||||
poiS: '<path fill="currentColor" d="M4 8.5C4 4.32 7.32 1 11.5 1S19 4.32 19 8.5c0 1.09-.48 2.53-1.14 4.03a49 49 0 0 1-2.43 4.68A92 92 0 0 1 12 22.67l-.06.08-.01.03-.42-.28-.41.28-.02-.03-.06-.08A66 66 0 0 1 10 21.13a92 92 0 0 1-2.42-3.92 49 49 0 0 1-2.43-4.68C4.48 11.03 4 9.6 4 8.5m7.5 14-.41.28.41.6.41-.6zm0-.89a92 92 0 0 0 3.07-4.9c.87-1.5 1.73-3.1 2.38-4.58A10.4 10.4 0 0 0 18 8.5C18 4.88 15.12 2 11.5 2A6.46 6.46 0 0 0 5 8.5c0 .86.4 2.14 1.05 3.63a48 48 0 0 0 2.38 4.58 91 91 0 0 0 3.07 4.9M8 8.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0M11.5 6a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5"/>',
|
||||
poiCarL: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M17.96 7.98A63.3 63.3 0 0 1 29.05 8l.2.02.13.16c.6.7 1.1 1.36 1.46 1.94l1.31 2.08L34 14.31v7.19c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5V21H18v.5c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5v-7.19l1.85-2.11 1.3-2.08c.37-.58.86-1.24 1.47-1.96l.13-.16.2-.02ZM15.48 13 14 14.69V15h3v1h-3v5.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V20h13v1.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V16h-3v-1h3v-.31L31.52 13zm15.36-1L30 10.65a16 16 0 0 0-1.25-1.67 62.4 62.4 0 0 0-10.5-.02 17 17 0 0 0-1.25 1.7L16.16 12zM19.3 15h8.42l1.4 1.4-.16.31c-.34.74-.65 1.32-.92 1.72-.14.2-.28.38-.42.5a.92.92 0 0 1-.61.27h-7a.92.92 0 0 1-.6-.27 2.4 2.4 0 0 1-.43-.5c-.27-.4-.58-.98-.92-1.72l-.15-.32zm.42 1-.6.6c.26.57.5.99.69 1.27.11.17.2.27.26.32l.01.01h6.86v-.01c.07-.05.16-.15.27-.32.2-.28.43-.7.7-1.27l-.6-.6h-7.6Z"/>',
|
||||
selectAll: '<path fill="currentColor" d="M2.8 2H5v1H3v2H2V2.8a.76.76 0 0 1 .43-.71.9.9 0 0 1 .35-.09zv.5zM9 3H7V2h2zm4 0h-2V2h2zm4 0h-2V2h2zm4 0h-2V2h2.2v.5V2h.02a1 1 0 0 1 .1.01c.07.01.16.03.25.08a.76.76 0 0 1 .43.71V5h-1zM6 6h12v12H6zm1 1v10h10V7zM3 7v2H2V7zm19 0v2h-1V7zM3 11v2H2v-2zm19 0v2h-1v-2zM3 15v2H2v-2zm19 0v2h-1v-2zM3 21v-2H2v2.2a.76.76 0 0 0 .43.71.9.9 0 0 0 .35.09h.01l.01-.5v.5H5v-1zm19-2v2.2a.76.76 0 0 1-.43.71.9.9 0 0 1-.35.09h-.01l-.01-.49V22H19v-1h2v-2zM9 22H7v-1h2zm4 0h-2v-1h2zm4 0h-2v-1h2z"/>',
|
||||
planRoute: '<path fill="currentColor" d="m18.3 7-3.65-3.65.7-.7 4.86 4.85-4.86 4.85-.7-.7L18.29 8H9.5C8.67 8 8 8.67 8 9.5V21H7V9.5A2.5 2.5 0 0 1 9.5 7z"/>',
|
||||
@@ -2735,7 +2848,7 @@ function vFill(id) {
|
||||
}
|
||||
|
||||
return `
|
||||
${mapBox()}
|
||||
${mapBoxMitZentrieren()}
|
||||
<div class="tile flat" style="margin:18px 0 14px">
|
||||
<span class="label">Getankt</span>
|
||||
<div class="fig" style="font-size:52px;margin-top:10px">${f.liters != null ? de(f.liters, 2) : "–"}<span class="unit">l</span></div>
|
||||
@@ -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); }
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1786766000}
|
||||
{"version": 1786769000}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user