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:
@@ -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