From 27196f9e7870322910582037cc3e4277f382e7c7 Mon Sep 17 00:00:00 2001 From: Tobi G Date: Mon, 7 Sep 2026 13:39:52 +0200 Subject: [PATCH] Ueberschrittene Servicetermine werden markiert Wunsch des Eigentuemers: faellige oder ueberschrittene Termine dezent gelb/orange hinterlegen und mit "ueberschritten" markieren - Service-Seite und Mein Audi; auf der Uebersicht zusaetzlich die Kennzahl im Warnton. Erkannt wird am DATUM, nicht an den Restkilometern. Gemessen: die Sensoren melden -28600 bzw. -22000 bei Faelligkeit 2028 bzw. 2027, negativ heisst hier also "noch so weit hin". Welches Vorzeichen ein tatsaechlich ueberschrittener Wert traegt, ist an den vorliegenden Daten nicht beobachtbar - geraten wird es nicht. Der km-Fall faellt ohnehin unter die Datumsregel: servicePrognose() setzt datum auf heute, sobald restKm auf 0 laeuft. Der Faelligkeitstag zaehlt mit ("bereits faellig bzw. ueberschritten"). Dabei eine Luecke im Panel gefunden, vom Test und nicht im Betrieb: datumAusText() ist verankert und nimmt keine vollen Zeitstempel - die Fahrzeugmeldung ist aber genau das und bei Oelwechsel und Inspektion die Hauptquelle. Sie waere still nie als ueberschritten erkannt worden. zeitstempelAlsDatum() schliesst sie; die App hatte die Luecke nie. Farbe nach Rolle geteilt wie --bad/--bad-flaeche: --warn ist die Flaeche, --warn-text die Schrift. Grund: #FF9F0A als Text auf Weiss sind 2.06:1. Auf dem 12% hinterlegten Grund misst --warn-text 4.90:1 (Tag, #A15800) bzw. 9.30:1 (Nacht). Geprueft: 15 Faelle gegen die echten Panel-Funktionen, 7 in der App, 14 Paritaetsfaelle Panel gegen App, 3 Rendertests ueber alle drei Anzeigestellen. Gegenprobe: mit ausgehaengter Entscheidung fallen 9 Tests. 327/327, tsc sauber, Panel als Modul geparst, Manifest und Buendel auf 2026.9.6.6, Container sauber gestartet. Offen: optisch nicht gesehen - der Testbestand enthaelt keinen ueberschrittenen Termin. In OFFEN.md vermerkt. --- AGENTS.md | 97 +- OFFEN.md | 11 + .../src/daten/paritaet_ueberschritten.test.ts | 70 + companion-app/src/daten/service.test.ts | 59 +- companion-app/src/daten/service.ts | 36 + companion-app/src/screens/MeinAudi.tsx | 19 +- companion-app/src/screens/Service.tsx | 27 +- companion-app/src/screens/Uebersicht.tsx | 18 +- companion-app/src/screens/bausteine.tsx | 7 + companion-app/src/screens/screens.test.tsx | 47 +- companion-app/src/stile/screens.css | 4774 +++++++++-------- companion-app/src/tests/beispieldaten.ts | 7 +- .../audi_dashboard/frontend/app/bundle.json | 2 +- .../audi_dashboard/frontend/app/bundle.zip | Bin 378720 -> 379100 bytes .../frontend/audi-dashboard-app.js | 73 +- .../frontend/audi-dashboard-ios.css | 4 +- .../frontend/audi-dashboard.css | 28 + .../audi_dashboard/manifest.json | 2 +- design-system/src/tokens/tokens.css | 2 + 19 files changed, 2874 insertions(+), 2409 deletions(-) create mode 100644 companion-app/src/daten/paritaet_ueberschritten.test.ts diff --git a/AGENTS.md b/AGENTS.md index fc4cdce..8901221 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-09-06** (Die Batteriehistorie zeichnete stehende Werte als frische Messungen auf - der Vorschlag aus CB war messbar falsch, Abschnitt DI; davor: die ausgelieferte .ipa registriert zwei eigene Plugins nicht, Abschnitt DH). Davor: 2026-09-05, Audit-Durchgang, Abschnitt DC. (Tankstelle zweizeilig und antippbar, die Belegkarte sagt bei +**Last updated: 2026-09-07** (Überschrittene Servicetermine werden markiert, Abschnitt DJ). Davor: 2026-09-06 (Die Batteriehistorie zeichnete stehende Werte als frische Messungen auf - der Vorschlag aus CB war messbar falsch, Abschnitt DI; davor: die ausgelieferte .ipa registriert zwei eigene Plugins nicht, Abschnitt DH). Davor: 2026-09-05, Audit-Durchgang, Abschnitt DC. (Tankstelle zweizeilig und antippbar, die Belegkarte sagt bei fehlender Position die Wahrheit statt einen toten Knopf zu zeigen, der Regler bekommt einen Speichern-Knopf, und die Share-Erweiterung trug eine andere Versionsnummer als die App; dazu die Kopfmarke statt der Firmierung des Betreibers und ein @@ -12708,3 +12708,98 @@ Daten des Eigentümers, und welche davon bleiben, entscheidet er. Sollte er später mehr als tageweises Löschen brauchen (mehrere Tage auf einmal, oder eine Kennzeichnung verdächtiger Einträge), ist das ein eigener Auftrag - heute ist nichts davon angefragt. + +## DJ. Überschrittene Servicetermine werden markiert (2026.9.6.6) + +Wunsch des Eigentümers: fällige oder überschrittene Servicetermine hervorheben - +dezent gelb/orange transparent hinterlegt und mit „überschritten" markiert, auf +der Service-Seite und in „Mein Audi"; auf der Übersicht zusätzlich mit der +Kennzahl selbst im Warnton. + +### Woran „überschritten" erkannt wird - und woran nicht + +**Am Datum, nicht an den Restkilometern.** Das ist keine Bequemlichkeit: die +Kilometerseite trägt je nach Quelle ein anderes Vorzeichen. Am 07.09.2026 an den +echten Sensoren gemessen: + +| Sensor | Wert | Fälligkeit laut Datum | +|---|---|---| +| `oil_change_distance` | **-28600** | 2028-07-23 | +| `inspection_distance` | **-22000** | 2027-10-21 | + +Negativ heißt hier also „noch so weit hin", nicht „überschritten" - `Math.abs()` +in `meldungsPrognose()` ist damit richtig. Welches Vorzeichen ein *tatsächlich* +überschrittener Fahrzeugwert trägt, ist an den vorliegenden Daten **nicht +beobachtbar**; geraten wird es nicht. Dazu klemmt `servicePrognose()` ihre +Restkilometer ohnehin mit `Math.max(0, ...)`, die Information wäre dort also +auch verworfen. + +Das Datum ist eindeutig, und der km-Fall fällt darunter: `servicePrognose()` +setzt `datum` auf **heute**, sobald `restKm` auf 0 läuft. Die Regel lautet +deshalb: **überschritten, sobald das Datum nicht mehr in der Zukunft liegt** - +der Fälligkeitstag selbst zählt mit, denn gefragt war „bereits fällig bzw. +überschritten". + +Genommen wird das **erste lesbare** der übergebenen Daten - also das, das an +dieser Stelle auch angezeigt wird. So kann die Markierung nicht von dem +abweichen, was daneben steht. + +### Eine Lücke im Panel, die der Test gefangen hat + +`datumAusText()` im Panel ist verankert (`^…$`) und nimmt **keine** vollen +Zeitstempel. Die Fahrzeugmeldung (`fm.ts`) ist aber genau das - und bei Ölwechsel +und Inspektion die Hauptquelle. Ohne Korrektur wäre sie stillschweigend nie als +überschritten erkannt worden. Aufgefallen ist das im isolierten Test gegen die +echten Panel-Funktionen, nicht im Betrieb; `zeitstempelAlsDatum()` schließt sie, +bewusst eng gefasst (`/^\d{4}-\d{2}-\d{2}T/`) statt mit einem allgemeinen +`new Date()`, das auch deutete, was `datumAusText()` gerade abgelehnt hat. + +Die App hatte diese Lücke nicht: ihr `ISO`-Muster akzeptiert `[T ]` seit jeher. +Damit sind beide Datumsleser jetzt gleich weit. + +### Farbe: --warn ist die Fläche, --warn-text die Schrift + +Gemessen statt gewählt. `--warn` ist im Tagmodus `#FF9F0A` - als **Text** auf +Weiß sind das **2,06:1**, weit unter der 4,5:1-Grenze, die dieses Projekt sich +selbst gesetzt hat (siehe Abschnitt CP, wo `--fg3` aus demselben Grund +abgedunkelt wurde). Deshalb dieselbe Rollenteilung wie bei `--bad` / +`--bad-flaeche`: + +| | Fläche (12 % hinterlegt) | Schrift `--warn-text` | Kontrast auf dem hinterlegten Grund | +|---|---|---|---| +| Tag | `#FF9F0A` → Grund `#FFF3E2` | **`#A15800`** | **4,90:1** | +| Nacht | `#FFD60A` → Grund `#33311E` | `#FFD60A` | **9,30:1** | + +Nachts trägt `--warn` selbst genug Kontrast, tagsüber nicht. `#B36200` wäre +näher am reinen Orange, fällt auf dem hinterlegten Grund aber auf 4,11:1 und +damit unter die Grenze - deshalb `#A15800`. + +### Geprüft + +* **15 Fälle** gegen die echten Panel-Funktionen (aus der Panel-Quelle + geschnitten, nicht nachgebaut), darunter beide Zeitstempel-Richtungen und die + Tagesgrenze. +* **7 Fälle** in `service.test.ts` für die App; gegengeprüft, dass die + Tagesgrenze wirklich gemessen wird (`<=` auf `<` gedreht → genau der + „heute zählt mit"-Test fällt). +* **14 Paritätsfälle** in `paritaet_ueberschritten.test.ts`: Panel und App + urteilen über dieselben Eingaben gleich, geprüft gegen die aus der Panel-Quelle + geschnittene Funktion. +* **3 Rendertests** über alle drei Anzeigestellen, mit einem Profil, dessen + Hauptuntersuchung in der Vergangenheit liegt. Der Regelbestand der + Beispieldaten taugte dafür nicht (Wartungsplan 2026-02-10 + 24 Monate); + `beispielApi()` nimmt dafür jetzt ein abweichendes Profil entgegen - additiv, + alle bisherigen Aufrufer unverändert. +* Gegenprobe über alles: mit ausgehängter Entscheidung (`terminUeberschritten` + liefert immer `false`) fallen **9 Tests**, darunter alle drei Rendertests. +* 327/327 App-Tests, `tsc` sauber, beide Panel-Dateien als **Modul** geparst, + Manifest und Bündel gleichauf auf `2026.9.6.6`, Testcontainer sauber gestartet. + +### Was offen bleibt + +**Optisch nicht gesehen.** Die Markierung ist über Rendertests und gemessene +Kontraste belegt, nicht über ein Bild - der Testbestand enthält keinen +überschrittenen Termin (Ölwechsel 2028, Inspektion 2027, HU 2028), und für einen +Blick ins laufende Panel fehlte die angemeldete Sitzung. Nach der Regel dieses +Projekts (Abschnitt S: ein sauberer Testlauf beweist kein Layout) steht das +ausdrücklich als offen, nicht als erledigt. diff --git a/OFFEN.md b/OFFEN.md index 14ac83e..02a8a10 100644 --- a/OFFEN.md +++ b/OFFEN.md @@ -98,6 +98,17 @@ Beispielzahlen in der Statistik und ein überholter TODO-Kommentar. --- +## 3b. Optisch noch nicht gesehen + +**Die Markierung überschrittener Servicetermine** (`2026.9.6.6`, Abschnitt DJ) +ist über Rendertests und gemessene Kontraste belegt, aber nie am Bild geprüft - +der Testbestand hat keinen überschrittenen Termin, und für einen Blick ins +laufende Panel fehlte die angemeldete Sitzung. + +*Was zu tun ist:* einmal ansehen, sobald ein Termin wirklich fällig ist - oder +mit einer angemeldeten Sitzung und einem vorübergehend zurückdatierten +Wartungsplan-Eintrag (hinterher zurücksetzen). + ## 4. Gemeldet, aber nicht reproduzierbar Beide mehrfach gesucht und **nicht gefunden**. Sie bleiben stehen, bis sie diff --git a/companion-app/src/daten/paritaet_ueberschritten.test.ts b/companion-app/src/daten/paritaet_ueberschritten.test.ts new file mode 100644 index 0000000..af287ff --- /dev/null +++ b/companion-app/src/daten/paritaet_ueberschritten.test.ts @@ -0,0 +1,70 @@ +import { readFileSync } from "node:fs" +import { describe, expect, it } from "vitest" +import { terminUeberschritten } from "./service" + +/* Beide Codebasen müssen denselben Termin gleich beurteilen - sonst steht auf + der Übersicht "überschritten" und auf derselben Seite im Panel nicht. Geprüft + wird gegen die ECHTE Panel-Funktion, aus der Panel-Quelle herausgeschnitten, + nicht gegen einen Nachbau. Muster wie in paritaet_hu.test.ts. */ +const quelle = readFileSync( + "../custom_components/audi_dashboard/frontend/audi-dashboard-app.js", "utf8") +const block = (kopf: string) => { + const i = quelle.indexOf(kopf) + if (i < 0) throw new Error("fehlt: " + kopf) + let tiefe = 0 + for (let k = quelle.indexOf("{", i); k < quelle.length; k++) { + if (quelle[k] === "{") tiefe++ + else if (quelle[k] === "}" && !--tiefe) return quelle.slice(i, k + 1) + } + throw new Error("unvollstaendig: " + kopf) +} +const zeile = (muster: RegExp) => { + const m = muster.exec(quelle) + if (!m) throw new Error("fehlt: " + muster) + return m[0] +} + +const panel = new Function(` + ${zeile(/^const DATUM_ISO = .*$/m)} + ${zeile(/^const DATUM_TAG = .*$/m)} + ${zeile(/^const DATUM_MONAT = .*$/m)} + ${zeile(/^const DATUM_ISO_MONAT = .*$/m)} + ${zeile(/^const DATUM_ZEITSTEMPEL = .*$/m)} + ${zeile(/^const gueltigesDatum = .*$/m)} + ${block("function datumBauen")} + ${block("function datumAusText")} + ${block("function zeitstempelAlsDatum")} + ${block("function terminUeberschritten")} + return { terminUeberschritten }; +`)() as { terminUeberschritten: (...k: (string | Date | null)[]) => boolean } + +const tag = (versatz: number) => { + const x = new Date() + x.setDate(x.getDate() + versatz) + return `${String(x.getDate()).padStart(2, "0")}.${String(x.getMonth() + 1).padStart(2, "0")}.${x.getFullYear()}` +} + +describe("terminUeberschritten: Panel und App urteilen gleich", () => { + const faelle: { name: string; args: (string | null)[] }[] = [ + { name: "gestern", args: [tag(-1)] }, + { name: "heute", args: [tag(0)] }, + { name: "morgen", args: [tag(1)] }, + { name: "ISO vorbei", args: ["2024-03-01"] }, + { name: "ISO Zukunft", args: ["2028-07-23"] }, + { name: "Fahrzeug-Zeitstempel vorbei", args: ["2025-10-21T00:00:00+00:00"] }, + { name: "Fahrzeug-Zeitstempel Zukunft", args: ["2027-10-21T00:00:00+00:00"] }, + { name: "monatsgenau vorbei", args: ["03/2025"] }, + { name: "monatsgenau Zukunft", args: ["03/2099"] }, + { name: "kein Datum", args: [null] }, + { name: "leer", args: [""] }, + { name: "unlesbar", args: ["irgendwas"] }, + { name: "erstes lesbares: null, dann gestern", args: [null, tag(-1)] }, + { name: "erstes lesbares: morgen vor gestern", args: [tag(1), tag(-1)] }, + ] + + for (const f of faelle) { + it(f.name, () => { + expect(terminUeberschritten(...f.args)).toBe(panel.terminUeberschritten(...f.args)) + }) + } +}) diff --git a/companion-app/src/daten/service.test.ts b/companion-app/src/daten/service.test.ts index 3db62cc..b1b70ae 100644 --- a/companion-app/src/daten/service.test.ts +++ b/companion-app/src/daten/service.test.ts @@ -1,16 +1,6 @@ import { describe, expect, it } from "vitest" -import { - hauptuntersuchungFaellig, - inspektionPrognose, - kmProTagAusFahrten, - letzterOelwechsel, - meldungsPrognose, - meldungsPrognoseEigenesIntervall, - naechsterService, - oelMeldungsPrognose, - oelwechselPrognose, -} from "./service" +import { hauptuntersuchungFaellig, inspektionPrognose, kmProTagAusFahrten, letzterOelwechsel, meldungsPrognose, meldungsPrognoseEigenesIntervall, naechsterService, oelMeldungsPrognose, oelwechselPrognose, terminUeberschritten } from "./service" import type { Fahrzeug } from "./profilAdapter" function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug { @@ -400,3 +390,50 @@ describe("Hauptuntersuchung", () => { expect(hauptuntersuchungFaellig([], "keine Ahnung")).toBeNull() }) }) + + +describe("terminUeberschritten", () => { + const tag = (versatz: number) => { + const x = new Date() + x.setDate(x.getDate() + versatz) + return `${String(x.getDate()).padStart(2, "0")}.${String(x.getMonth() + 1).padStart(2, "0")}.${x.getFullYear()}` + } + + it("gestern ist überschritten", () => { + expect(terminUeberschritten(tag(-1))).toBe(true) + }) + + it("heute zählt mit - 'bereits fällig' ist gemeint", () => { + expect(terminUeberschritten(tag(0))).toBe(true) + }) + + it("morgen nicht", () => { + expect(terminUeberschritten(tag(1))).toBe(false) + }) + + it("erkennt den vollen Zeitstempel des Fahrzeugs", () => { + // Genau die Form, die oelwechselFaelligTs/inspektionFaelligTs tragen. Im + // Panel war das die Lücke: dessen Datumsleser ist verankert und nahm sie + // nicht an, die Fahrzeugmeldung wäre dort still nie überschritten gewesen. + expect(terminUeberschritten("2025-10-21T00:00:00+00:00")).toBe(true) + expect(terminUeberschritten("2027-10-21T00:00:00+00:00")).toBe(false) + }) + + it("versteht monatsgenaue Angaben (Hauptuntersuchung)", () => { + expect(terminUeberschritten("03/2025")).toBe(true) + expect(terminUeberschritten("03/2099")).toBe(false) + }) + + it("ohne lesbares Datum wird nichts behauptet", () => { + expect(terminUeberschritten(null)).toBe(false) + expect(terminUeberschritten("")).toBe(false) + expect(terminUeberschritten("irgendwas")).toBe(false) + expect(terminUeberschritten(undefined)).toBe(false) + }) + + it("nimmt das erste LESBARE Datum, nicht das erste beliebige", () => { + // So kann die Markierung nicht von dem abweichen, was daneben steht. + expect(terminUeberschritten(null, tag(-1))).toBe(true) + expect(terminUeberschritten(tag(1), tag(-1))).toBe(false) + }) +}) diff --git a/companion-app/src/daten/service.ts b/companion-app/src/daten/service.ts index c49fb3c..ce59e60 100644 --- a/companion-app/src/daten/service.ts +++ b/companion-app/src/daten/service.ts @@ -401,3 +401,39 @@ export function hauptuntersuchungFaellig( const ziel = monatePlus(isoTag(z.zeit), HU_MONATE_ERSTE) return z.genau === "monat" ? ziel.slice(0, 7) : ziel } + +/** Ist dieser Servicetermin bereits fällig oder überschritten? + * + * Der Fälligkeitstag selbst zählt mit („bereits fällig bzw. überschritten", + * Ansage des Eigentümers 07.09.2026). + * + * Entschieden wird am DATUM, nicht an den Restkilometern - und das ist keine + * Bequemlichkeit. Die Kilometerseite trägt je nach Quelle ein anderes + * Vorzeichen: die Sensoren des Fahrzeugs melden negativ, solange noch Strecke + * bleibt (am 07.09.2026 gemessen: oil_change_distance -28600 bei Fälligkeit + * 2028), und servicePrognose() klemmt ihre Restkilometer ohnehin mit + * Math.max(0, ...). Welches Vorzeichen ein überschrittener Fahrzeugwert trägt, + * lässt sich an den vorliegenden Daten nicht beobachten - geraten wird es + * nicht. Der km-Fall fällt ohnehin unter die Datumsregel: servicePrognose() + * setzt datum auf heute, sobald restKm auf 0 läuft. + * + * Genommen wird das ERSTE lesbare Datum der übergebenen Kandidaten - also das, + * das an dieser Stelle auch angezeigt wird. So kann die Markierung nicht von + * dem abweichen, was daneben steht. Wortgleich mit terminUeberschritten() im + * Panel. */ +export function terminUeberschritten( + ...kandidaten: (string | Date | null | undefined)[] +): boolean { + for (const kandidat of kandidaten) { + if (kandidat === null || kandidat === undefined || kandidat === "") continue + const z = alsZeitpunkt(kandidat) + if (!z) continue + const d = z.zeit + const h = new Date() + return ( + new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime() <= + new Date(h.getFullYear(), h.getMonth(), h.getDate()).getTime() + ) + } + return false +} diff --git a/companion-app/src/screens/MeinAudi.tsx b/companion-app/src/screens/MeinAudi.tsx index 5f061bb..f951580 100644 --- a/companion-app/src/screens/MeinAudi.tsx +++ b/companion-app/src/screens/MeinAudi.tsx @@ -18,10 +18,10 @@ import { useRef, useState, type PointerEvent as ReactPointerEvent } from "react" import { Tile } from "@audi-dash/ui" import { useDaten } from "../daten/DatenKontext" -import { hauptuntersuchungFaellig, inspektionPrognose, oelwechselPrognose } from "../daten/service" +import { hauptuntersuchungFaellig, inspektionPrognose, oelwechselPrognose, terminUeberschritten } from "../daten/service" import { datum, de, deOderStrich, eur } from "../format" import type { SeitenName } from "../navigation" -import { Wertzeile, Werteliste } from "./bausteine" +import { UeberschrittenMarke, Werteliste, Wertzeile } from "./bausteine" import { Bild } from "./Bild" import { GALERIE } from "./bilder" import type { ServicebuchEintrag } from "./Service" @@ -115,13 +115,14 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) = const huFaellig = hauptuntersuchungFaellig(buch, fahrzeug.erstzulassung) const oelProg = oelwechselPrognose(fahrzeug, buch) const inspProg = inspektionPrognose(fahrzeug, buch) - const termine: { art: string; prognose: string | null; haupt: string; neben: string | null }[] = [ + const termine: { art: string; prognose: string | null; haupt: string; neben: string | null; ueber: boolean }[] = [ { art: "Ölwechsel", prognose: oelProg ? `${de(oelProg.restKm)} km, voraussichtlich am ${datum(oelProg.datum)}` : null, haupt: fahrzeug.oelwechselFaelligTs ? datum(fahrzeug.oelwechselFaelligTs) : "–", + ueber: terminUeberschritten(fahrzeug.oelwechselFaelligTs, oelProg?.datum), neben: fahrzeug.oelwechselFaelligKm != null ? `in ${de(Math.abs(fahrzeug.oelwechselFaelligKm))} km` @@ -131,6 +132,7 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) = art: "Inspektion", prognose: inspProg ? `voraussichtlich am ${datum(inspProg.datum)}` : null, haupt: fahrzeug.inspektionFaelligTs ? datum(fahrzeug.inspektionFaelligTs) : "–", + ueber: terminUeberschritten(fahrzeug.inspektionFaelligTs, inspProg?.datum), neben: fahrzeug.inspektionFaelligKm != null ? `in ${de(Math.abs(fahrzeug.inspektionFaelligKm))} km` @@ -142,6 +144,7 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) = art: "Hauptuntersuchung", prognose: null, haupt: huFaellig ? datum(huFaellig) : "–", + ueber: terminUeberschritten(huFaellig), neben: null, }, ] @@ -270,7 +273,15 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) = )} } - wert={t.haupt} + wert={ + t.ueber ? ( + <> + {t.haupt} + + ) : ( + t.haupt + ) + } {...(t.neben ? { zusatz: t.neben } : {})} /> )) diff --git a/companion-app/src/screens/Service.tsx b/companion-app/src/screens/Service.tsx index 7888700..02624e2 100644 --- a/companion-app/src/screens/Service.tsx +++ b/companion-app/src/screens/Service.tsx @@ -14,15 +14,11 @@ import { ActionButton, Feld, Fig, Tile } from "@audi-dash/ui" import { kartendienstUrl } from "../daten/tankstelle" import { useDaten } from "../daten/DatenKontext" -import { hauptuntersuchungFaellig, inspektionPrognose, letzterInspektion, letzterOelwechsel, meldungsPrognose, oelMeldungsPrognose, oelwechselPrognose, serviceKandidaten } from "../daten/service" +import { hauptuntersuchungFaellig, inspektionPrognose, letzterInspektion, letzterOelwechsel, meldungsPrognose, oelMeldungsPrognose, oelwechselPrognose, serviceKandidaten, terminUeberschritten } from "../daten/service" import { datum, de, eur, isoTag } from "../format" import type { SeitenName } from "../navigation" import { SymbolInspektion, SymbolOelwechsel } from "../symbole" -import { - Blattzeile, - Leerzustand, - Zahnradknopf, -} from "./bausteine" +import { Blattzeile, Leerzustand, UeberschrittenMarke, Zahnradknopf } from "./bausteine" import { LOESCH_HINWEIS, LOESCH_TEXT, bestaetigen } from "./bestaetigung" import { Datumsfeld } from "./Datumsfeld" import { terminUebernehmen } from "./kalender" @@ -127,14 +123,25 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) => const huDatum = huFaellig ? new Date(huFaellig) : null const huGueltig = !!huDatum && !Number.isNaN(huDatum.getTime()) + // Dasselbe Datum, das gleich als Wert erscheint - siehe + // terminUeberschritten(). Die Fahrzeugmeldung geht vor, weil sie oben steht. + const oelUeber = terminUeberschritten( + oelHatMeldung ? fahrzeug.oelwechselFaelligTs : prognose?.datum, + ) + const inspUeber = terminUeberschritten( + inspHatMeldung ? fahrzeug.inspektionFaelligTs : inspektionsprognose?.datum, + ) + const huUeber = huGueltig && terminUeberschritten(huFaellig) + return ( <> -
+
Ölwechsel + {oelUeber && }
{oelHatEintrag || oelHatMeldung ? ( <> @@ -147,10 +154,11 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) => )}
-
+
Inspektion + {inspUeber && }
{inspHatEintrag || inspHatMeldung ? ( <> @@ -163,9 +171,10 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) => )}
-
+
Hauptuntersuchung + {huUeber && }
{huGueltig ? (
{datum(huFaellig)}
diff --git a/companion-app/src/screens/Uebersicht.tsx b/companion-app/src/screens/Uebersicht.tsx index a386ce9..878bf19 100644 --- a/companion-app/src/screens/Uebersicht.tsx +++ b/companion-app/src/screens/Uebersicht.tsx @@ -11,11 +11,11 @@ import { Fig, ProgressBar, StatusRow, Tile } from "@audi-dash/ui" import { useState } from "react" import { useDaten } from "../daten/DatenKontext" -import { naechsterService } from "../daten/service" +import { naechsterService, terminUeberschritten } from "../daten/service" import { datum, de, deOderStrich, eur, streckeText } from "../format" import type { SeitenName } from "../navigation" import { BILDPLAETZE, bildInfo } from "./bilder" -import { Blattzeile, Leerzustand } from "./bausteine" +import { Blattzeile, Leerzustand, UeberschrittenMarke } from "./bausteine" // Dieselbe Ortsangabe wie in der Fahrtenliste - eine Quelle, damit "Zuletzt" // und die Liste nicht auseinanderlaufen koennen. import { strecke } from "./Fahrten" @@ -81,6 +81,9 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string) const buch = (fahrzeug.service["buch"] ?? []) as ServicebuchEintrag[] const service = naechsterService(fahrzeug, buch, fahrten) + // Auf der Uebersicht wechselt zusaetzlich die Kennzahl die Farbe - sie ist + // hier die Aussage, nicht nur ihr Rahmen (Ansage des Eigentuemers). + const serviceUeber = !!service && terminUeberschritten(service.datum) const letzteFahrt = fahrten .slice() @@ -238,7 +241,11 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string) "differentiate controls from content" gilt auch für die Abwesenheit von Inhalt. */} {service ? ( - ${kmZeile} @@ -2610,15 +2660,17 @@ function vAudi() { const eigen = prognoseZeile(t.art); if (fm.ts == null && fm.km == null) { const x = terminText(t); + const ueber = terminUeberschritten(t.ziel, t.datum); return `
${t.art}${t.art === "Inspektion" ? eigen : ""}
-
${x.haupt}${x.neben ? `${x.neben}` : ""}
`; +
${x.haupt}${ueber ? UEBERSCHRITTEN_MARKE : ""}${x.neben ? `${x.neben}` : ""}
`; } const haupt = fm.ts ? dedat(new Date(fm.ts)) : ""; const neben = fm.km != null ? "in " + de(fm.km) + " km" : ""; + const ueber = terminUeberschritten(fm.ts, t.ziel, t.datum); return `
${t.art}${eigen}
-
${haupt}${neben ? `${neben}` : ""}
`; +
${haupt}${ueber ? UEBERSCHRITTEN_MARKE : ""}${neben ? `${neben}` : ""}
`; }).join("")}