feat: Trailstatus in relativer Zeit

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
This commit is contained in:
Daniel Michelberger 2026-09-07 22:09:41 +02:00
parent 70c2cdd101
commit 3ba8ec4c86
2 changed files with 47 additions and 6 deletions

View file

@ -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<string, string> = {
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). */

View file

@ -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 @@
</div>
{#if trail.status_changed}
<!--
Relativ, weil am Zustand die Frage „wie alt ist die
Meldung?" haengt und nicht „welcher Tag war das?". Das
genaue Datum steht im Tooltip.
-->
<p class="text-xs text-muted-foreground">
<span style="color: {statusInfo.color}">{statusInfo.label}</span>
gemeldet am {formatDate(trail.status_changed)}
gemeldet
<span use:tooltip={{ content: formatDateTime(trail.status_changed) }}>
{formatRelative(trail.status_changed)}
</span>
{#if trail.status_by}
von {teams.userLabel(trail.status_by)}
{/if}