Fahrt-Formular: Ankunftszeit statt Dauer, Art gross, neutrale Pille
Fuenf gemeldete Punkte plus ein Theme-Bug beim Nachpruefen: - "Dauer" ist kein Eingabefeld mehr - stattdessen Ankunftszeit, die Dauer wird aus Start/Ankunft berechnet und live angezeigt (fahrtZeitraum()/ dauerText()), inklusive Fahrten ueber Mitternacht. - Art-Werte werden grossgeschrieben angezeigt (Privat/Arbeitsweg), der gespeicherte Wert bleibt klein (artText()-Helfer). - Arbeitsweg-Pille ist nicht mehr rot - sieht jetzt aus wie Privat. - "tippen zum Umschalten" unter der Pille entfernt. - Bild-Platzhalter (Einstellungen) war im Tag-Theme unsichtbar: --tile wird dort vom iOS-Overlay auf reines Weiss gesetzt, identisch mit Canvas/ Kacheln. Auf --ios-fill umgestellt, in beiden Themes sichtbar grau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,11 @@ route — see section C; a twelfth round the same day removed `HECKKLAPPENSCHLOS
|
||||
half-cut-off image placeholder, gave the Inspektion its own "voraussichtlich am …" forecast, made
|
||||
trips fully editable (new `audi_dashboard_fahrt_aktualisieren`, "Neue Fahrt" now offers every field
|
||||
the detail page shows), aligned the page title with the tiles on phone width, and closed two
|
||||
dead-ends in the bootstrap retry loop behind the reported hanging "Lädt …" screen).
|
||||
dead-ends in the bootstrap retry loop behind the reported hanging "Lädt …" screen; a thirteenth
|
||||
round the same day replaced the trip form's editable "Dauer" with Start-/Ankunftszeit (duration is
|
||||
now computed, not entered), capitalized the Art labels shown to the user, dropped the red styling
|
||||
and hint text from the Arbeitsweg pill, and fixed a daylight-mode-only regression from round twelve
|
||||
where the image placeholder's grey background had turned invisible-white).
|
||||
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.
|
||||
@@ -1278,6 +1282,46 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
|
||||
war jedes Mal binnen ~2s da) - die Sackgassen sind im Code belegt, dass sie *die* Ursache
|
||||
beim Nutzer waren, ist damit aber nicht bewiesen. Tritt es erneut auf: Browser-Konsole
|
||||
erfragen, bevor weiter geraten wird.
|
||||
- [x] Thirteenth round (2026-08-17, same day) — five more user-reported items on the trip form and
|
||||
one theme bug found while verifying it:
|
||||
(1) **"Dauer" replaced by "Ankunftszeit"; duration is no longer editable.** `fahrtFelder()`
|
||||
now has Startzeit + Ankunftszeit inputs and a plain (non-input) duration display next to
|
||||
them, computed by `fahrtZeitraum()`/`dauerText()` and kept live via a new
|
||||
`data-fahrtzeitfeld` input handler (same "patch the one node, don't call render()" pattern
|
||||
the existing `data-tankpreisfeld` handler uses, for the same reason: a full re-render would
|
||||
steal focus from the field being typed in). An arrival time not later than the start time is
|
||||
treated as crossing midnight (`+1 Tag`), matching how `_fahrt_beenden()` already handles a
|
||||
real overnight trip. `fahrtFormularWerte()` derives `ts_start`/`ts_end` from the same
|
||||
calculation - the backend never sees a separate duration field, so it can't disagree with
|
||||
the times. Verified live: 09:00→10:45 shows "1 Std. 45 Min.", 23:30→00:15 shows "45 Min."
|
||||
(crossed midnight correctly), and editing an existing trip's arrival time on the Einzelfahrt
|
||||
page persisted the recalculated `duration_s` (7200s for a 10:00→12:00 edit, checked in
|
||||
`fahrten.jsonl`).
|
||||
(2) **Art values now display capitalized** ("Privat"/"Arbeitsweg") wherever shown to the user
|
||||
- the trip-edit `<select>`, the trip-list row, and the Einzelfahrt pill - via a new
|
||||
`artText()` helper; the stored value stays lowercase (`"privat"`/`"arbeitsweg"`), since it's
|
||||
compared in several places (the pill, the "Arbeitsweg" statistic, the CSV export) and already
|
||||
exists that way in every trip recorded so far. The CSV export itself was deliberately left
|
||||
showing the raw value - a data file, not UI copy.
|
||||
(3) **Arbeitsweg pill no longer red.** `.pill.work` (`color:var(--red)` / a translucent red
|
||||
fill in the iOS overlay) is gone from both stylesheets along with the now-unused `work` class
|
||||
on the button; the pill uses the same neutral styling regardless of Art, matching the request
|
||||
that Arbeitsweg should look like Privat, not draw attention to itself.
|
||||
(4) **"tippen zum Umschalten" hint removed** from under the Einzelfahrt pill, per explicit
|
||||
request - the pill is still clickable, just without the caption.
|
||||
(5) **Found while re-verifying the placeholder fix from round twelve: it broke daylight mode.**
|
||||
`.bildbox.bild-fehlt .platzhalter{background:var(--tile)}` (added round twelve) reads fine in
|
||||
Nacht theme, where `--tile` is a faint white overlay on a near-black canvas - but the iOS
|
||||
overlay's Tag-theme block redefines `--tile:#FFFFFF`, identical to the canvas/most tiles
|
||||
around it, so the placeholder box became invisible in daylight mode (measured live:
|
||||
`background-color: rgb(255,255,255)` on both the placeholder and its surroundings). Switched
|
||||
to `--ios-fill` (`rgba(120,120,128,.16)` in Tag theme, `.24` in Nacht) - the same token
|
||||
`.feld select`/`.aktion` already use to mark "this is a surface", visibly grey in both themes.
|
||||
Verified live in Tag theme: `background-color: rgba(120,120,128,.16)`, clearly a distinct box
|
||||
against the white canvas (see screenshot taken during verification).
|
||||
All five deployed to `audi_ha_test` (version `1787009000`) and synced to `installationspaket/`;
|
||||
console showed only the pre-existing service-worker/404 noise from the missing vehicle photos,
|
||||
nothing new.
|
||||
- [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) —
|
||||
text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed
|
||||
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
|
||||
|
||||
@@ -2473,7 +2473,38 @@ function vReifen() {
|
||||
Felder decken genau das ab, was die Einzelfahrt-Seite anzeigt; Verbrauch,
|
||||
Status und km-Quelle bleiben außen vor, die meldet das Fahrzeug bzw. leitet
|
||||
das Backend ab. */
|
||||
/* Gespeichert bleibt der kleingeschriebene Schlüssel - er wird an mehreren
|
||||
Stellen verglichen (Pille auf der Einzelfahrt, Statistik "Arbeitsweg", CSV)
|
||||
und steht so auch in allen bereits erfassten Fahrten. Angezeigt wird er
|
||||
groß, deshalb artText() statt eines geänderten Werts. */
|
||||
const FAHRT_ARTEN = ["privat", "arbeitsweg"];
|
||||
const artText = (a) => (a ? a.charAt(0).toUpperCase() + a.slice(1) : "");
|
||||
|
||||
/* "1 Std. 15 Min." statt "75 min" - bei mehrstündigen Fahrten liest sich die
|
||||
reine Minutenzahl schlecht. */
|
||||
function dauerText(sekunden) {
|
||||
if (sekunden == null || !isFinite(sekunden)) return "–";
|
||||
const min = Math.max(0, Math.round(sekunden / 60));
|
||||
const std = Math.floor(min / 60);
|
||||
return std ? `${std} Std. ${min % 60} Min.` : `${min} Min.`;
|
||||
}
|
||||
|
||||
/* Start und Ankunft eines Formulars als echte Zeitpunkte. Liegt die Ankunft
|
||||
vor der Abfahrt, wurde über Mitternacht gefahren - dann zählt der Folgetag.
|
||||
null, solange Datum oder eine der beiden Uhrzeiten fehlt. */
|
||||
function fahrtZeitraum(praefix) {
|
||||
const datum = $(`#${praefix}Datum`).value, ab = $(`#${praefix}Start`).value, an = $(`#${praefix}Ende`).value;
|
||||
if (!datum || !ab || !an) return null;
|
||||
const start = new Date(`${datum}T${ab}:00`);
|
||||
let ende = new Date(`${datum}T${an}:00`);
|
||||
if (isNaN(start) || isNaN(ende)) return null;
|
||||
if (ende <= start) ende = new Date(ende.getTime() + dTag);
|
||||
return { start, ende };
|
||||
}
|
||||
function fahrtDauerText(praefix) {
|
||||
const z = fahrtZeitraum(praefix);
|
||||
return z ? dauerText((z.ende - z.start) / 1000) : "–";
|
||||
}
|
||||
|
||||
/* Datum für ein <input type="date"> in Ortszeit. Bewusst nicht über
|
||||
toISOString(), das rechnet nach UTC um und liefert kurz nach Mitternacht
|
||||
@@ -2489,16 +2520,30 @@ function zeitfeld(d) {
|
||||
|
||||
function fahrtFelder(praefix, t) {
|
||||
const start = t && t.ts_start ? new Date(t.ts_start) : new Date();
|
||||
const dauer = t && t.duration_s != null ? Math.round(t.duration_s / 60) : "";
|
||||
const ende = t && t.ts_end ? new Date(t.ts_end) : null;
|
||||
const art = t && t.art ? t.art : FAHRT_ARTEN[0];
|
||||
const wert = (v) => (v == null ? "" : v);
|
||||
const startzeit = t ? zeitfeld(start) : "08:00", ankunftzeit = ende ? zeitfeld(ende) : "08:30";
|
||||
// Die Dauer ist bewusst kein Eingabefeld: sie ergibt sich aus Start und
|
||||
// Ankunft und würde sich sonst mit ihnen widersprechen können. Aus
|
||||
// t.duration_s statt neu berechnet, damit eine über Mitternacht laufende
|
||||
// bestehende Fahrt (Ankunft "kleiner" als Start) korrekt bleibt; bei einer
|
||||
// neuen Fahrt (t === null) ergibt sich der Startwert aus denselben
|
||||
// Vorgabezeiten wie die Felder selbst. Der data-fahrtzeitfeld-Handler
|
||||
// rechnet bei jeder Änderung neu.
|
||||
let dauerSekunden = t && t.duration_s != null ? t.duration_s : null;
|
||||
if (dauerSekunden == null) {
|
||||
const [sh, sm] = startzeit.split(":").map(Number), [eh, em] = ankunftzeit.split(":").map(Number);
|
||||
dauerSekunden = ((eh * 60 + em) - (sh * 60 + sm) + (eh * 60 + em <= sh * 60 + sm ? 1440 : 0)) * 60;
|
||||
}
|
||||
const dauer = dauerText(dauerSekunden);
|
||||
return `
|
||||
<div class="feld"><label for="${praefix}Datum">Datum</label><input type="date" id="${praefix}Datum" value="${datumsfeld(start)}"></div>
|
||||
<div class="feld"><label for="${praefix}Start">Startzeit</label><input type="time" id="${praefix}Start" value="${t ? zeitfeld(start) : "08:00"}"></div>
|
||||
<div class="feld"><label for="${praefix}Dauer">Dauer</label>
|
||||
<span class="mitEinheit"><input type="number" min="0" id="${praefix}Dauer" value="${dauer}" placeholder="0"><span>min</span></span></div>
|
||||
<div class="feld"><label for="${praefix}Datum">Datum</label><input type="date" id="${praefix}Datum" value="${datumsfeld(start)}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label for="${praefix}Start">Startzeit</label><input type="time" id="${praefix}Start" value="${startzeit}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label for="${praefix}Ende">Ankunftszeit</label><input type="time" id="${praefix}Ende" value="${ankunftzeit}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label>Dauer</label><span id="${praefix}DauerAnzeige" style="color:var(--fg2)">${dauer}</span></div>
|
||||
<div class="feld"><label for="${praefix}Art">Art</label>
|
||||
<select id="${praefix}Art">${FAHRT_ARTEN.map((a) => `<option ${a === art ? "selected" : ""}>${a}</option>`).join("")}</select></div>
|
||||
<select id="${praefix}Art">${FAHRT_ARTEN.map((a) => `<option value="${a}" ${a === art ? "selected" : ""}>${artText(a)}</option>`).join("")}</select></div>
|
||||
<div class="feld"><label for="${praefix}Startort">Startort</label><input id="${praefix}Startort" value="${esc(wert(t && t.start_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}Zielort">Zielort</label><input id="${praefix}Zielort" value="${esc(wert(t && t.end_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}OdoStart">Startkilometer</label><input type="number" id="${praefix}OdoStart" value="${wert(t && t.odo_start)}" placeholder="wird ergänzt"></div>
|
||||
@@ -2510,11 +2555,10 @@ function fahrtFelder(praefix, t) {
|
||||
function fahrtFormularWerte(praefix) {
|
||||
const zahl = (id) => { const v = $(`#${praefix}${id}`).value; return v === "" ? null : parseFloat(v.replace(",", ".")); };
|
||||
const text = (id) => $(`#${praefix}${id}`).value.trim() || null;
|
||||
const dauer = parseInt($(`#${praefix}Dauer`).value, 10) || 0;
|
||||
const tsStart = new Date(`${$(`#${praefix}Datum`).value}T${$(`#${praefix}Start`).value}:00`);
|
||||
const z = fahrtZeitraum(praefix);
|
||||
return {
|
||||
ts_start: tsStart.toISOString(),
|
||||
ts_end: new Date(tsStart.getTime() + dauer * 60000).toISOString(),
|
||||
ts_start: z ? z.start.toISOString() : null,
|
||||
ts_end: z ? z.ende.toISOString() : null,
|
||||
art: $(`#${praefix}Art`).value,
|
||||
start_ort: text("Startort"), ziel_ort: text("Zielort"),
|
||||
odo_start: zahl("OdoStart"), odo_end: zahl("OdoEnde"), distanz: zahl("Distanz"),
|
||||
@@ -2556,7 +2600,7 @@ function vTrips() {
|
||||
<div class="acc-b ${mi === 0 ? "on" : ""}" id="m${j}${m}">`;
|
||||
tm.forEach((t) => { h += swipeZeile(`trip:${t.trip_id}`, `<button class="leaf" data-go="trip:${t.trip_id}">
|
||||
<span class="k">${strecke(t)}
|
||||
<small>${dedat(new Date(t.ts_start))} · ${esc(t.art)}</small></span>
|
||||
<small>${dedat(new Date(t.ts_start))} · ${artText(t.art)}</small></span>
|
||||
<span class="v">${t.distance_km != null ? de(t.distance_km) + " km" : "offen"}<small>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : t.status}</small></span>
|
||||
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
|
||||
h += `</div></div>`;
|
||||
@@ -2586,9 +2630,8 @@ function vTrip(id) {
|
||||
<span class="label">Distanz</span>
|
||||
<div class="fig" style="font-size:52px;margin-top:10px">${t.distance_km != null ? de(t.distance_km) : "–"}<span class="unit">km</span></div>
|
||||
<div style="margin-top:15px">
|
||||
<button class="pill ${t.art === "arbeitsweg" ? "work" : ""}" data-art="${t.trip_id}"
|
||||
style="cursor:pointer;font-family:inherit">${esc(t.art)}</button></div>
|
||||
<span class="label" style="margin-top:12px">tippen zum Umschalten</span>
|
||||
<button class="pill" data-art="${t.trip_id}"
|
||||
style="cursor:pointer;font-family:inherit">${artText(t.art)}</button></div>
|
||||
</div>
|
||||
<div class="tile"><dl class="rows">
|
||||
<div class="row"><dt>Startort</dt><dd>${esc(t.start_address || "unbekannt")}<small>${dedat(new Date(t.ts_start))}</small></dd></div>
|
||||
@@ -4128,6 +4171,15 @@ function ereignisseVerdrahten() {
|
||||
if (liste) liste.innerHTML = entitaetZeilenMarkup(sucheFeld, key, setupSuchtext, wert);
|
||||
return;
|
||||
}
|
||||
// Gleicher Grund wie beim tankpreisfeld-Handler darunter: nur die
|
||||
// Anzeige patchen, kein render() - das würde das gerade bearbeitete Feld
|
||||
// neu erzeugen und den Fokus verlieren.
|
||||
const zeitfeldPraefix = e.target.dataset.fahrtzeitfeld;
|
||||
if (zeitfeldPraefix) {
|
||||
const anzeige = ROOT.getElementById(`${zeitfeldPraefix}DauerAnzeige`);
|
||||
if (anzeige) anzeige.textContent = fahrtDauerText(zeitfeldPraefix);
|
||||
return;
|
||||
}
|
||||
const praefix = e.target.dataset.tankpreisfeld;
|
||||
if (!praefix) return;
|
||||
const liter = parseFloat((ROOT.getElementById(`${praefix}Liter`).value || "0").replace(",", "."));
|
||||
|
||||
@@ -196,7 +196,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
|
||||
|
||||
.pill{font-size:12px;letter-spacing:.01em;text-transform:none;padding:5px 11px;
|
||||
border:0;background:var(--ios-fill);color:var(--fg2)}
|
||||
.pill.work{background:rgba(245,5,55,.16);color:var(--ios-tint)}
|
||||
|
||||
.status{padding:14px 0 2px}
|
||||
.status .t{font-size:16px}
|
||||
@@ -333,7 +332,12 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
|
||||
/* Fahrzeugbild randlos wie in einer Fahrzeug-App: der Platzhalter bleibt
|
||||
sichtbar, aber ohne technischen Strichrahmen. */
|
||||
.szene{margin-bottom:20px}
|
||||
.bildbox.bild-fehlt .platzhalter{border:0;background:var(--tile);border-radius:var(--r-tile)}
|
||||
/* --tile statt --ios-fill waere hier naheliegend, faellt im Tag-Theme aber
|
||||
auf reines Weiss (#FFFFFF) - ununterscheidbar von Canvas und den meisten
|
||||
Kacheln drumherum, die Box verschwand. --ios-fill ist in beiden Themes ein
|
||||
deutlich sichtbares transluzentes Grau, dieselbe Konvention wie
|
||||
.feld select/.aktion ("das hier ist eine Flaeche"). */
|
||||
.bildbox.bild-fehlt .platzhalter{border:0;background:var(--ios-fill);border-radius:var(--r-tile)}
|
||||
.platzhalter-datei{display:none}
|
||||
|
||||
/* Zeilen mit Wert plus Statuspunkt rechts (Sicherheitscheck) ruhiger setzen */
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1787000000}
|
||||
{"version": 1787009000}
|
||||
|
||||
@@ -676,7 +676,6 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
border-radius: var(--r-pill);
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.pill.work { color: var(--red); border-color: var(--red); }
|
||||
|
||||
/* ------------------------------------------------------------ Akkordeon */
|
||||
.acc-h {
|
||||
|
||||
@@ -2473,7 +2473,38 @@ function vReifen() {
|
||||
Felder decken genau das ab, was die Einzelfahrt-Seite anzeigt; Verbrauch,
|
||||
Status und km-Quelle bleiben außen vor, die meldet das Fahrzeug bzw. leitet
|
||||
das Backend ab. */
|
||||
/* Gespeichert bleibt der kleingeschriebene Schlüssel - er wird an mehreren
|
||||
Stellen verglichen (Pille auf der Einzelfahrt, Statistik "Arbeitsweg", CSV)
|
||||
und steht so auch in allen bereits erfassten Fahrten. Angezeigt wird er
|
||||
groß, deshalb artText() statt eines geänderten Werts. */
|
||||
const FAHRT_ARTEN = ["privat", "arbeitsweg"];
|
||||
const artText = (a) => (a ? a.charAt(0).toUpperCase() + a.slice(1) : "");
|
||||
|
||||
/* "1 Std. 15 Min." statt "75 min" - bei mehrstündigen Fahrten liest sich die
|
||||
reine Minutenzahl schlecht. */
|
||||
function dauerText(sekunden) {
|
||||
if (sekunden == null || !isFinite(sekunden)) return "–";
|
||||
const min = Math.max(0, Math.round(sekunden / 60));
|
||||
const std = Math.floor(min / 60);
|
||||
return std ? `${std} Std. ${min % 60} Min.` : `${min} Min.`;
|
||||
}
|
||||
|
||||
/* Start und Ankunft eines Formulars als echte Zeitpunkte. Liegt die Ankunft
|
||||
vor der Abfahrt, wurde über Mitternacht gefahren - dann zählt der Folgetag.
|
||||
null, solange Datum oder eine der beiden Uhrzeiten fehlt. */
|
||||
function fahrtZeitraum(praefix) {
|
||||
const datum = $(`#${praefix}Datum`).value, ab = $(`#${praefix}Start`).value, an = $(`#${praefix}Ende`).value;
|
||||
if (!datum || !ab || !an) return null;
|
||||
const start = new Date(`${datum}T${ab}:00`);
|
||||
let ende = new Date(`${datum}T${an}:00`);
|
||||
if (isNaN(start) || isNaN(ende)) return null;
|
||||
if (ende <= start) ende = new Date(ende.getTime() + dTag);
|
||||
return { start, ende };
|
||||
}
|
||||
function fahrtDauerText(praefix) {
|
||||
const z = fahrtZeitraum(praefix);
|
||||
return z ? dauerText((z.ende - z.start) / 1000) : "–";
|
||||
}
|
||||
|
||||
/* Datum für ein <input type="date"> in Ortszeit. Bewusst nicht über
|
||||
toISOString(), das rechnet nach UTC um und liefert kurz nach Mitternacht
|
||||
@@ -2489,16 +2520,30 @@ function zeitfeld(d) {
|
||||
|
||||
function fahrtFelder(praefix, t) {
|
||||
const start = t && t.ts_start ? new Date(t.ts_start) : new Date();
|
||||
const dauer = t && t.duration_s != null ? Math.round(t.duration_s / 60) : "";
|
||||
const ende = t && t.ts_end ? new Date(t.ts_end) : null;
|
||||
const art = t && t.art ? t.art : FAHRT_ARTEN[0];
|
||||
const wert = (v) => (v == null ? "" : v);
|
||||
const startzeit = t ? zeitfeld(start) : "08:00", ankunftzeit = ende ? zeitfeld(ende) : "08:30";
|
||||
// Die Dauer ist bewusst kein Eingabefeld: sie ergibt sich aus Start und
|
||||
// Ankunft und würde sich sonst mit ihnen widersprechen können. Aus
|
||||
// t.duration_s statt neu berechnet, damit eine über Mitternacht laufende
|
||||
// bestehende Fahrt (Ankunft "kleiner" als Start) korrekt bleibt; bei einer
|
||||
// neuen Fahrt (t === null) ergibt sich der Startwert aus denselben
|
||||
// Vorgabezeiten wie die Felder selbst. Der data-fahrtzeitfeld-Handler
|
||||
// rechnet bei jeder Änderung neu.
|
||||
let dauerSekunden = t && t.duration_s != null ? t.duration_s : null;
|
||||
if (dauerSekunden == null) {
|
||||
const [sh, sm] = startzeit.split(":").map(Number), [eh, em] = ankunftzeit.split(":").map(Number);
|
||||
dauerSekunden = ((eh * 60 + em) - (sh * 60 + sm) + (eh * 60 + em <= sh * 60 + sm ? 1440 : 0)) * 60;
|
||||
}
|
||||
const dauer = dauerText(dauerSekunden);
|
||||
return `
|
||||
<div class="feld"><label for="${praefix}Datum">Datum</label><input type="date" id="${praefix}Datum" value="${datumsfeld(start)}"></div>
|
||||
<div class="feld"><label for="${praefix}Start">Startzeit</label><input type="time" id="${praefix}Start" value="${t ? zeitfeld(start) : "08:00"}"></div>
|
||||
<div class="feld"><label for="${praefix}Dauer">Dauer</label>
|
||||
<span class="mitEinheit"><input type="number" min="0" id="${praefix}Dauer" value="${dauer}" placeholder="0"><span>min</span></span></div>
|
||||
<div class="feld"><label for="${praefix}Datum">Datum</label><input type="date" id="${praefix}Datum" value="${datumsfeld(start)}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label for="${praefix}Start">Startzeit</label><input type="time" id="${praefix}Start" value="${startzeit}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label for="${praefix}Ende">Ankunftszeit</label><input type="time" id="${praefix}Ende" value="${ankunftzeit}" data-fahrtzeitfeld="${praefix}"></div>
|
||||
<div class="feld"><label>Dauer</label><span id="${praefix}DauerAnzeige" style="color:var(--fg2)">${dauer}</span></div>
|
||||
<div class="feld"><label for="${praefix}Art">Art</label>
|
||||
<select id="${praefix}Art">${FAHRT_ARTEN.map((a) => `<option ${a === art ? "selected" : ""}>${a}</option>`).join("")}</select></div>
|
||||
<select id="${praefix}Art">${FAHRT_ARTEN.map((a) => `<option value="${a}" ${a === art ? "selected" : ""}>${artText(a)}</option>`).join("")}</select></div>
|
||||
<div class="feld"><label for="${praefix}Startort">Startort</label><input id="${praefix}Startort" value="${esc(wert(t && t.start_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}Zielort">Zielort</label><input id="${praefix}Zielort" value="${esc(wert(t && t.end_address))}" placeholder="unbekannt"></div>
|
||||
<div class="feld"><label for="${praefix}OdoStart">Startkilometer</label><input type="number" id="${praefix}OdoStart" value="${wert(t && t.odo_start)}" placeholder="wird ergänzt"></div>
|
||||
@@ -2510,11 +2555,10 @@ function fahrtFelder(praefix, t) {
|
||||
function fahrtFormularWerte(praefix) {
|
||||
const zahl = (id) => { const v = $(`#${praefix}${id}`).value; return v === "" ? null : parseFloat(v.replace(",", ".")); };
|
||||
const text = (id) => $(`#${praefix}${id}`).value.trim() || null;
|
||||
const dauer = parseInt($(`#${praefix}Dauer`).value, 10) || 0;
|
||||
const tsStart = new Date(`${$(`#${praefix}Datum`).value}T${$(`#${praefix}Start`).value}:00`);
|
||||
const z = fahrtZeitraum(praefix);
|
||||
return {
|
||||
ts_start: tsStart.toISOString(),
|
||||
ts_end: new Date(tsStart.getTime() + dauer * 60000).toISOString(),
|
||||
ts_start: z ? z.start.toISOString() : null,
|
||||
ts_end: z ? z.ende.toISOString() : null,
|
||||
art: $(`#${praefix}Art`).value,
|
||||
start_ort: text("Startort"), ziel_ort: text("Zielort"),
|
||||
odo_start: zahl("OdoStart"), odo_end: zahl("OdoEnde"), distanz: zahl("Distanz"),
|
||||
@@ -2556,7 +2600,7 @@ function vTrips() {
|
||||
<div class="acc-b ${mi === 0 ? "on" : ""}" id="m${j}${m}">`;
|
||||
tm.forEach((t) => { h += swipeZeile(`trip:${t.trip_id}`, `<button class="leaf" data-go="trip:${t.trip_id}">
|
||||
<span class="k">${strecke(t)}
|
||||
<small>${dedat(new Date(t.ts_start))} · ${esc(t.art)}</small></span>
|
||||
<small>${dedat(new Date(t.ts_start))} · ${artText(t.art)}</small></span>
|
||||
<span class="v">${t.distance_km != null ? de(t.distance_km) + " km" : "offen"}<small>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : t.status}</small></span>
|
||||
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
|
||||
h += `</div></div>`;
|
||||
@@ -2586,9 +2630,8 @@ function vTrip(id) {
|
||||
<span class="label">Distanz</span>
|
||||
<div class="fig" style="font-size:52px;margin-top:10px">${t.distance_km != null ? de(t.distance_km) : "–"}<span class="unit">km</span></div>
|
||||
<div style="margin-top:15px">
|
||||
<button class="pill ${t.art === "arbeitsweg" ? "work" : ""}" data-art="${t.trip_id}"
|
||||
style="cursor:pointer;font-family:inherit">${esc(t.art)}</button></div>
|
||||
<span class="label" style="margin-top:12px">tippen zum Umschalten</span>
|
||||
<button class="pill" data-art="${t.trip_id}"
|
||||
style="cursor:pointer;font-family:inherit">${artText(t.art)}</button></div>
|
||||
</div>
|
||||
<div class="tile"><dl class="rows">
|
||||
<div class="row"><dt>Startort</dt><dd>${esc(t.start_address || "unbekannt")}<small>${dedat(new Date(t.ts_start))}</small></dd></div>
|
||||
@@ -4128,6 +4171,15 @@ function ereignisseVerdrahten() {
|
||||
if (liste) liste.innerHTML = entitaetZeilenMarkup(sucheFeld, key, setupSuchtext, wert);
|
||||
return;
|
||||
}
|
||||
// Gleicher Grund wie beim tankpreisfeld-Handler darunter: nur die
|
||||
// Anzeige patchen, kein render() - das würde das gerade bearbeitete Feld
|
||||
// neu erzeugen und den Fokus verlieren.
|
||||
const zeitfeldPraefix = e.target.dataset.fahrtzeitfeld;
|
||||
if (zeitfeldPraefix) {
|
||||
const anzeige = ROOT.getElementById(`${zeitfeldPraefix}DauerAnzeige`);
|
||||
if (anzeige) anzeige.textContent = fahrtDauerText(zeitfeldPraefix);
|
||||
return;
|
||||
}
|
||||
const praefix = e.target.dataset.tankpreisfeld;
|
||||
if (!praefix) return;
|
||||
const liter = parseFloat((ROOT.getElementById(`${praefix}Liter`).value || "0").replace(",", "."));
|
||||
|
||||
@@ -196,7 +196,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
|
||||
|
||||
.pill{font-size:12px;letter-spacing:.01em;text-transform:none;padding:5px 11px;
|
||||
border:0;background:var(--ios-fill);color:var(--fg2)}
|
||||
.pill.work{background:rgba(245,5,55,.16);color:var(--ios-tint)}
|
||||
|
||||
.status{padding:14px 0 2px}
|
||||
.status .t{font-size:16px}
|
||||
@@ -333,7 +332,12 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
|
||||
/* Fahrzeugbild randlos wie in einer Fahrzeug-App: der Platzhalter bleibt
|
||||
sichtbar, aber ohne technischen Strichrahmen. */
|
||||
.szene{margin-bottom:20px}
|
||||
.bildbox.bild-fehlt .platzhalter{border:0;background:var(--tile);border-radius:var(--r-tile)}
|
||||
/* --tile statt --ios-fill waere hier naheliegend, faellt im Tag-Theme aber
|
||||
auf reines Weiss (#FFFFFF) - ununterscheidbar von Canvas und den meisten
|
||||
Kacheln drumherum, die Box verschwand. --ios-fill ist in beiden Themes ein
|
||||
deutlich sichtbares transluzentes Grau, dieselbe Konvention wie
|
||||
.feld select/.aktion ("das hier ist eine Flaeche"). */
|
||||
.bildbox.bild-fehlt .platzhalter{border:0;background:var(--ios-fill);border-radius:var(--r-tile)}
|
||||
.platzhalter-datei{display:none}
|
||||
|
||||
/* Zeilen mit Wert plus Statuspunkt rechts (Sicherheitscheck) ruhiger setzen */
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1787000000}
|
||||
{"version": 1787009000}
|
||||
|
||||
@@ -676,7 +676,6 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
border-radius: var(--r-pill);
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.pill.work { color: var(--red); border-color: var(--red); }
|
||||
|
||||
/* ------------------------------------------------------------ Akkordeon */
|
||||
.acc-h {
|
||||
|
||||
Reference in New Issue
Block a user