Add timezone preference across time displays

This commit is contained in:
2026-09-20 22:12:27 -04:00
parent 1e6ff3abbf
commit 5c5e75526d
28 changed files with 297 additions and 52 deletions
+3 -2
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { timeFormat } from '$ui/stores/time-zone.js';
import { onMount, untrack } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
@@ -186,7 +187,7 @@
<section class="rankings" class:compact={!fullTable} aria-labelledby="rankings-title" aria-busy={loading}>
<div class="rankings-header">
<div><h2 id="rankings-title">{metricLabel}</h2><p>{descriptions[metric]}</p></div>
<div class="refresh-status" role="status">{loading ? 'Refreshing…' : checkedAt ? `Checked ${checkedAt.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', second: '2-digit' })}` : ''}<span>Refreshes every 15 seconds</span></div>
<div class="refresh-status" role="status">{loading ? 'Refreshing…' : checkedAt ? `Checked ${$timeFormat.time(checkedAt)}` : ''}<span>Refreshes every 15 seconds</span></div>
</div>
{#if data.entries.length}
<div class="mobile-view"><button class="text-button" aria-pressed={fullTable} onclick={() => fullTable = !fullTable}>{fullTable ? 'Show selected stat only' : 'Show all stats and player IDs'}</button></div>
@@ -239,7 +240,7 @@
<p>Records include active games and completed games. Filters apply to totals and rankings; player search only filters the rankings. Players are identified by their GUID within each game, so name changes keep their records together.</p>
<p>Highest round includes mid-game joins. Total rounds counts round progression during observed participation, excluding earlier rounds. Joining at round 8 records a highest round of 8; reaching round 9 adds one to total rounds. The fleet total adds all players’ rounds.</p>
<p>Kills, headshots, revives, and downs accumulate across recorded games. Highest reported score is the largest observed points balance, not the total points a player has earned. Equal values share a rank.</p>
<p>Records cover telemetry saved since tracking began; older unrecorded games cannot be included.{#if data?.summary.firstTrackedAt}{' '}Tracking began <time datetime={data.summary.firstTrackedAt}>{new Date(data.summary.firstTrackedAt).toLocaleString()}</time> for this selection.{/if}{#if data?.summary.updatedAt}{' '}Latest report: <time datetime={data.summary.updatedAt}>{new Date(data.summary.updatedAt).toLocaleString()}</time>.{/if}</p>
<p>Records cover telemetry saved since tracking began; older unrecorded games cannot be included.{#if data?.summary.firstTrackedAt}{' '}Tracking began <time datetime={data.summary.firstTrackedAt}>{$timeFormat.dateTime(data.summary.firstTrackedAt)}</time> for this selection.{/if}{#if data?.summary.updatedAt}{' '}Latest report: <time datetime={data.summary.updatedAt}>{$timeFormat.dateTime(data.summary.updatedAt)}</time>.{/if}</p>
</div>
</details>
</section>
@@ -1,4 +1,5 @@
<script lang="ts">
import { timeFormat } from '$ui/stores/time-zone.js';
import { invalidateAll } from '$app/navigation';
import { leaderboardMetrics, type PlayerProfile, type LeaderboardMetric } from '$lib/leaderboard.js';
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
@@ -9,7 +10,7 @@
const profile = $derived(data.profile);
const stats = $derived(profile.stats);
const number = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
const date = (value: string) => new Date(value).toLocaleString('en-US', { timeZone: 'UTC' }) + ' UTC';
const date = (value: string) => $timeFormat.dateTime(value);
const ratio = (value: number, total: number, percent = false) => total ? `${number.format(value / total * (percent ? 100 : 1))}${percent ? '%' : ''}` : '—';
const historyColumns = ['highestRound', 'kills', 'roundsSurvived', 'headshots', 'revives', 'downs', 'bestScore'] as const satisfies readonly LeaderboardMetric[];
let refreshing = $state(false);