aboutsummaryrefslogtreecommitdiff
path: root/web/templates.templ
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates.templ')
-rw-r--r--web/templates.templ88
1 files changed, 87 insertions, 1 deletions
diff --git a/web/templates.templ b/web/templates.templ
index 14e9aab..d41dd39 100644
--- a/web/templates.templ
+++ b/web/templates.templ
@@ -15,7 +15,7 @@ templ Layout(title string, content templ.Component) {
<body>
<header class="site-header">
<a class="brand" href="/">biking_home</a>
- <nav class="nav"><a href="/">Rides</a><a href="/analytics">Analytics</a><a href="/preview">Preview</a><a href="/data-quality">Data quality</a><a href="/sync">Sync Strava</a></nav>
+ <nav class="nav"><a href="/">Rides</a><a href="/climbs">Climbs</a><a href="/analytics">Analytics</a><a href="/preview">Preview</a><a href="/data-quality">Data quality</a><a href="/sync">Sync Strava</a></nav>
</header>
<main class="container">
@content
@@ -485,3 +485,89 @@ templ DataQualityContent(data DataQualityPageData) {
}
</section>
}
+templ ClimbsPage(data ClimbsPageData) {
+ @Layout("Climbs", ClimbsContent(data))
+}
+
+templ ClimbsContent(data ClimbsPageData) {
+ <p class="eyebrow">Official climbs</p>
+ <section class="panel">
+ if len(data.Items) == 0 {
+ <div class="empty">No official climbs yet. Create one from a ride detail page.</div>
+ } else {
+ <table>
+ <thead><tr>
+ for _, header := range data.Headers {
+ <th class={ header.Class } scope="col" aria-sort={ header.AriaSort }><a class="sort-link" href={ templ.URL(header.URL) }>{ header.Label }<span class="sort-indicator" aria-hidden="true">{ header.Indicator }</span></a></th>
+ }
+ </tr></thead>
+ <tbody>
+ for _, item := range data.Items {
+ <tr><td><strong><a href={ templ.URL(item.URL) }>{ item.Climb.Name }</a></strong></td><td>{ item.Category }</td><td class="numeric">{ formatClimbCount(item.Climb.Attempts) }</td><td class="numeric">{ item.Distance }</td><td class="numeric">{ item.Slope }</td><td class="numeric">{ item.Cotacol }</td><td class="numeric">{ item.BestTime }</td></tr>
+ }
+ </tbody>
+ </table>
+ }
+ </section>
+}
+
+templ ClimbDetailPage(data ClimbDetailPageData) {
+ @Layout("Climb", ClimbDetailContent(data))
+}
+
+templ ClimbDetailContent(data ClimbDetailPageData) {
+ <div class="toolbar">
+ <div><h1 class="ride-detail-title">{ data.Climb.Name }</h1><p class="lead">Official climb ยท { data.Stats.Category }</p></div>
+ <div class="toolbar-actions"><a class="button secondary" href="/climbs">Back to climbs</a></div>
+ </div>
+ <section class="panel summary-panel">
+ <dl>
+ <div><dt>Attempts</dt><dd>{ data.Stats.Attempts }</dd></div>
+ <div><dt>Distance</dt><dd>{ data.Stats.Distance }</dd></div>
+ <div><dt>Elevation</dt><dd>{ data.Stats.ElevationG }</dd></div>
+ <div><dt>Avg slope</dt><dd>{ data.Stats.Slope }</dd></div>
+ <div><dt>Cotacol</dt><dd>{ data.Stats.Cotacol }</dd></div>
+ <div><dt>Best time</dt><dd>{ data.Stats.BestTime }</dd></div>
+ </dl>
+ </section>
+ <section class="panel map-panel">
+ <div id="climb-map" class="ride-map" role="img" aria-label="Official climb map"></div>
+ </section>
+ if len(data.Profile.Elevations) > 0 {
+ <section class="panel">
+ <div class="section-heading"><div><p class="eyebrow">Profile</p><h2>Median profile</h2></div></div>
+ <div class="profile-chart"><canvas id="climb-profile" data-climb-profile tabindex="0" role="img" aria-label={ officialProfileLabel(data.Climb.Name) }></canvas></div>
+ <p class="profile-hover" data-climb-profile-hover aria-live="polite">Hover or focus the profile to inspect the climb at a distance.</p>
+ <div class="official-climb-profile-legend" role="list" aria-label="Average slope color scale">
+ <span role="listitem"><i class="profile-band-swatch profile-band-downhill" aria-hidden="true"></i>&lt;=0%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-0-3" aria-hidden="true"></i>0โ€“3%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-3-6" aria-hidden="true"></i>3โ€“6%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-6-9" aria-hidden="true"></i>6โ€“9%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-9-plus" aria-hidden="true"></i>9%+</span>
+ </div>
+ </section>
+ }
+ <section class="panel">
+ <div class="section-heading">
+ <div><p class="eyebrow">History</p><h2>Attempts</h2></div>
+ <span class="section-count">{ formatClimbCount(len(data.Attempts)) }</span>
+ </div>
+ if len(data.Attempts) == 0 {
+ <div class="empty">No attempts recorded yet. Open a ride containing this climb to record one.</div>
+ } else {
+ <table>
+ <thead><tr><th>Date</th><th>Ride</th><th class="numeric">Time</th><th class="numeric">Distance</th><th class="numeric">Cotacol</th></tr></thead>
+ <tbody>
+ for _, attempt := range data.Attempts {
+ <tr><td>{ attempt.Date }</td><td><a href={ templ.URL(rideDetailURL(attempt.RideID)) }>{ attempt.RideName }</a></td><td class="numeric">{ attempt.Duration }</td><td class="numeric">{ attempt.Distance }</td><td class="numeric">{ attempt.Cotacol }</td></tr>
+ }
+ </tbody>
+ </table>
+ }
+ </section>
+ @templ.JSONScript("climb-profile-data", data.Profile)
+ @templ.JSONScript("climb-endpoints", data.Endpoints)
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
+ <script type="module" src="/static/climb-profile.js"></script>
+ <script type="module" src="/static/climb-map.js"></script>
+}