show time of last match, parse server timestamps as UTC

The match column now shows "27 · 22.09. 12:00", with the full date and
time in the tooltip. Server timestamps are naive UTC; appending 'Z'
before parsing keeps the browser from treating them as local time,
which also fixes mail dates in the rule test results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 10:09:09 +02:00
co-authored by Claude Opus 5.5
parent 86ae896ae9
commit d189424ab3
+15 -4
View File
@@ -135,7 +135,7 @@
/* Kompakte Zeilenliste: eine Zeile pro Regel, feste Spalten */
.rules { background:#fff; border:1px solid var(--app-border, #e3e6ea); border-radius:var(--pico-border-radius); overflow:hidden; font-size:0.85rem; }
.rules .r { display:grid; grid-template-columns:34px 38px minmax(140px, 1.1fr) minmax(0, 2.4fr) minmax(120px, 1fr) 118px 84px;
.rules .r { display:grid; grid-template-columns:34px 38px minmax(140px, 1.1fr) minmax(0, 2.4fr) minmax(120px, 1fr) 140px 84px;
gap:0.6rem; align-items:center; padding:0.3rem 0.75rem; border-bottom:1px solid #eef0f3; min-height:36px; }
.rules .r:last-child { border-bottom:0; }
.rules .r:hover { background:#f8f9fb; }
@@ -266,10 +266,21 @@ function esc(s) {
function fieldLabel(v) { return FIELDS.find(x => x.value === v)?.label || v; }
function matchLabel(v) { return ALL_MATCH_TYPES.find(x => x.value === v)?.label || v; }
function actionLabel(v) { return ACTION_TYPES.find(x => x.value === v)?.label || v; }
function parseUtc(iso) {
// Der Server liefert UTC-Zeitstempel ohne Zeitzone — ohne 'Z' würde der
// Browser sie als Ortszeit lesen und die Uhrzeit um 1–2 h verschieben.
return new Date(/[zZ]|[+-]\d{2}:?\d{2}$/.test(iso) ? iso : iso + 'Z');
}
function fmtDate(iso) {
if (!iso) return '';
const d = new Date(iso);
return d.toLocaleDateString('de-DE', {day:'2-digit', month:'2-digit', year:'2-digit'});
return parseUtc(iso).toLocaleDateString('de-DE', {day:'2-digit', month:'2-digit', year:'2-digit'});
}
function fmtDateTime(iso, withYear = true) {
if (!iso) return '';
const d = parseUtc(iso);
const date = d.toLocaleDateString('de-DE', withYear ? {day:'2-digit', month:'2-digit', year:'2-digit'} : {day:'2-digit', month:'2-digit'});
const time = d.toLocaleTimeString('de-DE', {hour:'2-digit', minute:'2-digit'});
return `${date} ${time}`;
}
function fmtDateValue(v) {
// '2026-01-01' → '01.01.2026'; Zeitraum 'a,b' → 'a – b'
@@ -439,7 +450,7 @@ function renderFilters() {
const conds = [...(f.conditions || [])].sort((a, b) => (COND_ORDER[a.field] ?? 9) - (COND_ORDER[b.field] ?? 9));
const condTitle = conds.map(condText).join('\n');
const stat = f.match_count
? `<span class="c-stat" title="${f.match_count} Treffer, zuletzt ${fmtDate(f.last_match_at)}"><strong>${f.match_count}</strong> · ${fmtDate(f.last_match_at).slice(0, 6)}</span>`
? `<span class="c-stat" title="${f.match_count} Treffer, zuletzt am ${fmtDateTime(f.last_match_at)} Uhr"><strong>${f.match_count}</strong> · ${fmtDateTime(f.last_match_at, false)}</span>`
: `<span class="c-stat never" title="Noch kein Treffer seit Einführung der Statistik">—</span>`;
html += `
<div class="r${f.enabled ? '' : ' disabled'}${warnings.length ? ' warn' : ''}" ondblclick="editFilter(${f.id})">