summaryrefslogtreecommitdiff
path: root/web/templates.templ
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates.templ')
-rw-r--r--web/templates.templ87
1 files changed, 76 insertions, 11 deletions
diff --git a/web/templates.templ b/web/templates.templ
index f3f07e7..8a987fc 100644
--- a/web/templates.templ
+++ b/web/templates.templ
@@ -22,34 +22,99 @@ templ Layout(title string, content templ.Component) {
</html>
}
-templ RidesPage(items []RideView, headers []RideSortHeader) {
- @Layout("Rides", RidesContent(items, headers))
+templ RidesPage(data RidesPageData) {
+ @Layout("Rides", RidesContent(data))
}
-templ RidesContent(items []RideView, headers []RideSortHeader) {
+templ RidesContent(data RidesPageData) {
<p class="eyebrow">Ride library</p>
- <div class="toolbar">
- <div><h1>All rides</h1><p class="lead">Your imported rides, ready for climb analysis.</p></div>
- <a class="button" href="/sync">Sync Strava</a>
- </div>
+ <section class="panel ride-filters-panel">
+ <form id="ride-filters" class="ride-filters" method="get" action="/" aria-label="Ride filters">
+ <div class="section-heading">
+ <div><p class="eyebrow">Refine</p><h2>Filter rides</h2></div>
+ <div class="ride-filter-actions"><button class="button" type="submit">Apply filters</button><a class="button secondary" href={ templ.URL(data.Filters.ResetURL) }>Reset</a></div>
+ </div>
+ <input type="hidden" name="sort" value={ data.Sort.Column }/>
+ <input type="hidden" name="dir" value={ rideSortDirection(data.Sort) }/>
+ <div class="ride-filter-ranges">
+ <fieldset class="ride-filter-group">
+ <legend>Distance</legend>
+ <div class="range-values"><output data-range-value="min" for="distance-min">{ formatRideFilterDistance(data.Filters.DistanceMinKm) }</output><span aria-hidden="true">to</span><output data-range-value="max" for="distance-max">{ formatRideFilterDistance(data.Filters.DistanceMaxKm) }</output></div>
+ <div class="range-control" data-range-filter="distance" data-range-format="distance">
+ <div class="range-track" data-range-track></div>
+ <input id="distance-min" class="range-input range-input-min" type="range" name="distance_min" min={ formatRideFilterInt(rideFilterDistanceMinKm) } max={ formatRideFilterInt(rideFilterDistanceMaxKm) } step={ formatRideFilterInt(rideFilterDistanceStepKm) } value={ formatRideFilterInt(data.Filters.DistanceMinKm) } data-range-min aria-label="Minimum distance"/>
+ <input id="distance-max" class="range-input range-input-max" type="range" name="distance_max" min={ formatRideFilterInt(rideFilterDistanceMinKm) } max={ formatRideFilterInt(rideFilterDistanceMaxKm) } step={ formatRideFilterInt(rideFilterDistanceStepKm) } value={ formatRideFilterInt(data.Filters.DistanceMaxKm) } data-range-max aria-label="Maximum distance"/>
+ </div>
+ </fieldset>
+ <fieldset class="ride-filter-group">
+ <legend>Moving duration</legend>
+ <div class="range-values"><output data-range-value="min" for="duration-min">{ formatRideFilterDuration(data.Filters.DurationMinMinutes) }</output><span aria-hidden="true">to</span><output data-range-value="max" for="duration-max">{ formatRideFilterDuration(data.Filters.DurationMaxMinutes) }</output></div>
+ <div class="range-control" data-range-filter="duration" data-range-format="duration">
+ <div class="range-track" data-range-track></div>
+ <input id="duration-min" class="range-input range-input-min" type="range" name="duration_min" min={ formatRideFilterInt(rideFilterDurationMinMinutes) } max={ formatRideFilterInt(rideFilterDurationMaxMinutes) } step={ formatRideFilterInt(rideFilterDurationStepMinutes) } value={ formatRideFilterInt(data.Filters.DurationMinMinutes) } data-range-min aria-label="Minimum moving duration"/>
+ <input id="duration-max" class="range-input range-input-max" type="range" name="duration_max" min={ formatRideFilterInt(rideFilterDurationMinMinutes) } max={ formatRideFilterInt(rideFilterDurationMaxMinutes) } step={ formatRideFilterInt(rideFilterDurationStepMinutes) } value={ formatRideFilterInt(data.Filters.DurationMaxMinutes) } data-range-max aria-label="Maximum moving duration"/>
+ </div>
+ </fieldset>
+ <fieldset class="ride-filter-group">
+ <legend>Cotacol</legend>
+ <div class="range-values"><output data-range-value="min" for="cotacol-min">{ formatRideFilterCotacol(data.Filters.CotacolMinPoints) }</output><span aria-hidden="true">to</span><output data-range-value="max" for="cotacol-max">{ formatRideFilterCotacol(data.Filters.CotacolMaxPoints) }</output></div>
+ <div class="range-control" data-range-filter="cotacol" data-range-format="cotacol">
+ <div class="range-track" data-range-track></div>
+ <input id="cotacol-min" class="range-input range-input-min" type="range" name="cotacol_min" min={ formatRideFilterInt(rideFilterCotacolMinPoints) } max={ formatRideFilterInt(rideFilterCotacolMaxPoints) } step={ formatRideFilterInt(rideFilterCotacolStepPoints) } value={ formatRideFilterInt(data.Filters.CotacolMinPoints) } data-range-min aria-label="Minimum Cotacol"/>
+ <input id="cotacol-max" class="range-input range-input-max" type="range" name="cotacol_max" min={ formatRideFilterInt(rideFilterCotacolMinPoints) } max={ formatRideFilterInt(rideFilterCotacolMaxPoints) } step={ formatRideFilterInt(rideFilterCotacolStepPoints) } value={ formatRideFilterInt(data.Filters.CotacolMaxPoints) } data-range-max aria-label="Maximum Cotacol"/>
+ </div>
+ </fieldset>
+ </div>
+ <div class="ride-filter-meta">
+ <fieldset class="ride-filter-group date-filter-group">
+ <legend>Started</legend>
+ <div class="date-filter-inputs"><label for="ride-date-from">From<input id="ride-date-from" type="date" name="date_from" value={ data.Filters.DateFrom }/></label><label for="ride-date-to">To<input id="ride-date-to" type="date" name="date_to" value={ data.Filters.DateTo }/></label></div>
+ </fieldset>
+ <label class="route-filter" for="ride-route">Route
+ <select id="ride-route" name="route">
+ if data.Filters.Route == rideRouteFilterAny {
+ <option value="" selected>Any route</option>
+ } else {
+ <option value="">Any route</option>
+ }
+ if data.Filters.Route == rideRouteFilterAvailable {
+ <option value={ rideRouteFilterAvailable } selected>Recorded route</option>
+ } else {
+ <option value={ rideRouteFilterAvailable }>Recorded route</option>
+ }
+ if data.Filters.Route == rideRouteFilterMissing {
+ <option value={ rideRouteFilterMissing } selected>Metadata only</option>
+ } else {
+ <option value={ rideRouteFilterMissing }>Metadata only</option>
+ }
+ </select>
+ </label>
+ </div>
+ </form>
+ </section>
<section class="panel">
- if len(items) == 0 {
- <div class="empty">No rides stored yet. <a href="/sync">Import your first Strava rides.</a></div>
+ if len(data.Items) == 0 {
+ if data.Filters.Active {
+ <div class="empty">No rides match the selected filters. <a href={ templ.URL(data.Filters.ResetURL) }>Clear filters.</a></div>
+ } else {
+ <div class="empty">No rides stored yet. <a href="/sync">Import your first Strava rides.</a></div>
+ }
} else {
<table>
<thead><tr>
- for _, header := range headers {
+ 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 items {
+ for _, item := range data.Items {
<tr><td><strong><a href={ templ.URL(rideDetailURL(item.ID)) }>{ item.Name }</a></strong><br/><small>{ item.Type }</small></td><td>{ formatRideDate(item.StartDate) }</td><td class="numeric">{ formatDistance(item.DistanceM) }</td><td class="numeric">{ formatDuration(item.MovingTimeS) }</td><td class="numeric">{ formatElevation(item.TotalElevationGainM) }</td><td class="numeric">{ item.Cotacol }</td><td class="numeric">{ item.CotacolPer100Km }</td></tr>
}
</tbody>
</table>
}
</section>
+ <script type="module" src="/static/ride-filters.js"></script>
}
templ RideDetailPage(data RideDetailView) {