Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt (2026.9.4.1)
Das Fahrtfenster stand seit dem 01.09. in der Gerätezeit, der Verlauf war nach Ankunftszeit sortiert. Zwei Uhren, und das Gerät puffert: was verspätet ankam, fiel aus einem Fenster heraus, in dem es der Sache nach lag. Am 03.09. hat das eine Fahrt gekostet - 26 von 48 GPS-Punkten und 3,0 statt 7 km. verlauf_lesen() stempelt jeden Punkt jetzt mit der Gerätezeit seines Datensatzes. Eine Stelle, an der alle Verbraucher vorbeikommen. Dazu zwei Riegel aus derselben Untersuchung: ein Rückschritt von 43 Metern ist keine Zählerrücksetzung mehr (aus 87 Metern wurden vorher 13,7 km), und das Fahrtende wird auf das letzte Lebenszeichen des Geräts geklemmt - der Nachlauf ist eine Rechnung, kein Messwert. Am echten Recorder nachgerechnet: Tacho 3,0 -> 7,0 km, GNSS 2,952 -> 6,877 km, Route 26 -> 47 Punkte, Ende 23:21:57 -> 23:17:48. Beim ersten Lauf haben zwei Fahrten eine Strecke bekommen, die vorher gar keine hatten. Nicht das Gerät war schuld: es hat lückenlos aufgezeichnet. Die zehn Minuten Funkstille waren eine offene, aber tote TCP-Sitzung (flespi-Log: 703 s, 16 Nachrichten) - kein Funkloch. Begründung und die Empfehlung fürs Gerät stehen in AGENTS.md, Abschnitt CB. Dazu die neun Paritätsbefunde, alle Richtung Panel gelöst - darunter ein unmaskierter Punkt in beiden Codebasen, der aus "vor 3 Tage" ein "vor 3 Tag" machte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -396,6 +396,66 @@ export function Einstellungen({
|
||||
</ActionButton>
|
||||
</Tile>
|
||||
|
||||
{zugaenge.length > 0 && (
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Zugänge</span>
|
||||
{/* Aufklappbar, weil die Token im Alltag nichts zu suchen haben —
|
||||
man fasst sie an, wenn einer abgelaufen ist, und sonst nie. */}
|
||||
{zugaenge.map((z) => (
|
||||
<Accordion
|
||||
key={z.dienst}
|
||||
title={z.titel}
|
||||
summary={
|
||||
<span className={z.gesetzt ? "dm-token-gesetzt" : "dm-token-offen"}>
|
||||
{z.gesetzt ? (z.rest ? `gesetzt · …${z.rest}` : "gesetzt") : "nicht gesetzt"}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<p className="dm-erklaerung">{z.zweck}</p>
|
||||
<Feld label="Neuer Token" last>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
placeholder={z.gesetzt ? "ersetzt den hinterlegten" : "noch keiner hinterlegt"}
|
||||
value={tokenEntwurf[z.dienst] ?? ""}
|
||||
onChange={(e) =>
|
||||
setzeTokenEntwurf((alt) => ({ ...alt, [z.dienst]: e.target.value }))
|
||||
}
|
||||
/>
|
||||
</Feld>
|
||||
<div className="dm-knopfreihe">
|
||||
<ActionButton
|
||||
variant="primary"
|
||||
disabled={tokenLaeuft !== null || !(tokenEntwurf[z.dienst] ?? "").trim()}
|
||||
onClick={() => void tokenSchreiben(z.dienst, tokenEntwurf[z.dienst] ?? "")}
|
||||
>
|
||||
{tokenLaeuft === z.dienst ? "Speichert …" : "Speichern"}
|
||||
</ActionButton>
|
||||
{z.gesetzt && (
|
||||
<ActionButton
|
||||
disabled={tokenLaeuft !== null}
|
||||
onClick={() => {
|
||||
if (bestaetigen("Token löschen?")) {
|
||||
void tokenSchreiben(z.dienst, "")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Löschen
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</Accordion>
|
||||
))}
|
||||
{tokenFehler && <p className="dm-fehler">{tokenFehler}</p>}
|
||||
<span className="ads-eyebrow" style={{ marginTop: "14px" }}>
|
||||
Ein gespeicherter Token wird nie wieder angezeigt — nur ob einer hinterlegt ist.
|
||||
Dieselben Felder gibt es unter Einstellungen → Geräte &
|
||||
Dienste → Audi Dashboard → Konfigurieren.
|
||||
</span>
|
||||
</Tile>
|
||||
)}
|
||||
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Fahrt beenden</span>
|
||||
<Slider
|
||||
@@ -429,11 +489,12 @@ export function Einstellungen({
|
||||
key={w.schluessel}
|
||||
label={`${w.titel} ${w.nummer}`}
|
||||
wert={`${w.geraet ?? "–"}${w.einheit ? " " + w.einheit : ""}`}
|
||||
warnung={Boolean(w.abweichung)}
|
||||
zusatz={
|
||||
w.offen != null
|
||||
? `${w.offen} ist unterwegs zum Gerät`
|
||||
? `→ ${w.offen}, unterwegs`
|
||||
: w.abweichung
|
||||
? `wir rechnen mit ${w.unser}`
|
||||
? `(wir: ${w.unser})`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
@@ -448,7 +509,7 @@ export function Einstellungen({
|
||||
{dongle.stand?.gelesen_am
|
||||
// Der Punkt der Abkürzung ist zugleich der Satzpunkt - sonst
|
||||
// stünde dort "vor 1 Std..".
|
||||
? `Gelesen ${standAlter(dongle.stand.gelesen_am).replace(/.$/, "")}. `
|
||||
? `Gelesen ${standAlter(dongle.stand.gelesen_am).replace(/\.$/, "")}. `
|
||||
: "Noch nicht gelesen. "}
|
||||
Der Regler oben schreibt den Schlaf-Timeout sofort hierher; bis das Gerät sich
|
||||
das nächste Mal meldet, steht die Änderung als „unterwegs" daneben.
|
||||
@@ -744,65 +805,6 @@ export function Einstellungen({
|
||||
</Tile>
|
||||
)}
|
||||
|
||||
{zugaenge.length > 0 && (
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Zugänge</span>
|
||||
{/* Aufklappbar, weil die Token im Alltag nichts zu suchen haben —
|
||||
man fasst sie an, wenn einer abgelaufen ist, und sonst nie. */}
|
||||
{zugaenge.map((z) => (
|
||||
<Accordion
|
||||
key={z.dienst}
|
||||
title={z.titel}
|
||||
summary={
|
||||
<span className={z.gesetzt ? "dm-token-gesetzt" : "dm-token-offen"}>
|
||||
{z.gesetzt ? (z.rest ? `gesetzt · …${z.rest}` : "gesetzt") : "nicht gesetzt"}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<p className="dm-erklaerung">{z.zweck}</p>
|
||||
<Feld label="Neuer Token" last>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
placeholder={z.gesetzt ? "ersetzt den hinterlegten" : "noch keiner hinterlegt"}
|
||||
value={tokenEntwurf[z.dienst] ?? ""}
|
||||
onChange={(e) =>
|
||||
setzeTokenEntwurf((alt) => ({ ...alt, [z.dienst]: e.target.value }))
|
||||
}
|
||||
/>
|
||||
</Feld>
|
||||
<div className="dm-knopfreihe">
|
||||
<ActionButton
|
||||
variant="primary"
|
||||
disabled={tokenLaeuft !== null || !(tokenEntwurf[z.dienst] ?? "").trim()}
|
||||
onClick={() => void tokenSchreiben(z.dienst, tokenEntwurf[z.dienst] ?? "")}
|
||||
>
|
||||
{tokenLaeuft === z.dienst ? "Speichert …" : "Speichern"}
|
||||
</ActionButton>
|
||||
{z.gesetzt && (
|
||||
<ActionButton
|
||||
disabled={tokenLaeuft !== null}
|
||||
onClick={() => {
|
||||
if (bestaetigen(`Token für ${z.titel} löschen?`)) {
|
||||
void tokenSchreiben(z.dienst, "")
|
||||
}
|
||||
}}
|
||||
>
|
||||
Löschen
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
</Accordion>
|
||||
))}
|
||||
{tokenFehler && <p className="dm-fehler">{tokenFehler}</p>}
|
||||
<span className="ads-eyebrow" style={{ marginTop: "14px" }}>
|
||||
Ein gespeicherter Token wird nie wieder angezeigt — nur ob einer hinterlegt ist.
|
||||
Dieselben Felder gibt es in Home Assistant unter Einstellungen → Geräte &
|
||||
Dienste → Audi Dashboard → Konfigurieren.
|
||||
</span>
|
||||
</Tile>
|
||||
)}
|
||||
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Version</span>
|
||||
|
||||
@@ -115,6 +115,8 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n
|
||||
? { ziel: { lat: fahrt.end_lat, lon: fahrt.end_lon } }
|
||||
: {})}
|
||||
{...(fahrt.route && fahrt.route.length >= 2 ? { route: fahrt.route } : {})}
|
||||
startText={fahrt.start_address ?? fahrt.start_stadt ?? ""}
|
||||
zielText={fahrt.end_address ?? fahrt.end_stadt ?? ""}
|
||||
/>
|
||||
) : (
|
||||
<Leerzustand
|
||||
@@ -166,14 +168,6 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n
|
||||
wert={fahrt.odo_end != null ? `${de(fahrt.odo_end)} km` : "noch offen"}
|
||||
/>
|
||||
<Wertzeile label="Dauer" wert={dauer(fahrt.duration_s)} />
|
||||
<Wertzeile
|
||||
label="Verbrauch"
|
||||
wert={
|
||||
fahrt.verbrauch_l_100km != null
|
||||
? `${de(fahrt.verbrauch_l_100km, 1)} l/100 km`
|
||||
: "liegt nicht vor"
|
||||
}
|
||||
/>
|
||||
<Wertzeile
|
||||
label="Ø Geschwindigkeit"
|
||||
wert={
|
||||
@@ -188,6 +182,14 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n
|
||||
fahrt.vmax_kmh != null ? `${de(fahrt.vmax_kmh)} km/h` : "liegt nicht vor"
|
||||
}
|
||||
/>
|
||||
<Wertzeile
|
||||
label="Verbrauch"
|
||||
wert={
|
||||
fahrt.verbrauch_l_100km != null
|
||||
? `${de(fahrt.verbrauch_l_100km, 1)} l/100 km`
|
||||
: "liegt nicht vor"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -28,6 +28,24 @@ const KACHEL_ZUSATZ = "© OpenStreetMap contributors"
|
||||
|
||||
const ROT = "#F50537"
|
||||
|
||||
/* Nadel der Tankstelle: die CI-Symbole poi-s und seine Silhouette, verbatim
|
||||
aus dem Panel (tankstellenMarkerSVG). Bewusst die schlichte Nadel und nicht
|
||||
poi-car, damit Fahrzeug und Tankstelle unterscheidbar bleiben. Die
|
||||
Silhouette liegt darunter, weil die reine Kontur auf Kartenkacheln fast
|
||||
unsichtbar ist - der Befund vom 16.08.2026, gemessen an der Deckung. */
|
||||
const CI_POI_SIL = '<path fill="#182634" 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.5"/>'
|
||||
const CI_POI = '<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"/>'
|
||||
const CI_GPS = 'M12.5 3.51V6.5h-1V3.51a8.46 8.46 0 0 0-7.99 7.99H6.5v1H3.51a8.46 8.46 0 0 0 7.99 7.99V17.5h1v2.99a8.46 8.46 0 0 0 7.99-7.99H17.5v-1h2.99a8.46 8.46 0 0 0-7.99-7.99m0-1a9.46 9.46 0 0 1 8.99 8.99h2.01v1h-2.01a9.46 9.46 0 0 1-8.99 8.99v2.01h-1v-2.01a9.46 9.46 0 0 1-8.99-8.99H.5v-1h2.01a9.46 9.46 0 0 1 8.99-8.99V.5h1z'
|
||||
|
||||
function nadelSvg(px = 32): string {
|
||||
return (
|
||||
`<svg viewBox="0 0 24 24" width="${px}" height="${px}" fill="none" aria-hidden="true">` +
|
||||
CI_POI_SIL +
|
||||
CI_POI +
|
||||
"</svg>"
|
||||
)
|
||||
}
|
||||
|
||||
/* Fahrtrichtung auf der Streckenlinie. Deckungsgleich mit spurPfeile() im
|
||||
Panel - dort steht die ausfuehrliche Begruendung.
|
||||
|
||||
@@ -105,13 +123,28 @@ export function Karte({
|
||||
start,
|
||||
ziel,
|
||||
route,
|
||||
startText,
|
||||
zielText,
|
||||
nadel,
|
||||
}: {
|
||||
start: Punkt
|
||||
ziel?: Punkt
|
||||
/** Echte Streckenlinie aus dem GPS-Verlauf (Backend: screening.py/historienimport.py). */
|
||||
route?: [number, number][]
|
||||
/** Anschrift zum Startpunkt - steht im Popup, wie im Panel (initMap). */
|
||||
startText?: string | null
|
||||
/** Anschrift zum Zielpunkt. */
|
||||
zielText?: string | null
|
||||
/**
|
||||
* Ein einzelner Ort statt Start und Ziel: die CI-Nadel mit Popup und einem
|
||||
* Knopf, der wieder darauf zentriert. So zeichnet das Panel den Einzelbeleg
|
||||
* (tankstelleAufKarteZeigen/mapBoxMitZentrieren); die App zeigte hier bis
|
||||
* zum 04.09.2026 nur den schwarzen Startpunkt ohne Namen.
|
||||
*/
|
||||
nadel?: { name?: string | null; adresse?: string | null }
|
||||
}) {
|
||||
const behaelter = useRef<HTMLDivElement | null>(null)
|
||||
const karteRef = useRef<import("leaflet").Map | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const element = behaelter.current
|
||||
@@ -127,17 +160,32 @@ export function Karte({
|
||||
karte = L.map(element, { attributionControl: true, scrollWheelZoom: false })
|
||||
L.tileLayer(KACHEL_URL, { attribution: KACHEL_ZUSATZ, maxZoom: 19 }).addTo(karte)
|
||||
|
||||
karteRef.current = karte
|
||||
const punkte: [number, number][] = [[start.lat, start.lon]]
|
||||
// Deckungsgleich mit initMap() im Panel: Start schwarz mit weißem Ring,
|
||||
// Ziel rot - erkennbar unterschiedlich, nicht beide in derselben Farbe.
|
||||
L.circleMarker([start.lat, start.lon], { radius: 6, color: "#fff", weight: 2, fillColor: "#000", fillOpacity: 1 })
|
||||
.bindTooltip("Start")
|
||||
.addTo(karte)
|
||||
if (nadel) {
|
||||
const symbol = L.divIcon({
|
||||
className: "dm-nadel",
|
||||
html: nadelSvg(32),
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [15, 31],
|
||||
})
|
||||
L.marker([start.lat, start.lon], { icon: symbol })
|
||||
.bindPopup([nadel.name, nadel.adresse].filter(Boolean).join("<br>"))
|
||||
.addTo(karte)
|
||||
} else {
|
||||
// Deckungsgleich mit initMap() im Panel: Start schwarz mit weißem Ring,
|
||||
// Ziel rot - erkennbar unterschiedlich, nicht beide in derselben Farbe.
|
||||
// Popup statt Tooltip, ebenfalls wie im Panel: auf dem Telefon ist ein
|
||||
// Tooltip praktisch unsichtbar, und die Anschrift ist die Auskunft.
|
||||
L.circleMarker([start.lat, start.lon], { radius: 6, color: "#fff", weight: 2, fillColor: "#000", fillOpacity: 1 })
|
||||
.bindPopup("Start · " + (startText ?? ""))
|
||||
.addTo(karte)
|
||||
}
|
||||
|
||||
if (ziel) {
|
||||
punkte.push([ziel.lat, ziel.lon])
|
||||
L.circleMarker([ziel.lat, ziel.lon], { radius: 6, color: ROT, weight: 2, fillColor: ROT, fillOpacity: 1 })
|
||||
.bindTooltip("Ziel")
|
||||
.bindPopup("Ziel · " + (zielText ?? ""))
|
||||
.addTo(karte)
|
||||
// Echte Route, wenn vorhanden - sonst eine Verbindungslinie NUR zwischen
|
||||
// den beiden echten Punkten, kein erfundener Verlauf dazwischen.
|
||||
@@ -162,9 +210,39 @@ export function Karte({
|
||||
|
||||
return () => {
|
||||
abgebrochen = true
|
||||
karteRef.current = null
|
||||
karte?.remove()
|
||||
}
|
||||
}, [start, ziel, route])
|
||||
}, [start, ziel, route, startText, zielText, nadel])
|
||||
|
||||
return <div ref={behaelter} className="dm-karte" role="img" aria-label="Karte der Fahrt" />
|
||||
const karte = (
|
||||
<div
|
||||
ref={behaelter}
|
||||
className="dm-karte"
|
||||
role="img"
|
||||
aria-label={nadel ? "Karte der Tankstelle" : "Karte der Fahrt"}
|
||||
/>
|
||||
)
|
||||
if (!nadel) return karte
|
||||
return (
|
||||
<div className="dm-kartenbox">
|
||||
{karte}
|
||||
<div className="dm-kartensteuerung dm-kartensteuerung--klein">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Auf die Tankstelle zentrieren"
|
||||
onClick={() =>
|
||||
karteRef.current?.flyTo(
|
||||
[start.lat, start.lon],
|
||||
Math.max(karteRef.current.getZoom(), 14),
|
||||
)
|
||||
}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||||
<path fill="currentColor" d={CI_GPS} />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function TankDetail({ id, geheZu }: { id: string | undefined; geheZu: (na
|
||||
return (
|
||||
<>
|
||||
<Tile className="dm-kartenkachel">
|
||||
<TankstellenKarte adresse={tank.station_address} />
|
||||
<TankstellenKarte name={tank.station_name} adresse={tank.station_address} />
|
||||
</Tile>
|
||||
|
||||
<Tile variant="flat" className="dm-distanzkachel">
|
||||
@@ -367,7 +367,7 @@ function BelegKnopf({
|
||||
const hochladen = async (datei: File) => {
|
||||
setzeMeldung(null)
|
||||
if (datei.type !== "application/pdf" && !datei.name.toLowerCase().endsWith(".pdf")) {
|
||||
setzeMeldung({ art: "fehler", text: "Bitte den Beleg als PDF auswählen." })
|
||||
setzeMeldung({ art: "fehler", text: "Nur PDF-Belege werden unterstützt" })
|
||||
return
|
||||
}
|
||||
setzeLaeuft(true)
|
||||
|
||||
@@ -223,7 +223,7 @@ function TankFormular({
|
||||
const belegLesen = async (datei: File) => {
|
||||
setzeFehler(null)
|
||||
if (datei.type !== "application/pdf" && !datei.name.toLowerCase().endsWith(".pdf")) {
|
||||
setzeFehler("Bitte den Beleg als PDF auswählen.")
|
||||
setzeFehler("Nur PDF-Belege werden unterstützt")
|
||||
return
|
||||
}
|
||||
setzeBelegLaeuft(true)
|
||||
|
||||
@@ -15,7 +15,13 @@ import type { Koordinate } from "../daten/geokodierung"
|
||||
import { Leerzustand } from "./bausteine"
|
||||
import { Karte } from "./Karte"
|
||||
|
||||
export function TankstellenKarte({ adresse }: { adresse: string | null | undefined }) {
|
||||
export function TankstellenKarte({
|
||||
name,
|
||||
adresse,
|
||||
}: {
|
||||
name?: string | null | undefined
|
||||
adresse: string | null | undefined
|
||||
}) {
|
||||
const [position, setzePosition] = useState<Koordinate | null>(null)
|
||||
const [geprueft, setzeGeprueft] = useState(false)
|
||||
|
||||
@@ -32,7 +38,10 @@ export function TankstellenKarte({ adresse }: { adresse: string | null | undefin
|
||||
}
|
||||
}, [adresse])
|
||||
|
||||
if (position) return <Karte start={position} />
|
||||
// Wie im Panel (tankstelleAufKarteZeigen): die CI-Nadel mit Name und
|
||||
// Anschrift im Popup, dazu der Knopf, der wieder darauf zentriert.
|
||||
if (position)
|
||||
return <Karte start={position} nadel={{ name: name ?? null, adresse: adresse ?? null }} />
|
||||
// Solange die Auflösung läuft: eine leere Kartenfläche, wie im Panel (die
|
||||
// Karte steht dort schon, die Nadel kommt nach). Bewusst nicht die
|
||||
// Ladeanzeige der Seite - die meldet „der Bildschirm ist noch nicht da".
|
||||
|
||||
@@ -256,6 +256,7 @@ export function Wertzeile({
|
||||
wert,
|
||||
zusatz,
|
||||
hinweis,
|
||||
warnung,
|
||||
}: {
|
||||
label: ReactNode
|
||||
wert: ReactNode
|
||||
@@ -264,11 +265,20 @@ export function Wertzeile({
|
||||
Angabe - fuer Zeilen, deren rechte Seite nur beschreibt, was hinter der
|
||||
Kachel liegt, statt selbst ein Messwert zu sein. */
|
||||
hinweis?: boolean
|
||||
/** Hebt den Wert als abweichend hervor (var(--warn)), wie das Panel es bei
|
||||
einem Dongle-Wert tut, der nicht zu unserer Einstellung passt. */
|
||||
warnung?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className="dm-wertzeile">
|
||||
<dt>{label}</dt>
|
||||
<dd className={hinweis ? "dm-wertzeile__hinweis" : undefined}>
|
||||
<dd
|
||||
className={
|
||||
[hinweis ? "dm-wertzeile__hinweis" : "", warnung ? "dm-wertzeile__warnung" : ""]
|
||||
.filter(Boolean)
|
||||
.join(" ") || undefined
|
||||
}
|
||||
>
|
||||
{wert}
|
||||
{zusatz && <small>{zusatz}</small>}
|
||||
</dd>
|
||||
|
||||
@@ -1696,6 +1696,30 @@ button.dm-listenzeile {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
/* Karte mit eigener Bedienung darauf (Einzelbeleg). Der Kasten traegt die
|
||||
Positionierung, damit der Knopf sich an DIESER Karte ausrichtet und nicht am
|
||||
naechsten Vorfahren weiter oben - dieselbe Ueberlegung wie .mapbox im Panel. */
|
||||
.dm-kartenbox {
|
||||
position: relative;
|
||||
border-radius: var(--r-tile);
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Kleine Variante: die Belegkarte ist nur gut 200px hoch, ein 48er-Knopf mit
|
||||
16px Abstand verdeckte davon ein Viertel. */
|
||||
.dm-kartensteuerung--klein {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.dm-kartensteuerung--klein button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
/* Die CI-Nadel traegt ihre Farben selbst; der Schatten hebt sie von den
|
||||
Kartenkacheln ab, wie .fahrzeug-pin im Panel. */
|
||||
.dm-nadel {
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
|
||||
}
|
||||
|
||||
.dm-standortmenu {
|
||||
position: absolute;
|
||||
z-index: 410;
|
||||
@@ -1995,3 +2019,8 @@ button.dm-listenzeile {
|
||||
|
||||
.dm-token-gesetzt { color: var(--ok); }
|
||||
.dm-token-offen { color: var(--fg3); }
|
||||
|
||||
/* Abweichender Wert - im Panel steht er in var(--warn), hier ebenso. */
|
||||
.dm-wertzeile__warnung {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user