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
|
* Formatierung von Zeiten. Bewusst hier und nicht im times-Store: Auch
|
||||||
* Komponenten ohne Store-Zugriff zeigen Dauern an, und die Darstellung soll
|
* 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',
|
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`. */
|
/** Zeitpunkt als `TT.MM.JJJJ, HH:MM`. */
|
||||||
export function formatDateTime(iso: string | undefined): string {
|
export function formatDateTime(iso: string | undefined): string {
|
||||||
if (!iso) return '—'
|
const date = parseDate(iso)
|
||||||
return new Date(iso).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' })
|
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). */
|
/** Heutiges Datum als `JJJJ-MM-TT` in lokaler Zeit (nicht UTC). */
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
import IconPicker from '$lib/components/IconPicker.svelte'
|
import IconPicker from '$lib/components/IconPicker.svelte'
|
||||||
import Avatar from '$lib/components/Avatar.svelte'
|
import Avatar from '$lib/components/Avatar.svelte'
|
||||||
import { tooltip } from '$lib/stores/app.svelte'
|
import { tooltip } from '$lib/stores/app.svelte'
|
||||||
|
import { formatDateTime, formatRelative } from '$lib/time'
|
||||||
import { positionAtDistance } from '$lib/gpx'
|
import { positionAtDistance } from '$lib/gpx'
|
||||||
import { app } from '$lib/stores/app.svelte'
|
import { app } from '$lib/stores/app.svelte'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
@ -276,9 +277,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Hilfsfunktionen -------------------------------------------------
|
// --- Hilfsfunktionen -------------------------------------------------
|
||||||
function formatDate(s: string) {
|
// Dieselbe Formatierung stand hier als eigene Zeile Code und in
|
||||||
return new Date(s).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' })
|
// $lib/time; jetzt kommt sie nur noch von dort.
|
||||||
}
|
const formatDate = formatDateTime
|
||||||
|
|
||||||
async function setStatus(status: string) {
|
async function setStatus(status: string) {
|
||||||
if (!trail || status === trail.status) return
|
if (!trail || status === trail.status) return
|
||||||
|
|
@ -368,9 +369,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if trail.status_changed}
|
{#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">
|
<p class="text-xs text-muted-foreground">
|
||||||
<span style="color: {statusInfo.color}">{statusInfo.label}</span>
|
<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}
|
{#if trail.status_by}
|
||||||
von {teams.userLabel(trail.status_by)}
|
von {teams.userLabel(trail.status_by)}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue