Termine ohne Gedaechtnis, Listen nach HIG, zwei zu schmale Felder (2026.9.2.13-.20)
TERMIN. Der Werkstatttermin wanderte bisher ins Fahrzeugprofil, stand als
Merkmal an der Karte und liess sich "verwerfen" - eine zweite Verwaltung neben
dem Kalender. Auf Ansage entfernt: er dient jetzt allein dem Kalendereintrag
"was, wann, bei welcher Werkstatt". Dabei aufgefallen: die App schrieb gar kein
LOCATION in die .ics, obwohl das Panel es seit jeher setzt.
Ohne Speicherung startete das Feld leer ("tt.mm.jjjj", so gemeldet). Es
schlaegt jetzt die Faelligkeit der gewaehlten Art vor, sonst heute - dafuer
liefert service.ts serviceKandidaten(). "Andere" ergaenzt die Auswahl.
BREITEN. Am laufenden Panel gemessen: die vier festen 132px-Datumsfelder
brauchen ab 140px, damit die letzte Ziffer nicht unter dem Kalendersymbol
liegt (jetzt 150px); die Art-Auswahl braucht 178px fuer
"Hauptuntersuchung" (jetzt 190px). In der App war nichts davon kaputt - eine
dort vorsorglich eingebaute Breite ist wieder entfernt.
LISTEN. Fahrten- und Tankliste verloren 58px an tote Breite: 22px Einrueckung
der Monatsebene plus 40px linkes Polster der Zeile. Auf dem Telefon brach
"Eichstaett -> Adelschlag" deshalb um. Apples Richtlinien gruppieren ueber
Abschnittskoepfe statt Einrueckung; die Zeilen stehen jetzt alle auf derselben
Kante (34 statt 96px), der Monat traegt Versalien und Sperrung und liest sich
als Kopf. Beschraenkt auf .liste/.dm-liste - anderswo ist das Polster gewollt.
BILDER. Die Draufsicht ist aus der Galerie genommen (nurStatus), gehoert aber
weiter in die Auswahl der Einstellungen: nur dort laesst sie sich hochladen.
Die Galerie schneidet Fotos nicht mehr an - die Frontansicht verlor unten die
Stossstange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8187,3 +8187,100 @@ Statistik**: y 2,76 bis 21,24. Ränder nachgemessen, nicht nur die Größe — d
|
||||
Übersicht-Symbol vom selben Tag.
|
||||
|
||||
`fuel-station-l` (48er-Raster) wird nicht gebraucht: die Reiter zeichnen mit 21 bzw. 26px.
|
||||
|
||||
---
|
||||
|
||||
## BQ. Termine, Listen und was sich im laufenden Panel messen lässt (2026.9.2.13-.20)
|
||||
|
||||
Ein Stapel Meldungen des Eigentümers am Nachmittag des 02.09.2026. Der rote Faden: diesmal ließ
|
||||
sich das meiste **am laufenden Panel messen** statt zu rechnen — der Browser dieser Sitzung hatte
|
||||
die Testinstanz angemeldet offen, und über die Shadow-Roots von Home Assistant kommt man an jedes
|
||||
Element (`getBBox()`, `getComputedStyle()`, Bildschirmfotos). Das ist der Weg, der in diesem Projekt
|
||||
schon zweimal Größenfehler verhindert hätte; er steht hier, damit er nicht wieder vergessen wird.
|
||||
|
||||
### Der Werkstatttermin hatte ein Gedächtnis, das niemand wollte (.14)
|
||||
|
||||
Jedes eingetragene Datum wanderte ins Fahrzeugprofil (`service.vereinbart`), erschien als Merkmal
|
||||
an der Terminkarte und ließ sich über einen eigenen Knopf „verwerfen" — eine **zweite
|
||||
Terminverwaltung neben dem Kalender**, die auseinanderlief, sobald der Termin dort verschoben wurde.
|
||||
Auf Ansage entfernt: kein Schreiben mehr (weder im Feld noch beim Export), kein Merkmal, kein
|
||||
Verwerfen-Knopf. Der Termin hat jetzt genau einen Zweck — dem Kalender zu sagen, **was** wann und
|
||||
bei **welcher Werkstatt**.
|
||||
|
||||
Dabei ist ein echter Mangel aufgefallen: die App schrieb **gar kein `LOCATION`** in die .ics. Der
|
||||
Termin sagte „was" und „wann", aber nicht „wo", obwohl das Panel es seit jeher setzt. Jetzt tragen
|
||||
beide Autohausname als Ort und Anschrift als Beschreibung.
|
||||
|
||||
### Und der Vorschlag, den ich dabei kaputt gemacht habe (.18)
|
||||
|
||||
Ohne Speicherung startete das Datumsfeld **leer** — auf dem Telefon als „tt.mm.jjjj" sichtbar, und
|
||||
genau so gemeldet („Datums-Textfeld kaputt bei Hauptuntersuchung"; bei Ölwechsel und Inspektion
|
||||
stand noch ein gespeicherter Termin drin). Der Fehler war meiner, aus demselben Nachmittag.
|
||||
|
||||
Jetzt schlägt das Feld die **Fälligkeit der gewählten Art** vor — dieselbe Zahl wie auf den Karten
|
||||
darüber, derselbe Vorschlag wie im Panel; fehlt sie (Hauptuntersuchung ohne HU-Datum, „Andere"),
|
||||
steht heute im Feld. Leer wird es nie. Dafür liefert `service.ts` jetzt `serviceKandidaten()`, alle
|
||||
drei Arten mit ihrer Fälligkeit; `naechsterService()` nimmt davon weiterhin die früheste.
|
||||
|
||||
### Zwei Felder, die zu schmal waren — gemessen, nicht geschätzt (.17)
|
||||
|
||||
Im Panel standen vier Datumsfelder und die Art-Auswahl auf fest verdrahteten **132px**. Am
|
||||
laufenden Panel nachgemessen (Audi Type 16px):
|
||||
|
||||
| | braucht | stand auf | jetzt |
|
||||
|---|---|---|---|
|
||||
| „24.07.2027" + Kalendersymbol | ab 140px frei | 132px | **150px** |
|
||||
| „Hauptuntersuchung" + Chevron | 150,4 + 28 = 178px | 132px | **190px** |
|
||||
|
||||
Beim Datum lag die letzte Ziffer über dem Symbol (Probereihe 132/140/150/160/170/180 nebeneinander
|
||||
eingesetzt und angesehen), bei der Auswahl stand „Hauptuntersuchur". Beide Zeilen passen auch auf
|
||||
375px Telefonbreite.
|
||||
|
||||
**In der App war nichts davon kaputt** — dort sitzt kein festes Maß, und der Eigentümer hat am Gerät
|
||||
gegengeprüft. Eine vorsorglich eingebaute Breite dort ist wieder entfernt worden: eine Änderung ohne
|
||||
Befund ist keine Verbesserung.
|
||||
|
||||
### Die Fahrtenliste: 58px tote Breite (.19/.20)
|
||||
|
||||
Gemeldet mit Bildschirmfoto vom iPhone: „Eichstätt → Adelschlag" brach um. Gemessen, warum:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 22px | Einrückung der Monatsebene (`.lvl2`) |
|
||||
| 40px | linkes Polster jeder Zeile (`.leaf`/`.dm-blatt`) |
|
||||
|
||||
Zusammen 58px von rund 300px Kachelbreite, für einen Ortsnamen blieben ~150px.
|
||||
|
||||
**Apples Richtlinien gruppieren nicht über Einrückung, sondern über Abschnittsköpfe über die volle
|
||||
Breite**; die Zeilen stehen alle auf derselben Kante. Genau so jetzt — beschränkt auf `.liste` /
|
||||
`.dm-liste`, weil das linke Polster anderswo (Batteriewerte, Servicebuch, Zuletzt-Kachel) gewollt
|
||||
ist. Nachgemessen auf 375px: Fahrtzeile von 96px auf **34px** eingerückt, der Ortsname steht
|
||||
einzeilig.
|
||||
|
||||
Der Monat war danach 13px grau und die Zeile 16px schwarz — dazu die Frage des Eigentümers, ob das
|
||||
so gehöre. Es gehört so (iOS macht Abschnittsköpfe kleiner als den Inhalt), aber die Liste wirkte
|
||||
zweistufig. Der Monat trägt jetzt **11px, Versalien, 0,18em Sperrung** wie die übrigen Eyebrows —
|
||||
drei erkennbare Stufen, ohne dass der Kopf Platz kostet. 11 statt 10px: die Untergrenze dieses
|
||||
Projekts aus den Apple-Richtlinien.
|
||||
|
||||
### Kleinigkeiten aus demselben Stapel
|
||||
|
||||
- **„Andere"** in der Auswahl „Wofür" (.13). Im Panel etwas mehr als ein Listeneintrag: die Auswahl
|
||||
entsteht dort aus `termine()`, also aus **fälligen** Terminen mit Ziel und Kilometerstand.
|
||||
„Andere" ist keiner davon — dafür `terminAuswahl()`/`gewaehlterTermin()`, die beide Stellen
|
||||
bedienen, die vorher dieselbe Zeile doppelt hatten.
|
||||
- **Draufsicht nicht mehr in der Galerie** (.13): neues Kennzeichen `nurStatus` am Bildplatz. In der
|
||||
Auswahl der Einstellungen bleibt sie — sie ist der einzige Weg, das Foto hochzuladen.
|
||||
- **Die Galerie schneidet nicht mehr an** (.15): sie stand als letzte im festen 16:9-Kasten mit
|
||||
`object-fit: cover`, die Frontansicht verlor unten die Stoßstange. Jetzt bestimmt das Bild die
|
||||
Höhe, wie Übersicht-Szene und Einstellungs-Vorschau.
|
||||
- **Zusatz „vereinbart"** am Wechseltermin entfernt (.16) — sein Schalter war am selben Tag von der
|
||||
Räder-Seite verschwunden, das Feld blieb ungelesen im Profil zurück.
|
||||
|
||||
### Offen aus diesem Stapel
|
||||
|
||||
- **Teilen legt keinen Tankvorgang an** (gemeldet am Gerät). Noch nicht untersucht.
|
||||
- **Kopieren/Einfügen klappt am iPhone nicht.**
|
||||
- **Hochladen über einen eigenen HTTP-Endpunkt** statt über den Dienst: hebt die 3-MB-Grenze des
|
||||
Panels auf (WebSocket, aiohttp-Vorgabe 4 MiB) und spart die Base64-Aufblähung. Gemessen: über
|
||||
REST gehen 12 MB durch, über den WebSocket bricht die Verbindung ab 3,2 MB Rohbild wortlos ab.
|
||||
|
||||
@@ -275,6 +275,27 @@ export function naechsterService(
|
||||
fahrten: readonly Fahrtdaten[] = [],
|
||||
jetzt = new Date(),
|
||||
): NaechsterService | null {
|
||||
const kandidaten = serviceKandidaten(fahrzeug, buch, fahrten, jetzt)
|
||||
if (kandidaten.length === 0) return null
|
||||
// ISO-Tagesdaten, deshalb reicht der Zeichenkettenvergleich.
|
||||
return kandidaten.slice().sort((a, b) => a.datum.localeCompare(b.datum))[0] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle drei Arten mit ihrer voraussichtlichen Fälligkeit, ungeordnet.
|
||||
*
|
||||
* `naechsterService()` nimmt davon die früheste. Getrennt herausgezogen, weil
|
||||
* das Terminfeld auf der Serviceseite die Fälligkeit **je gewählter Art**
|
||||
* vorschlägt - so wie `termine()` im Panel, das dort dieselbe Liste liefert.
|
||||
* Eine Art ohne Grundlage (Hauptuntersuchung ohne gepflegtes HU-Datum) fehlt
|
||||
* in der Liste; der Aufrufer entscheidet, was er stattdessen anbietet.
|
||||
*/
|
||||
export function serviceKandidaten(
|
||||
fahrzeug: Fahrzeug,
|
||||
buch: readonly Buchhaltung[],
|
||||
fahrten: readonly Fahrtdaten[] = [],
|
||||
jetzt = new Date(),
|
||||
): NaechsterService[] {
|
||||
const kandidaten: NaechsterService[] = []
|
||||
|
||||
const oel = oelwechselPrognose(fahrzeug, buch, jetzt)
|
||||
@@ -303,7 +324,5 @@ export function naechsterService(
|
||||
})
|
||||
}
|
||||
|
||||
if (kandidaten.length === 0) return null
|
||||
// ISO-Tagesdaten, deshalb reicht der Zeichenkettenvergleich.
|
||||
return kandidaten.sort((a, b) => a.datum.localeCompare(b.datum))[0] ?? null
|
||||
return kandidaten
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function Fahrten({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
/>
|
||||
</Tile>
|
||||
) : (
|
||||
<Tile>
|
||||
<Tile className="dm-liste">
|
||||
{gruppen.map((jahr) => (
|
||||
<Accordion
|
||||
key={jahr.jahr}
|
||||
|
||||
@@ -23,7 +23,7 @@ import { datum, de, deOderStrich, eur } from "../format"
|
||||
import type { SeitenName } from "../navigation"
|
||||
import { Wertzeile, Werteliste } from "./bausteine"
|
||||
import { Bild } from "./Bild"
|
||||
import { BILDPLAETZE } from "./bilder"
|
||||
import { GALERIE } from "./bilder"
|
||||
import type { ServicebuchEintrag } from "./Service"
|
||||
import { Typenschild, schildErkennen } from "./Typenschild"
|
||||
|
||||
@@ -40,7 +40,7 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
|
||||
const aktiverSatz =
|
||||
fahrzeug.reifen.aktiv === "Winter" ? fahrzeug.reifen.winter : fahrzeug.reifen.sommer
|
||||
const platz = BILDPLAETZE[bildIndex % BILDPLAETZE.length]!
|
||||
const platz = GALERIE[bildIndex % GALERIE.length]!
|
||||
|
||||
const ausstattung = Array.isArray(fahrzeug.ausstattung)
|
||||
? (fahrzeug.ausstattung as AusstattungGruppe[])
|
||||
@@ -106,13 +106,13 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
<button
|
||||
type="button"
|
||||
className="dm-galerie-wechsel"
|
||||
onClick={() => setzeBildIndex((i) => (i + 1) % BILDPLAETZE.length)}
|
||||
onClick={() => setzeBildIndex((i) => (i + 1) % GALERIE.length)}
|
||||
aria-label="Nächstes Bild"
|
||||
>
|
||||
<Bild datei={platz.datei} beschreibung={platz.beschreibung} name={platz.beschreibung} />
|
||||
</button>
|
||||
<div className="dm-galerie-punkte" aria-hidden="true">
|
||||
{BILDPLAETZE.map((b, i) => (
|
||||
{GALERIE.map((b, i) => (
|
||||
<span key={b.datei} className={i === bildIndex ? "an" : ""} />
|
||||
))}
|
||||
</div>
|
||||
@@ -239,10 +239,15 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
<>
|
||||
<Wertzeile label="aktuell montiert:" wert={`${fahrzeug.reifen.aktiv}räder`} />
|
||||
<Wertzeile label="Laufleistung" wert={deOderStrich(aktiverSatz.km) + " km"} />
|
||||
{/* Ohne Zusatz "vereinbart": das Merkmal kam aus
|
||||
reifen.wechsel.vereinbart, dessen Schalter am 02.09.2026 von der
|
||||
Raeder-Seite entfernt wurde. Es liess sich seither nicht mehr
|
||||
setzen und stand nur noch an Altbestaenden - im Panel war die
|
||||
entsprechende Bedingung schon tot. Das Feld selbst bleibt im
|
||||
Profil, es wird nur nirgends mehr gelesen. */}
|
||||
<Wertzeile
|
||||
label="Wechseltermin"
|
||||
wert={datum(fahrzeug.reifen.wechsel["datum"] as string)}
|
||||
zusatz={fahrzeug.reifen.wechsel["vereinbart"] ? "vereinbart" : undefined}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
meldungsPrognose,
|
||||
oelMeldungsPrognose,
|
||||
oelwechselPrognose,
|
||||
serviceKandidaten,
|
||||
} from "../daten/service"
|
||||
import { datum, de, eur, isoTag } from "../format"
|
||||
import type { SeitenName } from "../navigation"
|
||||
@@ -248,47 +249,63 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
)
|
||||
}
|
||||
|
||||
/** `Termin vereinbaren` aus vService(): Art wählen, Datum setzen, ICS laden. */
|
||||
/**
|
||||
* `Termin vereinbaren` aus vService(): Art wählen, Datum setzen, ICS laden.
|
||||
*
|
||||
* DER TERMIN WIRD NICHT GESPEICHERT. Bis zum 02.09.2026 wanderte jedes
|
||||
* eingetragene Datum ins Fahrzeugprofil (`service.vereinbart`), erschien als
|
||||
* Merkmal an der Terminkarte und ließ sich über einen eigenen Knopf wieder
|
||||
* "verwerfen" - eine zweite Verwaltung neben dem Kalender, die niemand
|
||||
* gebeten hatte und die auseinanderlaufen konnte, sobald der Termin im
|
||||
* Kalender verschoben wurde.
|
||||
*
|
||||
* Auf Ansage des Eigentümers hat der Termin jetzt genau einen Zweck: dem
|
||||
* Kalender zu sagen, **was** wann und bei **welcher Werkstatt** ansteht. Das
|
||||
* Datum lebt deshalb nur, solange diese Ansicht offen ist.
|
||||
*/
|
||||
function TerminVereinbaren() {
|
||||
const { fahrzeug, profilSpeichern } = useDaten()
|
||||
const { fahrzeug, einstellungen, fahrten } = useDaten()
|
||||
const [art, setzeArt] = useState("Ölwechsel")
|
||||
const vereinbart = (fahrzeug?.service["vereinbart"] ?? {}) as Record<string, string>
|
||||
const [datumWert, setzeDatumWert] = useState(() => vereinbart[art] ?? "")
|
||||
/* null = "noch nichts von Hand geändert", dann gilt der Vorschlag unten.
|
||||
Sobald jemand ein Datum wählt, bleibt es stehen, auch wenn die Art
|
||||
wechselt - sonst würde die eigene Eingabe stillschweigend überschrieben. */
|
||||
const [eigenesDatum, setzeEigenesDatum] = useState<string | null>(null)
|
||||
|
||||
if (!fahrzeug) return null
|
||||
|
||||
const merken = async (neuesDatum: string) => {
|
||||
setzeDatumWert(neuesDatum)
|
||||
await profilSpeichern({
|
||||
fahrzeug: {
|
||||
...fahrzeug,
|
||||
service: { ...fahrzeug.service, vereinbart: { ...vereinbart, [art]: neuesDatum } },
|
||||
},
|
||||
})
|
||||
const autohaus = (fahrzeug.service["autohaus"] ?? {}) as {
|
||||
name?: string
|
||||
adresse?: string
|
||||
}
|
||||
|
||||
const verwerfen = async () => {
|
||||
setzeDatumWert("")
|
||||
const rest = { ...vereinbart }
|
||||
delete rest[art]
|
||||
await profilSpeichern({
|
||||
fahrzeug: { ...fahrzeug, service: { ...fahrzeug.service, vereinbart: rest } },
|
||||
})
|
||||
}
|
||||
/* Vorgeschlagen wird die Fälligkeit der gewählten Art - dieselbe Zahl, die
|
||||
die Karten oben zeigen, und derselbe Vorschlag wie im Panel. Fehlt sie
|
||||
(Hauptuntersuchung ohne gepflegtes HU-Datum, "Andere"), steht heute im
|
||||
Feld: ein leeres Datumsfeld sieht auf dem Telefon aus wie ein Fehler
|
||||
("tt.mm.jjjj"), und genau so wurde es gemeldet. */
|
||||
const buch = (fahrzeug.service["buch"] ?? []) as ServicebuchEintrag[]
|
||||
const kandidaten = serviceKandidaten(fahrzeug, buch, fahrten)
|
||||
const vorschlag = kandidaten.find((k) => k.art === art)?.datum ?? isoTag()
|
||||
const datumWert = eigenesDatum ?? vorschlag
|
||||
|
||||
return (
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Termin vereinbaren</span>
|
||||
<Feld label="Wofür">
|
||||
{/* Keine eigene Breite: "Hauptuntersuchung" passt hier auch in die 60 %
|
||||
aus .dm-auswahl - am Geraet nachgesehen (Eigentuemer, 02.09.2026).
|
||||
Abgeschnitten wurde es nur im Panel, wo das Feld auf festen 132px
|
||||
stand; dort jetzt 190px. */}
|
||||
<select
|
||||
className="dm-auswahl"
|
||||
value={art}
|
||||
onChange={(e) => {
|
||||
setzeArt(e.target.value)
|
||||
setzeDatumWert(vereinbart[e.target.value] ?? "")
|
||||
}}
|
||||
onChange={(e) => setzeArt(e.target.value)}
|
||||
>
|
||||
{["Ölwechsel", "Inspektion", "Hauptuntersuchung"].map((x) => (
|
||||
{/* "Andere" ist kein fälliger Termin, sondern ein freier Werkstattbesuch,
|
||||
der zu keiner der drei geplanten Arten gehört (Reifenwechsel,
|
||||
Garantiearbeit, Nachbesserung). Er hat deshalb kein Ziel und
|
||||
keine Kilometerangabe - nur ein Datum. */}
|
||||
{["Ölwechsel", "Inspektion", "Hauptuntersuchung", "Andere"].map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
@@ -296,14 +313,23 @@ function TerminVereinbaren() {
|
||||
</select>
|
||||
</Feld>
|
||||
<Feld label="Werkstatttermin am" last>
|
||||
<Datumsfeld wert={datumWert} beiAenderung={(w) => void merken(w)} />
|
||||
<Datumsfeld wert={datumWert} beiAenderung={setzeEigenesDatum} />
|
||||
</Feld>
|
||||
{/* Name des Autohauses als Ort, Anschrift als Beschreibung - wie im
|
||||
Panel (`ics()` dort). Fehlt das Autohaus, bleiben beide Felder leer
|
||||
und der Termin trägt nur "was" und "wann". */}
|
||||
<ActionButton
|
||||
onClick={() => void kalenderDateiLaden(art, datumWert || isoTag(), `${art} in der Werkstatt`)}
|
||||
onClick={() =>
|
||||
void kalenderDateiLaden(
|
||||
`${einstellungen?.fahrzeugtitel ?? "Fahrzeug"}: ${art}`,
|
||||
datumWert,
|
||||
autohaus.adresse ?? "",
|
||||
autohaus.name ?? "",
|
||||
)
|
||||
}
|
||||
>
|
||||
In den Kalender übernehmen
|
||||
</ActionButton>
|
||||
{vereinbart[art] && <ActionButton onClick={() => void verwerfen()}>Termin verwerfen</ActionButton>}
|
||||
</Tile>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function Tanken({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
/>
|
||||
</Tile>
|
||||
) : (
|
||||
<Tile>
|
||||
<Tile className="dm-liste">
|
||||
{gruppen.map((jahr) => {
|
||||
const ersparnisJahr = summe(jahr.eintraege, (t) => t.discount ?? 0)
|
||||
return (
|
||||
|
||||
@@ -17,9 +17,17 @@ export interface Bildplatz {
|
||||
*/
|
||||
beschreibung: string
|
||||
datei: string
|
||||
/**
|
||||
* Nicht in der Fotogalerie unter „Mein Audi" zeigen.
|
||||
*
|
||||
* Die Draufsicht gehört zur Fahrzeugstatus-Seite und nur dorthin (Ansage
|
||||
* des Eigentümers, 02.09.2026). In der Auswahl der Einstellungen bleibt sie
|
||||
* trotzdem stehen - sie ist der einzige Weg, das Foto hochzuladen.
|
||||
*/
|
||||
nurStatus?: boolean
|
||||
}
|
||||
|
||||
/** Die acht Plätze der Fotogalerie unter „Mein Audi". */
|
||||
/** Alle Bildplätze. Die Galerie zeigt davon nur GALERIE, siehe nurStatus. */
|
||||
export const BILDPLAETZE: Bildplatz[] = [
|
||||
{ name: "Seitenansicht", beschreibung: "Auto Seite", datei: "seitenansicht.webp" },
|
||||
{ name: "Front schräg", beschreibung: "Auto Front schräg", datei: "front-schraeg.webp" },
|
||||
@@ -32,9 +40,12 @@ export const BILDPLAETZE: Bildplatz[] = [
|
||||
// 02.09.2026 war sie der einzige sichtbare Bildplatz, der in den
|
||||
// Einstellungen NICHT auswählbar war — hochladen ließ sie sich damit gar
|
||||
// nicht (gemeldet vom Eigentümer). bilder.py kannte den Dateinamen längst.
|
||||
{ name: "Draufsicht", beschreibung: "Auto von oben", datei: "draufsicht.webp" },
|
||||
{ name: "Draufsicht", beschreibung: "Auto von oben", datei: "draufsicht.webp", nurStatus: true },
|
||||
]
|
||||
|
||||
/** Die Plätze, die die Galerie unter „Mein Audi" durchblättert. */
|
||||
export const GALERIE: Bildplatz[] = BILDPLAETZE.filter((b) => !b.nurStatus)
|
||||
|
||||
/**
|
||||
* Bei montierten Winterrädern zeigt die Seitenansicht das Winterfoto —
|
||||
* `SEITE_WINTER`/`bildInfo()` im Panel. Fehlte hier bisher ganz.
|
||||
|
||||
@@ -29,7 +29,19 @@ function alsIcsDatum(isoTag: string): string {
|
||||
return isoTag.replace(/-/g, "")
|
||||
}
|
||||
|
||||
export function kalenderDatei(titel: string, isoTag: string, beschreibung = ""): string {
|
||||
/**
|
||||
* `ort` wird zu LOCATION - die Kalender-Apps zeigen das als Ortsangabe und
|
||||
* bieten die Navigation dorthin an. Fehlte hier bis zum 02.09.2026 ganz,
|
||||
* obwohl das Panel es seit jeher setzt (`ics()` dort): der Termin sagte
|
||||
* "was" und "wann", aber nicht "wo" - und genau das ist neben dem Datum der
|
||||
* einzige Zweck, den ein Werkstatttermin in dieser App hat.
|
||||
*/
|
||||
export function kalenderDatei(
|
||||
titel: string,
|
||||
isoTag: string,
|
||||
beschreibung = "",
|
||||
ort = "",
|
||||
): string {
|
||||
const stempel = new Date().toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"
|
||||
const start = alsIcsDatum(isoTag)
|
||||
const ende = alsIcsDatum(
|
||||
@@ -47,6 +59,7 @@ export function kalenderDatei(titel: string, isoTag: string, beschreibung = ""):
|
||||
`DTSTART;VALUE=DATE:${start}`,
|
||||
`DTEND;VALUE=DATE:${ende}`,
|
||||
`SUMMARY:${zeilenUmbruch(titel)}`,
|
||||
ort ? `LOCATION:${zeilenUmbruch(ort)}` : "",
|
||||
beschreibung ? `DESCRIPTION:${zeilenUmbruch(beschreibung)}` : "",
|
||||
"BEGIN:VALARM",
|
||||
"TRIGGER:-P2D",
|
||||
@@ -74,8 +87,9 @@ export async function kalenderDateiLaden(
|
||||
titel: string,
|
||||
isoTag: string,
|
||||
beschreibung = "",
|
||||
ort = "",
|
||||
): Promise<void> {
|
||||
const inhalt = kalenderDatei(titel, isoTag, beschreibung)
|
||||
const inhalt = kalenderDatei(titel, isoTag, beschreibung, ort)
|
||||
const name = dateiname(titel)
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
|
||||
@@ -916,10 +916,21 @@ button.dm-serviceblock:active {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* 16:9, wie carImg("galerie") im Panel ("carfix", nicht "carfix klein" -
|
||||
das 16:8-Maß gilt nur für die Übersicht-Szene). */
|
||||
/* Das Bild bestimmt die Höhe, wie im Panel ("carfix klein").
|
||||
|
||||
Bis zum 02.09.2026 stand hier ein fester 16:9-Kasten mit object-fit:cover.
|
||||
Jedes Foto, dessen Verhältnis davon abweicht, verlor dabei oben und unten
|
||||
etwas - bei der Frontansicht die Stoßstange, so gemeldet. Der Preis ist,
|
||||
dass der Block beim Durchblättern seine Höhe ändert; das ist dieselbe
|
||||
Abwägung, die die Übersicht-Szene am 31.08. entschieden hat. Der
|
||||
Platzhalter behält sein festes Maß (Regel weiter oben), er hat ja kein
|
||||
eigenes Verhältnis. */
|
||||
.dm-galerie-wechsel .ads-bildbox {
|
||||
aspect-ratio: 16 / 9;
|
||||
aspect-ratio: auto;
|
||||
}
|
||||
|
||||
.dm-galerie-wechsel .ads-bildbox img {
|
||||
height: auto;
|
||||
}
|
||||
.dm-galerie-punkte {
|
||||
display: flex;
|
||||
@@ -1064,6 +1075,46 @@ button.dm-sicherheitszeile:active { background: var(--tile-2); }
|
||||
gemessen): 12px oben/unten, 40px links (Platz der Wischspalte), min-height
|
||||
56px, Trennlinie unten, Beschriftung 16px in --fg mit 12,5px-Zusatz in
|
||||
--fg3, Wert 15px in --fg2 mit demselben Zusatz. */
|
||||
/* Fahrten- und Tankliste: Abschnittsköpfe statt Einrückung.
|
||||
|
||||
Gemessen am 02.09.2026 im laufenden Panel (gleicher Aufbau): von der
|
||||
Innenkante der Kachel bis zum Ortsnamen lagen 58px tote Breite - 22px
|
||||
Einrückung der Monatsebene plus 40px linkes Polster der Zeile. Auf einem
|
||||
Telefon brach "Eichstätt → Adelschlag" deshalb um.
|
||||
|
||||
Apples Richtlinien gruppieren nicht über Einrückung, sondern über
|
||||
Abschnittsköpfe über die volle Breite; die Zeilen stehen alle auf
|
||||
derselben Kante. Die Monatszeile bleibt kleiner und grau und liest sich
|
||||
dadurch als Kopf, ohne Platz zu kosten. Beschränkt auf .dm-liste, weil das
|
||||
linke Polster von .dm-blatt anderswo gewollt ist. Gleichlautend .liste im
|
||||
Panel. */
|
||||
.dm-liste .ads-accordion--lvl2 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* Der Monat ist ein Abschnittskopf, kein kleingeratener Eintrag.
|
||||
|
||||
Vorher unterschied er sich von der Fahrtzeile nur durch Größe (13 gegen
|
||||
16px) und Farbe - beim Lesen wirkte die Liste dadurch zweistufig statt
|
||||
dreistufig, und genau danach hat der Eigentümer gefragt (02.09.2026).
|
||||
Versalien plus Sperrung sind die Form, die iOS für Abschnittsköpfe
|
||||
benutzt und die in dieser Oberfläche schon über dem Seitentitel steht
|
||||
(.ads-eyebrow).
|
||||
|
||||
11px und nicht 10: das ist die Untergrenze, die dieses Projekt aus den
|
||||
Apple-Richtlinien übernommen hat. In beiden Oberflächen dieselben Werte,
|
||||
damit der Kopf nicht je Gerät anders aussieht. */
|
||||
.dm-liste .ads-accordion--lvl2 .ads-acc-h__k {
|
||||
font-size: 11px;
|
||||
letter-spacing: .18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg3);
|
||||
}
|
||||
|
||||
.dm-liste .dm-blatt {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.dm-blatt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":"2026.9.2.12","sha256":"2d28d29f526a130188efe816fc5cb8054f3f1afaf86f61eb371dc617753de29c","bytes":266536,"gebaut":"2026-09-02T13:40:40Z"}
|
||||
{"version":"2026.9.2.20","sha256":"04aa82b37c019eadf0c9c23bc27e4931b9371dba512ddd979adc1ea70f482924","bytes":266578,"gebaut":"2026-09-02T14:34:34Z"}
|
||||
Binary file not shown.
@@ -344,8 +344,14 @@ const BILDER = [
|
||||
// 02.09.2026 war sie der einzige sichtbare Bildplatz, der in den
|
||||
// Einstellungen NICHT auswählbar war - hochladen ließ sie sich damit gar
|
||||
// nicht (gemeldet vom Eigentümer). bilder.py kannte den Dateinamen längst.
|
||||
{ name: "Draufsicht", label: "Auto von oben", datei: "draufsicht.webp" },
|
||||
/* nurStatus: gehoert zur Fahrzeugseite und nur dorthin (Ansage des
|
||||
Eigentuemers, 02.09.2026) - die Galerie unter "Mein Audi" ueberspringt
|
||||
sie, in der Auswahl der Einstellungen bleibt sie stehen, weil das der
|
||||
einzige Weg ist, das Foto hochzuladen. */
|
||||
{ name: "Draufsicht", label: "Auto von oben", datei: "draufsicht.webp", nurStatus: true },
|
||||
];
|
||||
/* Die Plaetze, die die Galerie durchblaettert. */
|
||||
const GALERIE = BILDER.filter((b) => !b.nurStatus);
|
||||
let bild = 0;
|
||||
const SEITE_WINTER = { label: "Auto Seite mit Winterrädern", datei: "seitenansicht-winter.webp" };
|
||||
// Bei jedem Hochladen/Löschen eines Fahrzeugfotos hochgezählt und als
|
||||
@@ -403,9 +409,14 @@ function carImg(modus) {
|
||||
.bildbox.bild-fehlt img{display:none} unsichtbar und damit unklickbar
|
||||
ist - der bisherige Handler (id="carimg") griff dann ins Leere. */
|
||||
return `<div data-bildgalerie>
|
||||
${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix", "carimg")}
|
||||
${/* "klein": das Bild bestimmt die Hoehe. Im festen 16:9-Kasten mit
|
||||
object-fit:cover verlor die Frontansicht unten die Stossstange
|
||||
(gemeldet 02.09.2026) - jedes Foto, dessen Verhaeltnis nicht 16:9
|
||||
ist, wurde beschnitten. Dieselbe Entscheidung wie bei der
|
||||
Uebersicht-Szene (31.08.) und der Vorschau in den Einstellungen. */""
|
||||
}${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein", "carimg")}
|
||||
</div>
|
||||
<div class="dots" id="dots">${BILDER.map((_, i) => `<i class="${i === bild ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
<div class="dots" id="dots">${GALERIE.map((b) => `<i class="${b === BILDER[bild] ? "on" : ""}"></i>`).join("")}</div>`;
|
||||
}
|
||||
/* Bildmarke des Kraftstoffanbieters (Shell). Anders als bei den Fahrzeug-
|
||||
fotos ist hier kein generischer Platzhalter-Rahmen sinnvoll, weil das Logo
|
||||
@@ -430,7 +441,11 @@ function markenlogo() {
|
||||
an .szene haengte; die zugehoerigen ::after-Regeln sind in
|
||||
audi-dashboard.css beschrieben. */
|
||||
function bildWeiter() {
|
||||
bild = (bild + 1) % BILDER.length;
|
||||
// Ueber die Galerie-Plaetze, nicht ueber alle: die Draufsicht wird
|
||||
// uebersprungen (siehe nurStatus in BILDER).
|
||||
const jetzt = GALERIE.findIndex((b) => b === BILDER[bild]);
|
||||
const naechst = GALERIE[(Math.max(0, jetzt) + 1) % GALERIE.length];
|
||||
bild = BILDER.indexOf(naechst);
|
||||
const img = ROOT.getElementById("carimg");
|
||||
if (!img) return;
|
||||
const info = bildInfo(bild);
|
||||
@@ -443,7 +458,7 @@ function bildWeiter() {
|
||||
}
|
||||
const sz = ROOT.getElementById("szene");
|
||||
if (sz) sz.className = "szene";
|
||||
[...ROOT.getElementById("dots").children].forEach((d, i) => d.classList.toggle("on", i === bild));
|
||||
[...ROOT.getElementById("dots").children].forEach((d, i) => d.classList.toggle("on", GALERIE[i] === BILDER[bild]));
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- Termine/Vorhersage
|
||||
@@ -1974,7 +1989,7 @@ function vAudi() {
|
||||
<div class="row"><dt>aktuell montiert:</dt><dd>${r.aktiv}räder</dd></div>
|
||||
<div class="row"><dt>Laufleistung</dt><dd>${a.km != null ? de(a.km) + " km" : "unbekannt"}</dd></div>
|
||||
<div class="row" style="border-bottom:0"><dt>Wechseltermin</dt>
|
||||
<dd>${dedat(new Date(r.wechsel.datum))}${r.wechsel.vereinbart ? "" : ""}</dd></div>
|
||||
<dd>${dedat(new Date(r.wechsel.datum))}</dd></div>
|
||||
</dl></button>`;
|
||||
}
|
||||
|
||||
@@ -2398,7 +2413,6 @@ function vService() {
|
||||
return `<div class="termingruppe">
|
||||
<div class="tg-kopf">
|
||||
${kopf}
|
||||
${t.vereinbart ? `<span class="tg-chip">Werkstatt am ${dedat(new Date(t.vereinbart))}</span>` : ""}
|
||||
</div>
|
||||
${herstellervorgabe ? `<div class="tg-vorgabe">Herstellervorgabe</div>` : ""}
|
||||
<div class="tg-wert">${wert}</div>
|
||||
@@ -2406,15 +2420,18 @@ function vService() {
|
||||
</div>`; }).join("")}
|
||||
</div>
|
||||
<div class="tile"><span class="label">Termin vereinbaren</span>
|
||||
${(() => { const ts = termine(), t = ts[Math.min(svAuswahl, ts.length - 1)];
|
||||
const vorschlag = CAR.service.vereinbart[t.art] || (t.ziel ? t.ziel.toISOString().slice(0, 10) : "");
|
||||
${(() => { const ts = terminAuswahl(), t = gewaehlterTermin();
|
||||
/* Vorschlag ist das errechnete Faelligkeitsdatum, sonst leer. Der
|
||||
eingetragene Termin wird NICHT gespeichert - siehe der Kommentar an
|
||||
TerminVereinbaren() in der App: er dient allein dem Kalendereintrag
|
||||
("was, wann, bei welcher Werkstatt") und hat keine weitere Wirkung. */
|
||||
const vorschlag = t.ziel ? t.ziel.toISOString().slice(0, 10) : "";
|
||||
return `
|
||||
<div class="feld"><label for="svArt">Wofür</label>
|
||||
<select id="svArt" style="width:132px" data-svsel>${ts.map((x, i) => `<option value="${i}" ${i === svAuswahl ? "selected" : ""}>${x.art}</option>`).join("")}</select></div>
|
||||
<select id="svArt" style="width:190px" data-svsel>${ts.map((x, i) => `<option value="${i}" ${i === svAuswahl ? "selected" : ""}>${x.art}</option>`).join("")}</select></div>
|
||||
<div class="feld" style="border-bottom:0"><label for="svDat">Werkstatttermin am</label>
|
||||
<input id="svDat" type="date" style="width:132px" value="${vorschlag}" data-vereinbart="${esc(t.art)}"></div>
|
||||
<button class="aktion" data-ics="service">In den Kalender übernehmen</button>
|
||||
${CAR.service.vereinbart[t.art] ? `<button class="aktion" data-loeschen="${esc(t.art)}">Termin verwerfen</button>` : ""}`; })()}
|
||||
<input id="svDat" type="date" style="width:150px" value="${vorschlag}"></div>
|
||||
<button class="aktion" data-ics="service">In den Kalender übernehmen</button>`; })()}
|
||||
</div>
|
||||
<div class="tile">
|
||||
<span class="label">Autohaus</span>
|
||||
@@ -2528,6 +2545,23 @@ function vIdentBearbeiten() {
|
||||
|
||||
function monatePlus(datum, monate) { const d = dat(datum); d.setMonth(d.getMonth() + monate); return d; }
|
||||
function letzterEintrag(stichwort) { return CAR.service.buch.find((e) => e.art.toLowerCase().includes(stichwort)) || null; }
|
||||
/* Die Auswahl im Feld "Wofuer": die faelligen Termine plus "Andere".
|
||||
|
||||
"Andere" ist kein faelliger Termin, sondern ein freier Werkstattbesuch, der
|
||||
zu keiner der drei geplanten Arten gehoert (Reifenwechsel, Garantiearbeit,
|
||||
Nachbesserung). Er hat deshalb weder Ziel noch Kilometerangabe - nur ein
|
||||
Datum, und taucht oben in der Terminliste nicht als Karte auf. */
|
||||
function terminAuswahl() {
|
||||
return [
|
||||
...termine(),
|
||||
{ art: "Andere", km: null, ziel: null },
|
||||
];
|
||||
}
|
||||
function gewaehlterTermin() {
|
||||
const auswahl = terminAuswahl();
|
||||
return auswahl[Math.min(svAuswahl, auswahl.length - 1)];
|
||||
}
|
||||
|
||||
function termine() {
|
||||
const oel = CAR.oel;
|
||||
const oelKm = oel.modus === "hersteller" ? oel.herstellerKm : Math.min(oel.km, oel.herstellerKm);
|
||||
@@ -2538,7 +2572,7 @@ function termine() {
|
||||
const kmDatum = kmZiel !== null ? prognose(kmZiel).datum : null;
|
||||
const zuerst = (kmDatum && datZiel) ? (kmDatum < datZiel ? "km" : "zeit") : (kmDatum ? "km" : "zeit");
|
||||
return { art, km: kmZiel, datum: datZiel, kmDatum, zuerst, basis,
|
||||
ziel: zuerst === "km" ? kmDatum : datZiel, vereinbart: CAR.service.vereinbart[art] || null };
|
||||
ziel: zuerst === "km" ? kmDatum : datZiel };
|
||||
};
|
||||
const oelTermin = bauen("Ölwechsel", letzterEintrag("öl"), oelKm, oelM);
|
||||
// Genauere, eigenständige Ölwechsel-Prognose (siehe oelwechselPrognose())
|
||||
@@ -2727,7 +2761,7 @@ function vBeitrag() {
|
||||
return `
|
||||
<div class="tile"><span class="label">Beitrag anpassen</span>
|
||||
<div class="feld"><label for="vGueltigAb">Gültig ab</label>
|
||||
<input id="vGueltigAb" type="date" style="width:132px" value="${v.gueltigAb}" data-vgueltigab></div>
|
||||
<input id="vGueltigAb" type="date" style="width:150px" value="${v.gueltigAb}" data-vgueltigab></div>
|
||||
<div class="feld"><label for="vSfHaft">Schadenfreiheitsklasse Haftpflicht</label>
|
||||
<input id="vSfHaft" style="width:132px" value="${esc(v.teile[0]?.sf || "")}" data-vteil="0|sf"></div>
|
||||
<div class="feld"><label for="vSfKasko">Schadenfreiheitsklasse Vollkasko</label>
|
||||
@@ -2865,7 +2899,7 @@ function vSteuerBearbeiten() {
|
||||
<option ${t.zeitraum === "halbjährlich" ? "selected" : ""}>halbjährlich</option>
|
||||
</select></div>
|
||||
<div class="feld"><label for="stFaellig">Nächste Fälligkeit</label>
|
||||
<input id="stFaellig" type="date" style="width:132px" value="${t.faellig || ""}" data-steuerfaellig></div>
|
||||
<input id="stFaellig" type="date" style="width:150px" value="${t.faellig || ""}" data-steuerfaellig></div>
|
||||
<div class="feld" style="border-bottom:0"><label for="stLast">Lastschrift erteilt</label>
|
||||
<label class="switch"><input id="stLast" type="checkbox" data-steuerlast ${t.lastschrift ? "checked" : ""}><span class="track"></span></label></div>
|
||||
</div>`;
|
||||
@@ -2979,7 +3013,7 @@ function vEinst() {
|
||||
<div class="bildmenu-catcher" data-sdabbrechen></div>
|
||||
<div class="sdpopup">
|
||||
<div class="feld" style="border-bottom:0"><label for="sdPopupBis">Gültig bis</label>
|
||||
<input id="sdPopupBis" type="date" style="width:132px" value="${CAR.smartdeal.laeuftAb || ""}"></div>
|
||||
<input id="sdPopupBis" type="date" style="width:150px" value="${CAR.smartdeal.laeuftAb || ""}"></div>
|
||||
<div class="seg" style="margin-top:14px">
|
||||
<button type="button" data-sdabbrechen>Abbrechen</button>
|
||||
<button type="button" class="on" data-sdbestaetigen>Bestätigen</button>
|
||||
@@ -3621,7 +3655,7 @@ function vTrips() {
|
||||
<p>Fahrten werden erkannt, sobald der Motor gestartet wird. Bis dahin lässt sich jede Fahrt von Hand erfassen.</p>
|
||||
</div></div>
|
||||
${vTripsFormular()}`;
|
||||
let h = `<div class="tile">`;
|
||||
let h = `<div class="tile liste">`;
|
||||
jahre.forEach((j, ji) => {
|
||||
const tj = neuesteZuerst(TRIPS.filter((t) => new Date(t.ts_start).getFullYear() === j), "ts_start");
|
||||
h += `<button class="acc-h" aria-expanded="${ji === 0}" data-acc="y${j}"><span class="mark"></span>
|
||||
@@ -4134,7 +4168,7 @@ function vFuel() {
|
||||
<p>Ein Tankvorgang wird automatisch angelegt, sobald der Tankstand des Fahrzeugs steigt. Den Beleg kann man danach hochladen – oder den Vorgang gleich hier von Hand erfassen.</p>
|
||||
</div></div>
|
||||
${vFuelFormular()}`;
|
||||
let h = `<div class="tile">`;
|
||||
let h = `<div class="tile liste">`;
|
||||
jahre.forEach((j, ji) => {
|
||||
const fj = neuesteZuerst(FILLS.filter((f) => new Date(f.ts).getFullYear() === j), "ts");
|
||||
const ersparnisJahr = sum(fj, (f) => f.discount || 0);
|
||||
@@ -5658,8 +5692,7 @@ function ereignisseVerdrahten() {
|
||||
}
|
||||
const dsExport = e.target.closest("[data-datensatzsichern]");
|
||||
if (dsExport) { datensatzExportieren(dsExport.dataset.datensatzsichern); return; }
|
||||
const del = e.target.closest("[data-loeschen]");
|
||||
if (del) { delete CAR.service.vereinbart[del.dataset.loeschen]; profilSpeichern(); render(); return; }
|
||||
|
||||
/* Identität und Technik: Bearbeitungsansicht ein/aus, Positionen der
|
||||
Technik- und Ausstattungsgruppen anlegen/entfernen. */
|
||||
if (e.target.closest("[data-identbearbeiten]")) { identBearbeitenOffen = !identBearbeitenOffen; render(); return; }
|
||||
@@ -5780,13 +5813,10 @@ function ereignisseVerdrahten() {
|
||||
const k = e.target.closest("[data-ics]");
|
||||
if (k) {
|
||||
if (k.dataset.ics === "service") {
|
||||
const ts = termine(), t = ts[Math.min(svAuswahl, ts.length - 1)];
|
||||
const datum = $("#svDat").value;
|
||||
CAR.service.vereinbart[t.art] = datum;
|
||||
profilSpeichern();
|
||||
ics(`${CONFIG.fahrzeugtitel}: ${t.art}`, datum, CAR.service.autohaus.name,
|
||||
const t = gewaehlterTermin();
|
||||
// Nur exportieren, nichts merken: der Termin lebt im Kalender.
|
||||
ics(`${CONFIG.fahrzeugtitel}: ${t.art}`, $("#svDat").value, CAR.service.autohaus.name,
|
||||
t.km !== null ? `Fällig bei ${de(t.km)} km · ${CAR.service.autohaus.adresse}` : CAR.service.autohaus.adresse);
|
||||
render();
|
||||
} else {
|
||||
const datum = $("#rfDat").value;
|
||||
CAR.reifen.wechsel.datum = datum;
|
||||
@@ -5930,8 +5960,7 @@ function ereignisseVerdrahten() {
|
||||
else CAR[ein] = e.target.value;
|
||||
profilSpeichern(); return;
|
||||
}
|
||||
const vb = e.target.dataset.vereinbart;
|
||||
if (vb) { CAR.service.vereinbart[vb] = e.target.value; profilSpeichern(); render(); return; }
|
||||
|
||||
const ah = e.target.dataset.ah;
|
||||
if (ah) {
|
||||
CAR.service.autohaus[ah] = e.target.value;
|
||||
|
||||
@@ -902,6 +902,41 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
|
||||
.lvl2 { margin-left: 22px; }
|
||||
.lvl2 .acc-h .k { font-size: 13px; }
|
||||
|
||||
/* Fahrten- und Tankliste: Abschnittsköpfe statt Einrückung.
|
||||
|
||||
Gemessen am 02.09.2026 im laufenden Panel: von der Innenkante der
|
||||
Kachel bis zum Ortsnamen lagen 58px tote Breite - 22px Einrückung der
|
||||
Monatsebene plus 40px linkes Polster der Zeile. Auf einem Telefon blieb
|
||||
für "Eichstätt → Adelschlag" so wenig übrig, dass der Name umbrach
|
||||
(gemeldet vom Eigentümer mit Bildschirmfoto).
|
||||
|
||||
Apples Richtlinien lösen Gruppierung nicht über Einrückung, sondern über
|
||||
Abschnittsköpfe, die über die volle Breite laufen; die Zeilen darunter
|
||||
stehen alle auf derselben Kante. Genau das ist hier umgesetzt - die
|
||||
Monatszeile bleibt kleiner und grau und liest sich dadurch als Kopf,
|
||||
ohne Platz zu kosten. Beschränkt auf .liste, weil das linke Polster von
|
||||
.leaf anderswo (Batteriewerte, Servicebuch) gewollt ist. */
|
||||
.liste .lvl2 { margin-left: 0; }
|
||||
/* Der Monat ist ein Abschnittskopf, kein kleingeratener Eintrag.
|
||||
|
||||
Vorher unterschied er sich von der Fahrtzeile nur durch Groesse (13 gegen
|
||||
16px) und Farbe - beim Lesen wirkte die Liste dadurch zweistufig statt
|
||||
dreistufig, und genau danach hat der Eigentuemer gefragt (02.09.2026).
|
||||
Versalien plus Sperrung sind die Form, die iOS fuer Abschnittskoepfe
|
||||
benutzt und die in dieser Oberflaeche schon ueber dem Seitentitel steht
|
||||
(.eyebrow).
|
||||
|
||||
11px und nicht 10: das ist die Untergrenze, die dieses Projekt aus den
|
||||
Apple-Richtlinien uebernommen hat. In beiden Oberflaechen dieselben
|
||||
Werte, damit der Kopf nicht je Geraet anders aussieht. */
|
||||
.liste .lvl2 .acc-h .k {
|
||||
font-size: 11px;
|
||||
letter-spacing: .18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg3);
|
||||
}
|
||||
.liste .leaf { padding-left: 0; }
|
||||
|
||||
.tile-h {
|
||||
display: block; width: 100%;
|
||||
background: none; border: none;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "audi_dashboard",
|
||||
"name": "Audi Dashboard",
|
||||
"version": "2026.9.2.12",
|
||||
"version": "2026.9.2.20",
|
||||
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||
"codeowners": ["@paul"],
|
||||
|
||||
Reference in New Issue
Block a user