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}