diff options
Diffstat (limited to 'web/templates.templ')
| -rw-r--r-- | web/templates.templ | 87 |
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) { |