Laenge und Hoehenmeter tragen dieselben Icons wie der Kopf der Trail-Seite. Dazu kommen Anzahl der Marker und Kommentare - und ein Punkt daneben, wenn davon etwas aus den letzten 14 Tagen stammt. Die blosse Zahl sagt nichts darueber, ob sich gerade etwas tut. Kommentare bekommen dafuer einen eigenen Store. Sie lagen bisher direkt in der Trail-Seite, was richtig war, solange es sie nur dort gab; fuer die Liste waere ein zweiter Ladeweg auf dieselben Daten einer zu viel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014dh9W1i7aLSdPYJzQPid5o
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
/**
|
||
* Formatierung von Zeiten. Bewusst hier und nicht im times-Store: Auch
|
||
* Komponenten ohne Store-Zugriff zeigen Dauern an, und die Darstellung soll
|
||
* überall dieselbe sein.
|
||
*/
|
||
|
||
/** Dauer in Sekunden als `m:ss.s`. */
|
||
export function formatDuration(seconds: number | undefined): string {
|
||
if (seconds == null || !Number.isFinite(seconds) || seconds < 0) return '0:00.0'
|
||
|
||
const m = Math.floor(seconds / 60)
|
||
const s = (seconds % 60).toFixed(1)
|
||
|
||
return `${m}:${s.padStart(4, '0')}`
|
||
}
|
||
|
||
/** Beschriftungen der Zeit-Status aus der Collection. */
|
||
export const TIME_STATUS_LABEL: Record<string, string> = {
|
||
active: 'Läuft',
|
||
finished: 'Beendet',
|
||
dnf: 'DNF',
|
||
dns: 'DNS',
|
||
dsq: 'DSQ',
|
||
}
|
||
|
||
/** 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' })
|
||
}
|
||
|
||
/** Heutiges Datum als `JJJJ-MM-TT` in lokaler Zeit (nicht UTC). */
|
||
export function todayISO(): string {
|
||
const d = new Date()
|
||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||
const day = String(d.getDate()).padStart(2, '0')
|
||
return `${d.getFullYear()}-${m}-${day}`
|
||
}
|
||
|
||
/**
|
||
* Datumsanteil eines PocketBase-Werts. Die kommen als
|
||
* `2026-09-12 00:00:00.000Z` zurück; für Vergleiche und `<input type="date">`
|
||
* zählt nur der Tag.
|
||
*/
|
||
export function dayOf(value: string | undefined): string {
|
||
if (!value) return ''
|
||
return value.slice(0, 10)
|
||
}
|
||
|
||
/** Ein Tag als `TT.MM.JJJJ`. */
|
||
export function formatDay(value: string | undefined): string {
|
||
const day = dayOf(value)
|
||
if (!day) return ''
|
||
|
||
const [y, m, d] = day.split('-')
|
||
return `${d}.${m}.${y}`
|
||
}
|
||
|
||
/**
|
||
* Zeitraum eines Events. Ohne Enddatum oder bei gleichem Tag steht nur ein
|
||
* Datum da; sonst „12.09.2026 – 14.09.2026".
|
||
*/
|
||
export function formatDateRange(starts: string | undefined, ends: string | undefined): string {
|
||
const from = formatDay(starts)
|
||
const to = formatDay(ends)
|
||
|
||
if (!from && !to) return ''
|
||
if (!from) return to
|
||
if (!to || to === from) return from
|
||
|
||
return `${from} – ${to}`
|
||
}
|
||
|
||
/**
|
||
* Standardfenster für „neu" in Übersichten. Zwei Wochen, weil ein Trail
|
||
* selten öfter befahren wird — kürzer wäre die Hervorhebung meist leer.
|
||
*/
|
||
export const RECENT_DAYS = 14
|
||
|
||
/** Liegt der Zeitpunkt innerhalb der letzten `days` Tage? */
|
||
export function isRecent(value: string | undefined, days = RECENT_DAYS): boolean {
|
||
if (!value) return false
|
||
|
||
const then = new Date(value).getTime()
|
||
if (!Number.isFinite(then)) return false
|
||
|
||
return Date.now() - then <= days * 24 * 60 * 60 * 1000
|
||
}
|