Files
streba/src/routes/segments/[id]/+page.svelte
T

174 lines
4.2 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { enhance } from '$app/forms';
import MapSlot from '$lib/components/MapSlot.svelte';
import StatTile from '$lib/components/StatTile.svelte';
import { mapState } from '$lib/map-state.svelte';
import { fmtDate, fmtDistance, fmtElapsed, fmtElevation } from '$lib/format';
let { data } = $props();
const s = $derived(data.segment);
// show the segment on the persistent map: synthetic negative id so it can
// be highlighted without colliding with activity ids
$effect(() => {
mapState.setDetailTrack({
id: -s.id,
name: s.name,
type: 'segment',
latlngs: data.latlngs
});
mapState.setHighlight(-s.id);
mapState.setFocus(JSON.parse(s.bounds));
return () => mapState.reset();
});
</script>
<svelte:head>
<title>{s.name} · Streba</title>
</svelte:head>
<MapSlot />
<a class="back" href="/segments">← Segments</a>
<div class="head">
<div>
<h1>{s.name}</h1>
<p class="page-sub">Segment · created {fmtDate(s.created_at.slice(0, 10))}</p>
</div>
{#if data.canDelete}
<form
method="POST"
action="?/delete"
use:enhance={({ cancel }) => {
if (!confirm('Delete this segment and all its efforts?')) cancel();
}}
>
<button class="btn danger" type="submit">Delete</button>
</form>
{/if}
</div>
<div class="kpis">
<StatTile label="Distance" value={fmtDistance(s.distance_m)} />
<StatTile label="Ascent" value={fmtElevation(s.elev_gain_m)} />
<StatTile
label="Record"
value={data.leaderboard[0] ? fmtElapsed(data.leaderboard[0].best_s) : ''}
detail={data.leaderboard[0]?.username ?? 'no timed efforts yet'}
/>
</div>
<h2>Leaderboard</h2>
{#if data.leaderboard.length === 0}
<div class="card empty">No timed efforts yet - ride or run it with a GPS track.</div>
{:else}
<div class="card table-wrap">
<table>
<thead>
<tr><th>#</th><th>Athlete</th><th>Best time</th><th>Attempts</th><th>Date</th></tr>
</thead>
<tbody>
{#each data.leaderboard as row, i (row.user_id)}
<tr class:me={row.user_id === data.myUserId}>
<td>{i + 1}</td>
<td><a href="/users/{row.username}">{row.username}</a></td>
<td class="time">{fmtElapsed(row.best_s)}</td>
<td>{row.attempts}</td>
<td>{row.date ? fmtDate(row.date) : ''}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{#if data.efforts.length > 0}
<h2>All efforts ({data.efforts.length})</h2>
<div class="card table-wrap">
<table>
<thead>
<tr><th>Athlete</th><th>Time</th><th>Date</th><th>Activity</th></tr>
</thead>
<tbody>
{#each data.efforts as effort (effort.id)}
<tr class:me={effort.user_id === data.myUserId}>
<td><a href="/users/{effort.username}">{effort.username}</a></td>
<td class="time">{effort.elapsed_s !== null ? fmtElapsed(effort.elapsed_s) : ''}</td>
<td>{effort.date ? fmtDate(effort.date) : ''}</td>
<td><a href="/activities/{effort.activity_id}">{effort.activity_name}</a></td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
<style>
.back {
display: inline-block;
margin-bottom: 0.75rem;
font-size: 0.85rem;
font-weight: 500;
color: var(--text-secondary);
text-decoration: none;
padding: 0.25rem 0.6rem;
margin-left: -0.6rem;
border-radius: 0.45rem;
}
.back:hover {
background: var(--wash);
color: var(--text-primary);
}
.head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
}
.kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: 0.75rem;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
}
th {
text-align: left;
font-weight: 500;
font-size: 0.78rem;
color: var(--text-muted);
padding: 0.6rem 1rem 0.35rem;
border-bottom: 1px solid var(--border);
}
td {
padding: 0.5rem 1rem;
font-variant-numeric: tabular-nums;
}
tbody tr:not(:last-child) td {
border-bottom: 1px solid var(--border);
}
tr.me td {
background: var(--accent-wash);
}
td a {
color: var(--accent-strong);
text-decoration: none;
font-weight: 600;
}
.time {
font-weight: 600;
}
.empty {
padding: 1.75rem;
text-align: center;
color: var(--text-secondary);
}
</style>