diff --git a/AGENTS.md b/AGENTS.md index 407da29..1ed7691 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-08-17** (merged the `umsetzung-datametric360` branch — companion app phases +**Last updated: 2026-08-18** (merged the `umsetzung-datametric360` branch — companion app phases 1–10 done, see `UMSETZUNGSPLAN.md`; 2026-08-13: cleaned up remaining EU Data Act residue, fixed oversized toggle switches, and fixed a desktop-layout audit (settings button / rings logo / popups overflowing past the capped content column) — see `DESIGN_AUDIT_2026-08-13.md`; 2026-08-16: fixed @@ -47,7 +47,12 @@ where the image placeholder's grey background had turned invisible-white; separa panel) was brought back in sync with everything the panel gained since 2026-08-11 - see the "Gap found while auditing..." entry near the end of section C; finally, the Übersicht's Kilometerstand/Service tiles were redesigned after the owner asked how Apple HIG and Audi CI would -solve them - both answer the same way, and the merged "9,0 tkm/ 04/27" number field is gone). +solve them - both answer the same way, and the merged "9,0 tkm/ 04/27" number field is gone; +2026-08-18: the owner rejected a follow-up mockup for the "Mein Audi" service preview and +"Anstehende Termine" boxes ("Lost information") - no code from that proposal was built; instead the +existing Nächster-Service box on the Übersicht had its "bis zum/zur ..." wording replaced by the +real Audi CI oil-change/inspection icons (user-supplied SVGs) placed left of the figure, in both +codebases, per the owner's explicit request). This file is the entry point for every new agent session: what this repo is, what is finished, what is missing, and how to work here. Detail lives in the linked documents — this file points, it does not duplicate. @@ -1476,6 +1481,133 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed {opacity:.55}` als Tipp-Rückmeldung ergänzt, da der übliche `.tilebtn`-Hintergrundwechsel hier nicht geht), synchron in `installationspaket/`. Entwurf und Begründung als Artefakt: „Zwei Kacheln, drei Antworten“. +- [x] Nächster-Service-Box: CI-Icon statt "bis zum/zur ..."-Wortlaut (2026-08-18) — der Nutzer + lehnte zunächst einen Folge-Entwurf ab, der dieselbe HIG/CI-Systematik auf die "Mein Audi"- + Service-Vorschau und "Anstehende Termine" auf der Service-Seite anwenden sollte ("I don't like + it" → per Rückfrage: "Lost information", also zu viel weggelassen) — dieser Vorschlag wurde + **nicht** umgesetzt, keine Codeänderung daraus übernommen. Stattdessen ein präziser, neuer + Wunsch für die bereits bestehende Nächster-Service-Box auf der Übersicht: das Wort + ("bis zum Ölwechsel"/"bis zur Inspektion") vor der Kennzahl entfällt zugunsten der echten Audi- + CI-Icons `inspection-l/-s`/`oil-change-l/-s` (vom Nutzer als SVG geliefert, aus + `C:\Users\tobia\Downloads\Icons 2026-08-09 13_04_03\`, verbatim übernommen wie die übrigen + CI-Icons dieses Projekts — l im 48er-, s im 24er-Grid), links der Zahl platziert; auf + Nutzerwunsch anschließend nochmal um 30% vergrößert (24px → 31px). Umgesetzt in beiden + Codebasen: Panel bekam `CI.oilChangeL/-S`/`CI.inspectionL/-S` (neben den bestehenden CI- + Icon-Konstanten) und ein neues `.fig-zeile`-Wrapper-Div (`display:flex;align-items:center`) + um Icon + `.fig` in `vHome()`'s Serviceblock; die Nebenzeile zeigt jetzt nur noch + `vsl. DATUM`. Companion-app bekam die identischen Pfade als neue `SymbolOelwechsel`/ + `SymbolInspektion`-Komponenten in `symbole.tsx` (eigener `RahmenFlaeche`-Wrapper, da diese + zwei — anders als die generischen Tab-Icons dort — gefüllte Flächen-Icons sind, kein + stroke-Wrapper) und eine neue `.dm-servicefig`-CSS-Klasse in `screens.css`; `Uebersicht.tsx` + wählt das Symbol über eine kleine `SERVICE_SYMBOL`-Tabelle nach `service.art`. **Wichtig:** die + Icons blieben bewusst außerhalb von `design-system/` (Lizenzregel oben — echte Audi-CI-Assets + dürfen nur in Panel/companion-app, nicht in das markenfreie `@audi-dash/ui`) — die Verdrahtung + liegt deshalb direkt im jeweiligen Screen, nicht in der gemeinsamen `Fig`-Komponente. Die + Hauptuntersuchung hat kein eigenes Icon (kein Restkilometer-Ziel, `restKm` ist dort immer + `null`) und behält `bisText()` als einzige Kennzeichnung — das deckt sich in beiden Codebasen + genau mit der Bedingung, unter der ein Icon überhaupt existiert. `npm run typecheck`/ + `npm run test` (100/100)/`npm run build` sauber. Live im Docker-Testcontainer geprüft (Version + `1787012001`, synchron in `installationspaket/`): Ölwechsel-Icon rendert links der "8.999 km"- + Kennzahl, Unterzeile zeigt nur noch "vsl. 18.04.2027", keine neuen Konsolenfehler. +- [x] Nächster-Service-Box: Hauptuntersuchung-Datum in Zahlengröße + Art-Name statt "bis zur ..." + (2026-08-18) — Folgewunsch zum Icon-Umbau: das Datum im Zahlenfeld war für die + Hauptuntersuchung (kein Restkilometer-Ziel) bisher kleiner gesetzt (30px statt 40px), extra + damit lange Daten wie "15.04.2027" auf 375px nicht umbrechen; der Nutzer wollte stattdessen + dieselbe 40px-Größe wie bei Ölwechsel/Inspektion, plus darunter statt "bis zur + Hauptuntersuchung" nur noch das Wort "Hauptuntersuchung" selbst. Vor der Umsetzung am + laufenden Panel nachgemessen (nicht angenommen): "15.04.2027" misst bei 40px/Audi Type Wide + 182px, der Kachel-Container hat 322px — die ursprüngliche Wrap-Sorge, die zur 30px-Regel + geführt hatte, bestätigt sich bei den tatsächlichen Container-Maßen nicht. `zahlGroesse` ist + jetzt fest 40 (Panel `vHome()` und companion-app `Uebersicht.tsx`, `Fig size={40}` + unabhängig von `restKm`). Da `bisText()`/`ART_BIS` (Panel) und `bisText()`/`BIS_TEXT` + (companion-app `service.ts`) dadurch ihre einzige Aufrufstelle verloren, wurden beide als + Orphans entfernt (inkl. des zugehörigen companion-app-Tests in `service.test.ts` — von 100 auf + 99 Tests, kein Funktionsverlust, nur der jetzt toten Funktion beraubt). Live im + Docker-Testcontainer per DOM-Patch simuliert (Testfixture hat aktuell Ölwechsel als + nächstgelegenen Termin, nicht die Hauptuntersuchung) und bei 390px Breite screenshotet: + "06.08.2028" einzeilig bei 40px, "Hauptuntersuchung" darunter, kein Icon (wie vorgesehen — es + gibt kein CI-Icon für die Hauptuntersuchung). `npm run typecheck`/`npm run test` + (99/99)/`npm run build` in companion-app sauber. Deployed als Panel-Version `1787012002`, + synchron in `installationspaket/`. +- [x] "Anstehende Termine"-Box (Service-Seite) umgebaut: drei sichtbar getrennte Blöcke + (2026-08-18) — auf Wunsch des Nutzers ein neuer Entwurf (Apple HIG + Audi CI, wie beim + Übersicht-Umbau), mit expliziten Vorgaben: Herstellervorgabe bleibt für Ölwechsel und + Inspektion möglich, "voraussichtlich ..." bleibt für beide erhalten, alle drei Arten werden + sichtbar getrennt, die Hauptuntersuchung bekommt bewusst weder Herstellervorgabe noch + Prognosezeile. Durchlief drei Mockup-Runden als Artefakt ("Drei Termine, drei Blöcke") vor der + Umsetzung: **Runde 1** abgelehnt ("Major Information is missing"). **Runde 2** ergänzte ein + vollständiges Feld-Inventar (Tabelle: jedes Element aus `vService()` mit Fundstelle im + Vorschlag), integrierte den vereinbarten Werkstatttermin in den Hauptvergleich statt als + Anhang, zeigte den "kein Eintrag im Servicebuch"-Zustand. **Runde 3** (auf drei + Inline-Kommentare des Nutzers im Artefakt): Ölwechsel-Icon von 20px auf 26px angehoben (füllt + in seinem 24×24-Raster nur eine 24×10-Fläche statt der fast vollen Höhe des + Inspektion-Icons — per `getBBox()` nachgemessen, kein exakter Ausgleich möglich ohne das Icon + unverhältnismäßig breit zu machen), Herstellervorgabe-Zeile auch am Ölwechsel-Beispiel gezeigt + (vorher nur bei Inspektion), der Hauptuntersuchungs-Block von einem Icon-Platzhalter-Abstand + auf echt linksbündig geändert. Danach vom Nutzer freigegeben ("Ok, implement it"). + **Umsetzung, beide Codebasen:** + Panel (`vService()`, `audi-dashboard-app.js`): `
` durch drei + `
` ersetzt (Kopfzeile mit Icon + Art-Name + optionalem + `.tg-chip` für einen vereinbarten Werkstatttermin, optionale `.tg-vorgabe`-Zeile, `.tg-wert` + als 22px/Gewicht-300-Zahl, optionale `.tg-prognose`-Fußzeile) — Auswahllogik für Wert/ + Fußzeile/Herstellervorgabe unverändert aus dem bisherigen Code übernommen, nur die Markup- + Struktur geändert. `ART_ICON`/`ART_ICON_GROESSE` (26px Ölwechsel, 20px Inspektion) von + `vHome()` auf Modulebene gehoben, da jetzt von zwei Stellen genutzt — `vHome()`s eigene + Icon-Größe (31px, beide gleich) bewusst unverändert gelassen, das ist eine offene, dem Nutzer + gestellte Rückfrage. Der Werkstatt-Chip nutzt `var(--red)` nur als Text-/Rahmenfarbe, keine + Füllung — deckt sich mit der bereits bestehenden Projektregel "Rot ist Akzent, nie Fläche" + (siehe iOS-Overlay-Fix vom 2026-08-13). Deployed als Version `1787013000`, synchron in + `installationspaket/`, live im Docker-Testcontainer per direkter DOM-Abfrage geprüft (JS statt + Screenshot, da die Browser-Pane in dieser Sitzung zeitweise nicht kompositierte): alle drei + Blöcke rendern mit korrektem Text, Icons in 26px/20px, Hauptuntersuchung ohne Icon, keine + neuen Konsolenfehler. + companion-app (`Service.tsx`): dieselbe Drei-Blöcke-Struktur, aber eine **echte + Neustrukturierung, kein 1:1-Kopieren** — die bisherige Kachel hieß "Nächster Ölwechsel" und + mischte Ölwechsel-, Inspektions- und Hauptuntersuchungs-Zeilen fachfremd in einer gemeinsamen + `Werteliste`; jetzt "Anstehende Termine" wie im Panel, mit denselben drei `.dm-termingruppe`- + Blöcken. Neue `SymbolOelwechsel`/`SymbolInspektion`-Komponenten in `symbole.tsx` (bereits für + die Übersicht angelegt, hier wiederverwendet). Neuer Export `letzterInspektion()` in + `service.ts` (Gegenstück zu `letzterOelwechsel()`) für den "kein Eintrag"-Zustand der + Inspektion. **Zwei bewusste, dokumentierte Vereinfachungen gegenüber dem Panel** (vorbestehende + Lücken, nicht durch diese Änderung verursacht): (1) companion-app hat keine "Termin + vereinbaren"-Funktion und keine `vereinbart`-Datenfelder für Service-Termine — der Werkstatt- + Chip entfällt deshalb ersatzlos, es gibt nichts anzuzeigen. (2) companion-app hat kein + Gegenstück zu `intervalle()`/dem generischen Herstellerintervall-Fallback des Panels; die + Herstellervorgabe-Zeile wird stattdessen direkt aus `fahrzeug.oel.modus` abgeleitet (Ölwechsel: + nur bei `modus === "hersteller"`, Inspektion: immer, da sie kein einstellbares Intervall hat — + deckt sich inhaltlich mit dem Panel-Verhalten, auch ohne dieselbe Funktion zu teilen). Beide + Punkte sind funktional gleichwertig zum bisherigen companion-app-Stand, nicht neu eingeführte + Lücken. `npm run typecheck` sauber, `npm run test` 99/99, `npm run build` erfolgreich; die + bestehenden "Alle Seiten rendern mit Beispieldaten"-Tests rendern die Service-Seite weiterhin + ohne Absturz. Kein Live-Screenshot in dieser Sitzung (bräuchte einen laufenden Dev-Server samt + Backend-Anbindung) — strukturell verifiziert über Typecheck/Tests/Build. + **Zwei Nachbesserungen direkt nach der ersten Live-Prüfung, beide vom Nutzer gemeldet:** + (1) "Herstellervorgabe" fehlte beim Ölwechsel, obwohl der angezeigte Wert (29.100 km / + 25.07.2028) vom Fahrzeug selbst gemeldet wird (`hatMeldung`/`fahrzeug.oelwechselFaelligTs`). + Root Cause: die Zeile war bisher ausschließlich an `iv.hersteller` (Panel) bzw. + `fahrzeug.oel.modus === "hersteller"` (companion-app) gekoppelt — dem *App-eigenen* + Intervall-Modus aus "Einrichten". Der Bordcomputer kennt diesen App-Modus aber gar nicht; + eine Fahrzeugmeldung stammt immer aus dem werkseigenen Wartungsprogramm und ist damit per + Definition Herstellervorgabe, unabhängig vom gewählten App-Modus. Das Testprofil hat den + Ölwechsel aktuell auf `modus: "eigen"` stehen (10.000 km/12 Monate statt 30.000 km/24 + Monate) — das war korrektes Verhalten der alten Bedingung, nur die Bedingung selbst war zu + eng. Fix: `herstellervorgabe = hatMeldung || iv.hersteller` (Panel) bzw. + `oelHatMeldung || fahrzeug.oel.modus === "hersteller"` (companion-app) — dieselbe alte + Bedingung bleibt für den Fall ohne Fahrzeugmeldung erhalten (App-eigener Fallback auf das + Herstellerintervall), kommt jetzt aber zusätzlich bei einer echten Fahrzeugmeldung zum + Tragen. (2) Die Prognose-Fußzeile zeigte "8.999 km, voraussichtlich am 18.04.2027" auch dann, + wenn in "Einrichten" tatsächlich Herstellervorgabe gewählt ist — dort rechnet die eigene + Prognose ohnehin schon mit dem Herstellerintervall, die Restkilometerzahl wäre also nur eine + zweite, verwirrende km-Angabe neben dem Fahrzeug-Wert direkt darüber. Fix: die "X km,"- + Vorsilbe entfällt, sobald `CAR.oel.modus`/`fahrzeug.oel.modus === "hersteller"` ist — nur beim + eigenen (kürzeren) Intervall bleibt sie stehen, weil sie dort echte Zusatzinformation ist + (matcht damit exakt, wie die Inspektion-Fußzeile schon immer aussah, da sie kein einstellbares + Intervall hat). Mit dem Testprofil (`modus: "eigen"`) bleibt die Kilometerzahl also weiterhin + sichtbar — korrekt, das ist genau der Fall, für den sie gedacht ist; umschalten auf + Herstellervorgabe in "Einrichten" würde sie ausblenden. Beide Fixes deployed als Panel-Version + `1787013001`, synchron in `installationspaket/`, live per DOM-Abfrage geprüft: Ölwechsel-Block + zeigt jetzt "Herstellervorgabe", keine neuen Konsolenfehler. companion-app: `npm run typecheck` + sauber, `npm run test` 99/99, `npm run build` erfolgreich. - [x] **Parity rule eingeführt (2026-08-17):** der Nutzer stellte klar, dass Panel und `companion-app/` immer denselben funktionalen Stand haben sollen — auch wenn eine Anfrage nur das Panel nennt, das ist reiner Testkomfort (Docker-Instanz), keine Scope-Entscheidung. Regel diff --git a/companion-app/src/daten/service.test.ts b/companion-app/src/daten/service.test.ts index 96f9847..95fca7c 100644 --- a/companion-app/src/daten/service.test.ts +++ b/companion-app/src/daten/service.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" -import { bisText, inspektionPrognose, letzterOelwechsel, naechsterService, oelwechselPrognose } from "./service" +import { inspektionPrognose, letzterOelwechsel, naechsterService, oelwechselPrognose } from "./service" import type { Fahrzeug } from "./profilAdapter" function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug { @@ -142,10 +142,4 @@ describe("naechsterService", () => { it("liefert nichts ohne Servicebuch und ohne gepflegte Hauptuntersuchung", () => { expect(naechsterService(fahrzeug(46000), [], jetzt)).toBeNull() }) - - it("bisText nennt den richtigen Artikel je Art", () => { - expect(bisText("Ölwechsel")).toBe("bis zum Ölwechsel") - expect(bisText("Inspektion")).toBe("bis zur Inspektion") - expect(bisText("Hauptuntersuchung")).toBe("bis zur Hauptuntersuchung") - }) }) diff --git a/companion-app/src/daten/service.ts b/companion-app/src/daten/service.ts index 56e2c46..2d88f16 100644 --- a/companion-app/src/daten/service.ts +++ b/companion-app/src/daten/service.ts @@ -51,6 +51,11 @@ export function letzterOelwechsel(buch: readonly Buchhaltung[]): Buchhaltung | n return letzterEintrag(buch, /öl|oel/i) } +/** Der jüngste Servicebucheintrag, dessen Art auf eine Inspektion hindeutet. */ +export function letzterInspektion(buch: readonly Buchhaltung[]): Buchhaltung | null { + return letzterEintrag(buch, /inspektion/i) +} + /** Gemeinsamer Kern für Ölwechsel- und Inspektionsprognose - beide rechnen identisch, nur mit anderem Intervall und anderer Suche im Servicebuch. */ function servicePrognose( @@ -134,19 +139,6 @@ export interface NaechsterService { datum: string } -/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese - Zuordnung stünde auf der Übersicht "bis zum Inspektion". Gleiche Tabelle wie - ART_BIS im Panel (audi-dashboard-app.js). */ -const BIS_TEXT: Record = { - "Ölwechsel": "bis zum Ölwechsel", - "Inspektion": "bis zur Inspektion", - "Hauptuntersuchung": "bis zur Hauptuntersuchung", -} - -export function bisText(art: Serviceart): string { - return BIS_TEXT[art] -} - /** * Der zeitlich nächste anstehende Service über alle drei Arten hinweg — * Gegenstück zu `naechsterTermin()` im Panel. diff --git a/companion-app/src/screens/Service.tsx b/companion-app/src/screens/Service.tsx index fd8f696..5c06c6c 100644 --- a/companion-app/src/screens/Service.tsx +++ b/companion-app/src/screens/Service.tsx @@ -13,9 +13,10 @@ import { useState } from "react" import { ActionButton, Feld, Tile } from "@audi-dash/ui" import { useDaten } from "../daten/DatenKontext" -import { inspektionPrognose, oelwechselPrognose } from "../daten/service" +import { inspektionPrognose, letzterInspektion, letzterOelwechsel, oelwechselPrognose } from "../daten/service" import { datum, de, isoTag } from "../format" import type { SeitenName } from "../navigation" +import { SymbolInspektion, SymbolOelwechsel } from "../symbole" import { Leerzustand, NaviKachel, Wertzeile, Werteliste, bestaetigen } from "./bausteine" import { kalenderDateiLaden } from "./kalender" @@ -44,53 +45,106 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) => const prognose = oelwechselPrognose(fahrzeug, buch) const inspektionsprognose = inspektionPrognose(fahrzeug, buch) + // Ölwechsel und Inspektion je einzeln, statt einer gemeinsamen Wertzeilen- + // Liste unter "Nächster Ölwechsel" (die Inspektion/Hauptuntersuchung dort + // fachfremd mitschleppte) - deckungsgleich mit vService() im Panel: drei + // sichtbar getrennte Blöcke, jeder mit eigenem Icon, optionaler + // Herstellervorgabe-Zeile und optionaler Prognose-Fußzeile. + const oelHatEintrag = letzterOelwechsel(buch) != null + const oelHatMeldung = fahrzeug.oelwechselFaelligTs != null || fahrzeug.oelwechselFaelligKm != null + const oelWert = oelHatMeldung + ? [ + fahrzeug.oelwechselFaelligKm != null ? `${de(Math.abs(fahrzeug.oelwechselFaelligKm))} km` : null, + fahrzeug.oelwechselFaelligTs ? datum(fahrzeug.oelwechselFaelligTs) : null, + ] + .filter(Boolean) + .join(" / ") + : prognose + ? `${de(prognose.restKm)} km · ${datum(prognose.datum)}` + : null + // Bei Herstellervorgabe rechnet die eigene Prognose ohnehin schon mit dem + // Herstellerintervall - die Restkilometerzahl daneben wäre nur eine zweite, + // verwirrende km-Angabe neben dem Fahrzeug-Wert oben. Nur beim eigenen + // (kürzeren) Intervall ist sie eine echte Zusatzinformation. + const oelFussnote = oelHatMeldung && prognose + ? fahrzeug.oel.modus === "hersteller" + ? `voraussichtlich am ${datum(prognose.datum)}` + : `${de(prognose.restKm)} km, voraussichtlich am ${datum(prognose.datum)}` + : null + // Der Ölwechsel folgt in "Einrichten" wahlweise der Herstellervorgabe oder + // einem kürzeren eigenen Intervall (fahrzeug.oel.modus) - die Inspektion hat + // kein einstellbares Intervall, rechnet also immer mit der Herstellervorgabe + // (siehe inspektionPrognose()'s eigener Kommentar dazu). Eine Fahrzeugmeldung + // stammt aber immer aus dem werkseigenen Wartungsprogramm des Bordcomputers - + // der kennt das eigene App-Intervall gar nicht -, zählt also unabhängig vom + // gewählten Modus als Herstellervorgabe. + const oelHerstellervorgabe = oelHatMeldung || fahrzeug.oel.modus === "hersteller" + + const inspHatEintrag = letzterInspektion(buch) != null + const inspHatMeldung = fahrzeug.inspektionFaelligTs != null || fahrzeug.inspektionFaelligKm != null + const inspWert = inspHatMeldung + ? [ + fahrzeug.inspektionFaelligKm != null ? `${de(Math.abs(fahrzeug.inspektionFaelligKm))} km` : null, + fahrzeug.inspektionFaelligTs ? datum(fahrzeug.inspektionFaelligTs) : null, + ] + .filter(Boolean) + .join(" / ") + : inspektionsprognose + ? datum(inspektionsprognose.datum) + : null + const inspFussnote = inspektionsprognose ? `voraussichtlich am ${datum(inspektionsprognose.datum)}` : null + + const huDatum = fahrzeug.hu ? new Date(fahrzeug.hu) : null + const huGueltig = !!huDatum && !Number.isNaN(huDatum.getTime()) + return ( <> - Nächster Ölwechsel - Anstehende Termine + +
+
+ + Ölwechsel +
+ {oelHatEintrag ? ( <> - - - - {inspektionsprognose && ( - - )} - + {oelHerstellervorgabe &&
Herstellervorgabe
} +
{oelWert}
+ {oelFussnote &&
{oelFussnote}
} - } - /> + ) : ( +
kein Eintrag im Servicebuch
+ )} +
+ +
+
+ + Inspektion +
+ {inspHatEintrag ? ( + <> +
Herstellervorgabe
+
{inspWert}
+ {inspFussnote &&
{inspFussnote}
} + + ) : ( +
kein Eintrag im Servicebuch
+ )} +
+ +
+
+ Hauptuntersuchung +
+ {huGueltig ? ( +
{datum(fahrzeug.hu)}
+ ) : ( +
kein Eintrag im Servicebuch
+ )} +
+ {prognose && (
)}

- Die eigene Prognose rechnet ab dem letzten Ölwechsel im Servicebuch neu und nutzt die - seither tatsächlich gefahrene Strecke. Sie ersetzt die Meldung des Fahrzeugs nicht, - sondern ergänzt sie. + Die eigene Prognose rechnet ab dem letzten Eintrag der jeweiligen Art im Servicebuch neu + und nutzt die seither tatsächlich gefahrene Strecke. Sie ersetzt die Meldung des + Fahrzeugs nicht, sondern ergänzt sie.

diff --git a/companion-app/src/screens/Uebersicht.tsx b/companion-app/src/screens/Uebersicht.tsx index c09ea31..3553777 100644 --- a/companion-app/src/screens/Uebersicht.tsx +++ b/companion-app/src/screens/Uebersicht.tsx @@ -11,14 +11,24 @@ import { useState } from "react" import { Fig, ProgressBar, StatusRow, Tile } from "@audi-dash/ui" import { useDaten } from "../daten/DatenKontext" -import { bisText, naechsterService } from "../daten/service" +import { naechsterService } from "../daten/service" import { datum, datumZeit, dauer, de, deOderStrich, eur } from "../format" import type { SeitenName } from "../navigation" import { Leerzustand, NaviKachel } from "./bausteine" import { Bild } from "./Bild" import type { ServicebuchEintrag } from "./Service" +import { SymbolInspektion, SymbolOelwechsel } from "../symbole" import { Typenschild, schildErkennen } from "./Typenschild" +// "bis zum Ölwechsel"/"bis zur Inspektion" entfaellt zugunsten des CI-Icons +// links der Kennzahl - deckungsgleich mit ART_ICON im Panel (vHome()). Die +// Hauptuntersuchung hat kein Restkilometer-Ziel und zeigt stattdessen den +// Art-Namen selbst unter dem (gleich großen) Datum. +const SERVICE_SYMBOL: Partial> = { + "Ölwechsel": SymbolOelwechsel, + "Inspektion": SymbolInspektion, +} + export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string) => void }) { const { fahrzeug, einstellungen, fahrten, tankvorgaenge, jetztAktualisieren, statusStand } = useDaten() @@ -143,15 +153,26 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string) {service ? ( <> - + {(() => { + const Symbol = SERVICE_SYMBOL[service.art] + const fig = ( + + ) + return Symbol ? ( +
+ + {fig} +
+ ) : ( + fig + ) + })()} - {service.restKm !== null - ? `${bisText(service.art)} · vsl. ${datum(service.datum)}` - : bisText(service.art)} + {service.restKm !== null ? `voraussichtlich am ${datum(service.datum)}` : service.art} ) : ( diff --git a/companion-app/src/stile/screens.css b/companion-app/src/stile/screens.css index b01901a..b871012 100644 --- a/companion-app/src/stile/screens.css +++ b/companion-app/src/stile/screens.css @@ -214,6 +214,17 @@ line-height: 1; color: var(--fg3); } +/* Serviceart-Icon links der Kennzahl statt des Wortes "bis zum/zur ..." - + deckungsgleich mit .fig-zeile im Panel (audi-dashboard.css). */ +.dm-servicefig { + display: flex; + align-items: center; + gap: 8px; +} +.dm-servicefig svg { + color: var(--fg2); + flex: none; +} .dm-servicezeile { display: flex; align-items: baseline; @@ -229,6 +240,57 @@ color: var(--fg2); } +/* Service-Seite, "Anstehende Termine": drei sichtbar getrennte Bloecke statt + einer Wertzeilen-Liste - deckungsgleich mit .termingruppe im Panel + (audi-dashboard.css, vService()). Trennung ueber Abstand und eine Haarlinie + zwischen den Bloecken, nicht ueber verschachtelte Kacheln (Audi-CI). */ +.dm-termingruppe { + padding: 18px 0; + border-top: 1px solid var(--line); +} +.dm-termingruppe:first-of-type { + padding-top: 2px; + border-top: 0; +} +.dm-tgkopf { + display: flex; + align-items: center; + gap: 9px; + min-width: 0; +} +.dm-tgkopf svg { + color: var(--fg2); + flex: none; +} +.dm-tgkopf span { + font-size: 16.5px; + color: var(--fg); + font-weight: 400; +} +.dm-tgvorgabe { + font-size: 11.5px; + color: var(--fg2); + margin-top: 8px; +} +.dm-tgwert { + font-weight: 300; + font-size: 22px; + color: var(--fg); + letter-spacing: -0.005em; + margin-top: 4px; +} +.dm-tgwert--leer { + font-weight: 400; + font-size: 15px; + color: var(--fg3); + margin-top: 8px; +} +.dm-tgprognose { + font-size: 12px; + color: var(--fg2); + margin-top: 6px; +} + .dm-tanktext { display: flex; justify-content: space-between; diff --git a/companion-app/src/symbole.tsx b/companion-app/src/symbole.tsx index c4d9263..034dc0f 100644 --- a/companion-app/src/symbole.tsx +++ b/companion-app/src/symbole.tsx @@ -147,6 +147,48 @@ export function SymbolMehr({ groesse }: SymbolProps) { ) } +/** + * oil-change-s/inspection-s aus dem Audi-CI-Icon-Satz (vom Nutzer als SVG + * geliefert, verbatim übernommen — 24er-Grid, wie im Panel als CI.oilChangeS/ + * CI.inspectionS). Anders als die generischen Rahmen()-Symbole oben sind das + * gefüllte Flächen-Icons, deshalb kein stroke-Wrapper. + */ +function RahmenFlaeche({ groesse = 24, kinder }: SymbolProps & { kinder: ReactElement }) { + return ( + + ) +} + +export function SymbolOelwechsel({ groesse }: SymbolProps) { + return ( + + } + /> + ) +} + +export function SymbolInspektion({ groesse }: SymbolProps) { + return ( + + } + /> + ) +} + export function SymbolWarnung({ groesse }: SymbolProps) { return ( ', editS: '', + /* oil-change-l/-s, inspection-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. Die -s-Variante steht links neben der Kennzahl im + Nächster-Service-Block (vHome()) statt des vorherigen "bis zum/zur..." + Textes - die Serviceart wird jetzt ueber das Icon erkannt, nicht ueber + Worte. */ + oilChangeL: '', + oilChangeS: '', + inspectionL: '', + inspectionS: '', }; function ciSVG(pfad, px = 16) { return ``; @@ -1089,14 +1099,15 @@ function teaser() { function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; } -/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese - Zuordnung stünde auf der Übersicht "bis zum Inspektion". */ -const ART_BIS = { - "Ölwechsel": "bis zum Ölwechsel", - "Inspektion": "bis zur Inspektion", - "Hauptuntersuchung": "bis zur Hauptuntersuchung", -}; -const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`; +// CI-Icon je Serviceart statt eines Wort-Zusatzes ("bis zum Ölwechsel" o.ä.) - +// nur Ölwechsel/Inspektion haben eines, die Hauptuntersuchung nicht. Verwendet +// von vHome() (Nächster-Service-Block) und vService() (Anstehende Termine). +// Das Ölwechsel-Icon ist bei gleicher Boxgröße optisch kleiner als das +// Inspektion-Icon (füllt in seinem eigenen 24x24-Raster nur eine 24x10-Fläche +// statt fast der vollen Höhe) - deshalb eigene, größere Boxgröße statt einer +// gemeinsamen Konstante. +const ART_ICON = { "Ölwechsel": CI.oilChangeS, "Inspektion": CI.inspectionS }; +const ART_ICON_GROESSE = { "Ölwechsel": 26, "Inspektion": 20 }; function intervalle(t) { const oel = CAR.oel, b = t.basis; @@ -1281,17 +1292,26 @@ function vHome() { const hauptwert = restKm !== null ? `${de(restKm)}km` : (datum ? dedat(datum) : "–"); - // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner, - // damit "15.04.2027" auf 375px nicht umbricht. - const zahlGroesse = restKm !== null ? 40 : 30; + // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - auf Nutzerwunsch + // jetzt in derselben Groesse wie die km-Zahl (vorher 30px, damit "15.04.2027" + // auf 375px nicht umbricht - am laufenden Panel geprueft, dass es bei 40px + // weiterhin einzeilig bleibt). + const zahlGroesse = 40; + // Icon statt "bis zum Ölwechsel"/"bis zur Inspektion" (nur fuer die beiden + // Arten, die eines haben). Die Hauptuntersuchung hat kein Restkilometer-Ziel + // und zeigt stattdessen den Art-Namen selbst unter dem Datum. + const artIcon = ART_ICON[t.art]; const nebenzeile = restKm !== null && datum - ? `${bisText(t.art)} · vsl. ${dedat(datum)}` - : bisText(t.art); + ? `voraussichtlich am ${dedat(datum)}` + : t.art; + const figMarkup = artIcon + ? `
${ciSVG(artIcon, 31)}
${hauptwert}
` + : `
${hauptwert}
`; return `
${kmZeile} @@ -1672,42 +1692,52 @@ function vService() { const sv = CAR.service; return `
Anstehende Termine -
- ${termine().map((t, i, arr) => { - const letzte = i === arr.length - 1 ? 'style="border-bottom:0"' : ""; - const iv = intervalle(t); - if (!iv) return `
-
${t.art}
kein Eintrag im Servicebuch
`; - const anzeige = iv.hersteller || iv.eigen; - const fm = fahrzeugMeldungRoh(t.art); - const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null; - // Forecast-Zeile aus dem kundeneigenen Intervall in "Einrichten" mit - // eigenständiger Reset-nach-Service-Logik (servicePrognose()): beim - // Ölwechsel mit Restkilometern, bei der Inspektion auf Wunsch nur mit - // dem Datum. Hauptuntersuchung bleibt bewusst ohne. - const eigenOel = t.art === "Ölwechsel" ? oelwechselPrognose() : null; - const eigenInsp = t.art === "Inspektion" ? inspektionPrognose() : null; - const hatMeldung = fm.km != null || fmDatum; - // Die Prognose steht als eigene Zeile ueber die volle Kachelbreite - // unter dt/dd (.row-fussnote), nicht mehr in der rechten Spalte: dort - // standen nur ~217px zur Verfuegung, in die "9.029 km, voraussichtlich - // am 12.04.2027" zwangslaeufig zweizeilig umbrach. - const fussnote = hatMeldung && eigenOel - ? `${de(eigenOel.restKm)} km, voraussichtlich am ${dedat(eigenOel.datum)}` - : eigenInsp ? `voraussichtlich am ${dedat(eigenInsp.datum)}` : ""; - return `
-
${t.art} - ${t.vereinbart ? `
Werkstatt am ${dedat(new Date(t.vereinbart))}` : ""} -
-
- ${iv.hersteller ? `Herstellervorgabe` : ""} - ${hatMeldung - ? `${fm.km != null ? de(fm.km) + " km" : ""}${fm.km != null && fmDatum ? " / " : ""}${fmDatum || ""}` - : (eigenOel ? de(eigenOel.restKm) + " km · " + dedat(eigenOel.datum) : (anzeige.km !== null ? de(anzeige.km) + " km · " : "") + dedat(anzeige.datum))} -
- ${fussnote ? `${fussnote}` : ""} + ${termine().map((t) => { + const artIcon = ART_ICON[t.art]; + const iconMarkup = artIcon ? ciSVG(artIcon, ART_ICON_GROESSE[t.art]) : ""; + const kopf = `
${iconMarkup}${t.art}
`; + const iv = intervalle(t); + if (!iv) return `
+
${kopf}
+
kein Eintrag im Servicebuch
+
`; + const anzeige = iv.hersteller || iv.eigen; + const fm = fahrzeugMeldungRoh(t.art); + const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null; + // Forecast-Zeile aus dem kundeneigenen Intervall in "Einrichten" mit + // eigenständiger Reset-nach-Service-Logik (servicePrognose()): beim + // Ölwechsel mit Restkilometern, bei der Inspektion auf Wunsch nur mit + // dem Datum. Hauptuntersuchung bleibt bewusst ohne. + const eigenOel = t.art === "Ölwechsel" ? oelwechselPrognose() : null; + const eigenInsp = t.art === "Inspektion" ? inspektionPrognose() : null; + const hatMeldung = fm.km != null || fmDatum; + // Bei Herstellervorgabe rechnet die eigene Prognose ohnehin schon mit dem + // Herstellerintervall - die Restkilometerzahl daneben waere nur eine + // zweite, verwirrende km-Angabe neben dem Fahrzeug-Wert oben. Nur beim + // eigenen (kuerzeren) Intervall ist sie eine echte Zusatzinformation. + const fussnote = hatMeldung && eigenOel + ? (CAR.oel.modus === "hersteller" + ? `voraussichtlich am ${dedat(eigenOel.datum)}` + : `${de(eigenOel.restKm)} km, voraussichtlich am ${dedat(eigenOel.datum)}`) + : eigenInsp ? `voraussichtlich am ${dedat(eigenInsp.datum)}` : ""; + const wert = hatMeldung + ? `${fm.km != null ? de(fm.km) + " km" : ""}${fm.km != null && fmDatum ? " / " : ""}${fmDatum || ""}` + : (eigenOel ? de(eigenOel.restKm) + " km · " + dedat(eigenOel.datum) : (anzeige.km !== null ? de(anzeige.km) + " km · " : "") + dedat(anzeige.datum)); + // Eine Fahrzeugmeldung stammt immer aus dem werkseigenen Wartungsprogramm + // des Bordcomputers - das kennt das eigene, in "Einrichten" gewaehlte + // Intervall gar nicht. Deshalb zaehlt sie unabhaengig von iv.hersteller + // als Herstellervorgabe; nur der App-eigene Eigen-Intervall-Fallback + // (anzeige.eigen/eigenOel bei modus "eigen") ist es nicht. + const herstellervorgabe = hatMeldung || iv.hersteller; + return `
+
+ ${kopf} + ${t.vereinbart ? `Werkstatt am ${dedat(new Date(t.vereinbart))}` : ""} +
+ ${herstellervorgabe ? `
Herstellervorgabe
` : ""} +
${wert}
+ ${fussnote ? `
${fussnote}
` : ""}
`; }).join("")} -
Termin vereinbaren ${(() => { const ts = termine(), t = ts[Math.min(svAuswahl, ts.length - 1)]; @@ -2058,7 +2088,7 @@ function vEinst() { Ölwechsel-Intervall
- +
${CAR.oel.modus === "hersteller" ? `
diff --git a/homeassistant/installationspaket/www/audi-dashboard-version.json b/homeassistant/installationspaket/www/audi-dashboard-version.json index dafb220..def37c1 100644 --- a/homeassistant/installationspaket/www/audi-dashboard-version.json +++ b/homeassistant/installationspaket/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1787007000} +{"version": 1787013003} diff --git a/homeassistant/installationspaket/www/audi-dashboard.css b/homeassistant/installationspaket/www/audi-dashboard.css index 2afff66..3574918 100644 --- a/homeassistant/installationspaket/www/audi-dashboard.css +++ b/homeassistant/installationspaket/www/audi-dashboard.css @@ -482,6 +482,9 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } .serviceblock:active { opacity: .55; } /* Beschriftung nie unter dem Chevron hindurchlaufen lassen */ .serviceblock > .label:first-child { padding-right: 24px; } +/* Serviceart-Icon links der Kennzahl statt des Wortes "bis zum/zur ...". */ +.fig-zeile { display: flex; align-items: center; gap: 8px; margin-top: 8px; } +.fig-zeile svg { color: var(--fg2); flex: 0 0 auto; } .servicezeile { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); @@ -489,6 +492,35 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } margin-top: 14px; padding-top: 13px; } +/* Anstehende Termine (vService): drei sichtbar getrennte Bloecke statt einer + dt/dd-Liste - Apple HIG ("essential information... don't obscure it by + crowding it with nonessential details") plus Audi-CI (Flaeche mit Haarlinien + statt Karten, eigene Groessenklasse fuer Zahlen). Trennung ueber Abstand und + eine Haarlinie zwischen den Bloecken, nicht ueber verschachtelte Kacheln. + Jeder Block: Kopfzeile (Icon + Art-Name, optional ein Chip fuer einen + vereinbarten Werkstatttermin), optionale Herstellervorgabe-Zeile, der Wert, + optional die eigene Prognose als Fusszeile. */ +.termingruppe { padding: 18px 0; border-top: 1px solid var(--line); } +.termingruppe:first-of-type { padding-top: 2px; border-top: 0; } +.tg-kopf { display: flex; align-items: center; justify-content: space-between; gap: 10px; } +.tg-name { display: flex; align-items: center; gap: 9px; min-width: 0; } +.tg-name svg { color: var(--fg2); flex: 0 0 auto; } +.tg-name span { font-size: 16.5px; color: var(--fg); font-weight: 400; } +/* Chip statt rotem Fliesstext fuer einen vereinbarten Werkstatttermin - Rot + bleibt Akzent (Text/Rahmen), nie Flaeche, wie im Rest der App. */ +.tg-chip { + font-size: 10.5px; color: var(--red); + border: 1px solid var(--red); border-radius: var(--r-pill); + padding: 3px 8px; white-space: nowrap; flex: 0 0 auto; +} +.tg-vorgabe { font-size: 11.5px; color: var(--fg2); margin-top: 8px; } +.tg-wert { + font-family: "Audi Type Wide", var(--font-stack); font-weight: 300; + font-size: 22px; color: var(--fg); letter-spacing: -.005em; margin-top: 4px; +} +.tg-wert.leer { font-family: var(--font-stack); font-weight: 400; font-size: 15px; color: var(--fg3); margin-top: 8px; } +.tg-prognose { font-size: 12px; color: var(--fg2); margin-top: 6px; } + .badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .badge img { height: 18px; width: auto; display: none; } :host([data-theme=nacht]) .badge img.negative { display: block; } diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 9612e67..0b82ca2 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -971,6 +971,16 @@ const CI = { verbatim uebernommen) - l im 48er-, s im 24er-Grid wie poi-l/poi-s. */ editL: '', editS: '', + /* oil-change-l/-s, inspection-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. Die -s-Variante steht links neben der Kennzahl im + Nächster-Service-Block (vHome()) statt des vorherigen "bis zum/zur..." + Textes - die Serviceart wird jetzt ueber das Icon erkannt, nicht ueber + Worte. */ + oilChangeL: '', + oilChangeS: '', + inspectionL: '', + inspectionS: '', }; function ciSVG(pfad, px = 16) { return ``; @@ -1089,14 +1099,15 @@ function teaser() { function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; } -/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese - Zuordnung stünde auf der Übersicht "bis zum Inspektion". */ -const ART_BIS = { - "Ölwechsel": "bis zum Ölwechsel", - "Inspektion": "bis zur Inspektion", - "Hauptuntersuchung": "bis zur Hauptuntersuchung", -}; -const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`; +// CI-Icon je Serviceart statt eines Wort-Zusatzes ("bis zum Ölwechsel" o.ä.) - +// nur Ölwechsel/Inspektion haben eines, die Hauptuntersuchung nicht. Verwendet +// von vHome() (Nächster-Service-Block) und vService() (Anstehende Termine). +// Das Ölwechsel-Icon ist bei gleicher Boxgröße optisch kleiner als das +// Inspektion-Icon (füllt in seinem eigenen 24x24-Raster nur eine 24x10-Fläche +// statt fast der vollen Höhe) - deshalb eigene, größere Boxgröße statt einer +// gemeinsamen Konstante. +const ART_ICON = { "Ölwechsel": CI.oilChangeS, "Inspektion": CI.inspectionS }; +const ART_ICON_GROESSE = { "Ölwechsel": 26, "Inspektion": 20 }; function intervalle(t) { const oel = CAR.oel, b = t.basis; @@ -1281,17 +1292,26 @@ function vHome() { const hauptwert = restKm !== null ? `${de(restKm)}km` : (datum ? dedat(datum) : "–"); - // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner, - // damit "15.04.2027" auf 375px nicht umbricht. - const zahlGroesse = restKm !== null ? 40 : 30; + // Ohne Restkilometer steht das Datum selbst im Zahlenfeld - auf Nutzerwunsch + // jetzt in derselben Groesse wie die km-Zahl (vorher 30px, damit "15.04.2027" + // auf 375px nicht umbricht - am laufenden Panel geprueft, dass es bei 40px + // weiterhin einzeilig bleibt). + const zahlGroesse = 40; + // Icon statt "bis zum Ölwechsel"/"bis zur Inspektion" (nur fuer die beiden + // Arten, die eines haben). Die Hauptuntersuchung hat kein Restkilometer-Ziel + // und zeigt stattdessen den Art-Namen selbst unter dem Datum. + const artIcon = ART_ICON[t.art]; const nebenzeile = restKm !== null && datum - ? `${bisText(t.art)} · vsl. ${dedat(datum)}` - : bisText(t.art); + ? `voraussichtlich am ${dedat(datum)}` + : t.art; + const figMarkup = artIcon + ? `
${ciSVG(artIcon, 31)}
${hauptwert}
` + : `
${hauptwert}
`; return `
${kmZeile} @@ -1672,42 +1692,52 @@ function vService() { const sv = CAR.service; return `
Anstehende Termine -
- ${termine().map((t, i, arr) => { - const letzte = i === arr.length - 1 ? 'style="border-bottom:0"' : ""; - const iv = intervalle(t); - if (!iv) return `
-
${t.art}
kein Eintrag im Servicebuch
`; - const anzeige = iv.hersteller || iv.eigen; - const fm = fahrzeugMeldungRoh(t.art); - const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null; - // Forecast-Zeile aus dem kundeneigenen Intervall in "Einrichten" mit - // eigenständiger Reset-nach-Service-Logik (servicePrognose()): beim - // Ölwechsel mit Restkilometern, bei der Inspektion auf Wunsch nur mit - // dem Datum. Hauptuntersuchung bleibt bewusst ohne. - const eigenOel = t.art === "Ölwechsel" ? oelwechselPrognose() : null; - const eigenInsp = t.art === "Inspektion" ? inspektionPrognose() : null; - const hatMeldung = fm.km != null || fmDatum; - // Die Prognose steht als eigene Zeile ueber die volle Kachelbreite - // unter dt/dd (.row-fussnote), nicht mehr in der rechten Spalte: dort - // standen nur ~217px zur Verfuegung, in die "9.029 km, voraussichtlich - // am 12.04.2027" zwangslaeufig zweizeilig umbrach. - const fussnote = hatMeldung && eigenOel - ? `${de(eigenOel.restKm)} km, voraussichtlich am ${dedat(eigenOel.datum)}` - : eigenInsp ? `voraussichtlich am ${dedat(eigenInsp.datum)}` : ""; - return `
-
${t.art} - ${t.vereinbart ? `
Werkstatt am ${dedat(new Date(t.vereinbart))}` : ""} -
-
- ${iv.hersteller ? `Herstellervorgabe` : ""} - ${hatMeldung - ? `${fm.km != null ? de(fm.km) + " km" : ""}${fm.km != null && fmDatum ? " / " : ""}${fmDatum || ""}` - : (eigenOel ? de(eigenOel.restKm) + " km · " + dedat(eigenOel.datum) : (anzeige.km !== null ? de(anzeige.km) + " km · " : "") + dedat(anzeige.datum))} -
- ${fussnote ? `${fussnote}` : ""} + ${termine().map((t) => { + const artIcon = ART_ICON[t.art]; + const iconMarkup = artIcon ? ciSVG(artIcon, ART_ICON_GROESSE[t.art]) : ""; + const kopf = `
${iconMarkup}${t.art}
`; + const iv = intervalle(t); + if (!iv) return `
+
${kopf}
+
kein Eintrag im Servicebuch
+
`; + const anzeige = iv.hersteller || iv.eigen; + const fm = fahrzeugMeldungRoh(t.art); + const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null; + // Forecast-Zeile aus dem kundeneigenen Intervall in "Einrichten" mit + // eigenständiger Reset-nach-Service-Logik (servicePrognose()): beim + // Ölwechsel mit Restkilometern, bei der Inspektion auf Wunsch nur mit + // dem Datum. Hauptuntersuchung bleibt bewusst ohne. + const eigenOel = t.art === "Ölwechsel" ? oelwechselPrognose() : null; + const eigenInsp = t.art === "Inspektion" ? inspektionPrognose() : null; + const hatMeldung = fm.km != null || fmDatum; + // Bei Herstellervorgabe rechnet die eigene Prognose ohnehin schon mit dem + // Herstellerintervall - die Restkilometerzahl daneben waere nur eine + // zweite, verwirrende km-Angabe neben dem Fahrzeug-Wert oben. Nur beim + // eigenen (kuerzeren) Intervall ist sie eine echte Zusatzinformation. + const fussnote = hatMeldung && eigenOel + ? (CAR.oel.modus === "hersteller" + ? `voraussichtlich am ${dedat(eigenOel.datum)}` + : `${de(eigenOel.restKm)} km, voraussichtlich am ${dedat(eigenOel.datum)}`) + : eigenInsp ? `voraussichtlich am ${dedat(eigenInsp.datum)}` : ""; + const wert = hatMeldung + ? `${fm.km != null ? de(fm.km) + " km" : ""}${fm.km != null && fmDatum ? " / " : ""}${fmDatum || ""}` + : (eigenOel ? de(eigenOel.restKm) + " km · " + dedat(eigenOel.datum) : (anzeige.km !== null ? de(anzeige.km) + " km · " : "") + dedat(anzeige.datum)); + // Eine Fahrzeugmeldung stammt immer aus dem werkseigenen Wartungsprogramm + // des Bordcomputers - das kennt das eigene, in "Einrichten" gewaehlte + // Intervall gar nicht. Deshalb zaehlt sie unabhaengig von iv.hersteller + // als Herstellervorgabe; nur der App-eigene Eigen-Intervall-Fallback + // (anzeige.eigen/eigenOel bei modus "eigen") ist es nicht. + const herstellervorgabe = hatMeldung || iv.hersteller; + return `
+
+ ${kopf} + ${t.vereinbart ? `Werkstatt am ${dedat(new Date(t.vereinbart))}` : ""} +
+ ${herstellervorgabe ? `
Herstellervorgabe
` : ""} +
${wert}
+ ${fussnote ? `
${fussnote}
` : ""}
`; }).join("")} -
Termin vereinbaren ${(() => { const ts = termine(), t = ts[Math.min(svAuswahl, ts.length - 1)]; @@ -2058,7 +2088,7 @@ function vEinst() { Ölwechsel-Intervall
- +
${CAR.oel.modus === "hersteller" ? `
diff --git a/homeassistant/www/audi-dashboard-version.json b/homeassistant/www/audi-dashboard-version.json index dafb220..def37c1 100644 --- a/homeassistant/www/audi-dashboard-version.json +++ b/homeassistant/www/audi-dashboard-version.json @@ -1 +1 @@ -{"version": 1787007000} +{"version": 1787013003} diff --git a/homeassistant/www/audi-dashboard.css b/homeassistant/www/audi-dashboard.css index 2afff66..3574918 100644 --- a/homeassistant/www/audi-dashboard.css +++ b/homeassistant/www/audi-dashboard.css @@ -482,6 +482,9 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } .serviceblock:active { opacity: .55; } /* Beschriftung nie unter dem Chevron hindurchlaufen lassen */ .serviceblock > .label:first-child { padding-right: 24px; } +/* Serviceart-Icon links der Kennzahl statt des Wortes "bis zum/zur ...". */ +.fig-zeile { display: flex; align-items: center; gap: 8px; margin-top: 8px; } +.fig-zeile svg { color: var(--fg2); flex: 0 0 auto; } .servicezeile { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); @@ -489,6 +492,35 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } margin-top: 14px; padding-top: 13px; } +/* Anstehende Termine (vService): drei sichtbar getrennte Bloecke statt einer + dt/dd-Liste - Apple HIG ("essential information... don't obscure it by + crowding it with nonessential details") plus Audi-CI (Flaeche mit Haarlinien + statt Karten, eigene Groessenklasse fuer Zahlen). Trennung ueber Abstand und + eine Haarlinie zwischen den Bloecken, nicht ueber verschachtelte Kacheln. + Jeder Block: Kopfzeile (Icon + Art-Name, optional ein Chip fuer einen + vereinbarten Werkstatttermin), optionale Herstellervorgabe-Zeile, der Wert, + optional die eigene Prognose als Fusszeile. */ +.termingruppe { padding: 18px 0; border-top: 1px solid var(--line); } +.termingruppe:first-of-type { padding-top: 2px; border-top: 0; } +.tg-kopf { display: flex; align-items: center; justify-content: space-between; gap: 10px; } +.tg-name { display: flex; align-items: center; gap: 9px; min-width: 0; } +.tg-name svg { color: var(--fg2); flex: 0 0 auto; } +.tg-name span { font-size: 16.5px; color: var(--fg); font-weight: 400; } +/* Chip statt rotem Fliesstext fuer einen vereinbarten Werkstatttermin - Rot + bleibt Akzent (Text/Rahmen), nie Flaeche, wie im Rest der App. */ +.tg-chip { + font-size: 10.5px; color: var(--red); + border: 1px solid var(--red); border-radius: var(--r-pill); + padding: 3px 8px; white-space: nowrap; flex: 0 0 auto; +} +.tg-vorgabe { font-size: 11.5px; color: var(--fg2); margin-top: 8px; } +.tg-wert { + font-family: "Audi Type Wide", var(--font-stack); font-weight: 300; + font-size: 22px; color: var(--fg); letter-spacing: -.005em; margin-top: 4px; +} +.tg-wert.leer { font-family: var(--font-stack); font-weight: 400; font-size: 15px; color: var(--fg3); margin-top: 8px; } +.tg-prognose { font-size: 12px; color: var(--fg2); margin-top: 6px; } + .badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .badge img { height: 18px; width: auto; display: none; } :host([data-theme=nacht]) .badge img.negative { display: block; }