Parity-Regel: Uebersicht-Service-Block in companion-app portiert

Nutzer stellte klar: Panel und companion-app sollen immer denselben
funktionalen Stand haben, auch wenn eine Anfrage nur das Panel nennt -
das ist reiner Testkomfort (Docker-Instanz laesst sich am schnellsten
ansehen), keine Scope-Entscheidung. Regel als verbindlicher Absatz in
AGENTS.md verankert, direkt unter der bestehenden Maintenance-Regel.

Erste Anwendung im selben Zug: die eben im Panel gebaute
"Naechster Service"-Kachel nach companion-app/ portiert.

- daten/service.ts: neues naechsterService() als Gegenstueck zu
  naechsterTermin() im Panel - waehlt ueber Oelwechsel-/Inspektions-
  Prognose und die von Hand gepflegte Hauptuntersuchung hinweg den
  zeitlich naechsten Termin. bisText() liefert denselben Artikel wie
  ART_BIS im Panel ("bis zum Oelwechsel" / "bis zur Inspektion").
- screens/Uebersicht.tsx: die bisherigen Kacheln "Reichweite"/
  "Kilometerstand" nebeneinander plus eine separate "Service"-Kachel
  mit Werteliste weichen einer Reichweiten-Kachel plus einer
  Service-Kachel mit .dm-serviceblock (Knopf, nur der obere Teil) und
  .dm-servicezeile (reine Anzeige) darunter - dieselbe Control/Content-
  Trennung wie im Panel.
- stile/screens.css: .dm-serviceblock/.dm-servicezeile ergaenzt.

Tests: 4 neue in service.test.ts (naechsterService waehlt das frueher
faellige Datum, nimmt die Hauptuntersuchung auf, liefert nichts ohne
Servicebuch/HU, bisText-Artikel je Art), 1 neuer in screens.test.tsx,
der mit einem vi.fn() als geheZu wirklich belegt, dass ein Klick auf
den Serviceblock navigiert und ein Klick auf die Kilometerstand-Zeile
es nicht tut - dafuer bekam zeige() in screens.test.tsx erst einen
injizierbaren geheZu-Parameter (vorher hart auf () => {} verdrahtet).

npm run typecheck sauber, npm run test 100/100 (von 95), npm run build
erfolgreich.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-17 20:44:31 +02:00
co-authored by Claude Opus 5
parent 600c92fd0f
commit 2848b5286f
6 changed files with 256 additions and 62 deletions
+38 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"
import { inspektionPrognose, letzterOelwechsel, oelwechselPrognose } from "./service"
import { bisText, inspektionPrognose, letzterOelwechsel, naechsterService, oelwechselPrognose } from "./service"
import type { Fahrzeug } from "./profilAdapter"
function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug {
@@ -112,3 +112,40 @@ describe("inspektionPrognose", () => {
expect(inspektionPrognose(fahrzeug(46000), [], jetzt)).toBeNull()
})
})
describe("naechsterService", () => {
const jetzt = new Date("2026-08-11T12:00:00")
it("wählt über Ölwechsel und Inspektion hinweg das zeitlich frühere Datum", () => {
// Beide unabhängig mit derselben Formel berechnet, statt ein erwartetes
// Datum von Hand vorzurechnen - so bleibt der Test robust gegen spätere
// Änderungen an den Intervallen, prüft aber trotzdem die Auswahllogik.
const oel = oelwechselPrognose(fahrzeug(46000), BUCH, jetzt)
const insp = inspektionPrognose(fahrzeug(46000), BUCH, jetzt)
const erwarteteArt = oel!.datum <= insp!.datum ? "Ölwechsel" : "Inspektion"
const s = naechsterService(fahrzeug(46000), BUCH, jetzt)
expect(s).not.toBeNull()
expect(s!.art).toBe(erwarteteArt)
expect(s!.datum).toBe(erwarteteArt === "Ölwechsel" ? oel!.datum : insp!.datum)
})
it("nimmt die Hauptuntersuchung auf, obwohl sie keine eigene Prognose hat", () => {
const f = { ...fahrzeug(46000), hu: "2026-09-01" }
const s = naechsterService(f, [], jetzt)
expect(s).not.toBeNull()
expect(s!.art).toBe("Hauptuntersuchung")
expect(s!.restKm).toBeNull()
expect(s!.datum).toBe("2026-09-01")
})
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")
})
})