From 3ba8ec4c86291ac24ffdd2a00bc3dd3ecb83a02d Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Mon, 7 Sep 2026 22:09:41 +0200 Subject: [PATCH] feat: Trailstatus in relativer Zeit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein Datum muss man erst gegen den heutigen Tag rechnen, um zu wissen, ob eine Sperrung von gestern stammt oder vom letzten Sommer — der Abstand sagt es sofort. Das genaue Datum steht daneben im Tooltip und geht dadurch nicht verloren. date-fns lag bereits im Projekt, es brauchte keine neue Library. parseDate nimmt PocketBases "2026-09-12 08:30:00.000Z" mit Leerzeichen statt T; die meisten Browser schlucken das, garantiert ist es nicht. formatDate auf der Trail-Seite war eine Kopie von formatDateTime und kommt jetzt aus $lib/time. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr --- frontend/src/lib/time.ts | 36 +++++++++++++++++-- .../routes/dashboard/trails/[id]/+page.svelte | 17 ++++++--- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/time.ts b/frontend/src/lib/time.ts index df035cc..901e7e2 100644 --- a/frontend/src/lib/time.ts +++ b/frontend/src/lib/time.ts @@ -1,3 +1,6 @@ +import { formatDistanceToNow } from 'date-fns' +import { de } from 'date-fns/locale' + /** * Formatierung von Zeiten. Bewusst hier und nicht im times-Store: Auch * Komponenten ohne Store-Zugriff zeigen Dauern an, und die Darstellung soll @@ -23,10 +26,39 @@ export const TIME_STATUS_LABEL: Record = { dsq: 'DSQ', } +/** + * Ein PocketBase-Zeitstempel als Date. Die kommen als + * `2026-09-12 08:30:00.000Z` mit Leerzeichen statt `T` — die meisten Browser + * schlucken das, garantiert ist es nicht. + */ +function parseDate(value: string | undefined): Date | null { + if (!value) return null + + const date = new Date(value.replace(' ', 'T')) + return Number.isNaN(date.getTime()) ? null : date +} + /** Zeitpunkt als `TT.MM.JJJJ, HH:MM`. */ export function formatDateTime(iso: string | undefined): string { - if (!iso) return '—' - return new Date(iso).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }) + const date = parseDate(iso) + if (!date) return '—' + return date.toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }) +} + +/** + * Zeitpunkt als Abstand zu jetzt: „vor 3 Tagen". + * + * Für alles, woran die Frage „wie frisch ist das?" hängt. Ein Datum muss man + * erst gegen den heutigen Tag rechnen, um zu wissen, ob eine Sperrung von + * gestern stammt oder vom letzten Sommer — der Abstand sagt es sofort. Das + * genaue Datum gehört daneben in einen Tooltip, es geht dadurch nicht + * verloren. + */ +export function formatRelative(value: string | undefined): string { + const date = parseDate(value) + if (!date) return '—' + + return formatDistanceToNow(date, { addSuffix: true, locale: de }) } /** Heutiges Datum als `JJJJ-MM-TT` in lokaler Zeit (nicht UTC). */ diff --git a/frontend/src/routes/dashboard/trails/[id]/+page.svelte b/frontend/src/routes/dashboard/trails/[id]/+page.svelte index f1ab12e..5f4a3ce 100644 --- a/frontend/src/routes/dashboard/trails/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/trails/[id]/+page.svelte @@ -14,6 +14,7 @@ import IconPicker from '$lib/components/IconPicker.svelte' import Avatar from '$lib/components/Avatar.svelte' import { tooltip } from '$lib/stores/app.svelte' + import { formatDateTime, formatRelative } from '$lib/time' import { positionAtDistance } from '$lib/gpx' import { app } from '$lib/stores/app.svelte' import { Button } from '@/components/ui/button' @@ -276,9 +277,9 @@ } // --- Hilfsfunktionen ------------------------------------------------- - function formatDate(s: string) { - return new Date(s).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }) - } + // Dieselbe Formatierung stand hier als eigene Zeile Code und in + // $lib/time; jetzt kommt sie nur noch von dort. + const formatDate = formatDateTime async function setStatus(status: string) { if (!trail || status === trail.status) return @@ -368,9 +369,17 @@ {#if trail.status_changed} +

{statusInfo.label} - gemeldet am {formatDate(trail.status_changed)} + gemeldet + + {formatRelative(trail.status_changed)} + {#if trail.status_by} von {teams.userLabel(trail.status_by)} {/if}