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:
parent
70c2cdd101
commit
3ba8ec4c86
2 changed files with 47 additions and 6 deletions
|
|
@ -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). */
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue