diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-18 21:32:28 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-18 21:32:28 +0200 |
| commit | 992c36c6c7ce7e1e732866b3cd4fb8269a905875 (patch) | |
| tree | 15b7ae06b14341929e555ebc126f07d963213104 | |
| parent | 51613971c1e25dedbc808c66b79d15111d250160 (diff) | |
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | web/frontend/ride-filters-logic.test.ts | 14 | ||||
| -rw-r--r-- | web/frontend/ride-filters-logic.ts | 15 | ||||
| -rw-r--r-- | web/frontend/ride-filters.ts | 49 | ||||
| -rw-r--r-- | web/ride-filters.go | 307 | ||||
| -rw-r--r-- | web/ride-filters_test.go | 256 | ||||
| -rw-r--r-- | web/server.go | 12 | ||||
| -rw-r--r-- | web/server_test.go | 3 | ||||
| -rw-r--r-- | web/static/style.css | 27 | ||||
| -rw-r--r-- | web/templates.templ | 87 | ||||
| -rw-r--r-- | web/templates_templ.go | 1010 | ||||
| -rw-r--r-- | web/views.go | 26 |
12 files changed, 1560 insertions, 248 deletions
@@ -17,6 +17,7 @@ A Go toolkit for analyzing cycling rides from GPX exports: parse rides, detect c - **Strava stream metrics** — preserves heart rate, cadence, and power in Garmin-compatible GPX files and in-memory ride columns - **Metadata-only ride imports** — stores Strava activity statistics for rides without GPS tracks, without Cotacol or route data - **Ride table sorting** — sorts every ride-library column through clickable server-side headers and query parameters +- **Ride library filters** — filters rides by distance, moving duration, start date, Cotacol score, and route availability through shareable query URLs - **Ride detail maps** — opens a stored ride with its recorded route on an interactive OpenStreetMap map - **Strava import progress** — streams live import progress with an aggregate progress bar and prevents overlapping syncs @@ -144,7 +145,6 @@ TypeScript checks can also be run directly with `npm run check`, covering format - Destination via Start - Planning rides - Mountain pass from OSM -- Rides filters (duration slider, distance slider) ## Resources diff --git a/web/frontend/ride-filters-logic.test.ts b/web/frontend/ride-filters-logic.test.ts new file mode 100644 index 0000000..5225028 --- /dev/null +++ b/web/frontend/ride-filters-logic.test.ts @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { formatRideFilterValue, normalizeRideFilterRange } from "./ride-filters-logic.ts"; + +test("formats ride filter values", () => { + assert.equal(formatRideFilterValue("distance", 120), "120 km"); + assert.equal(formatRideFilterValue("duration", 150), "2h 30m"); + assert.equal(formatRideFilterValue("cotacol", 500), "500 points"); +}); + +test("keeps range handles ordered when the minimum moves past the maximum", () => { + assert.deepEqual(normalizeRideFilterRange(200, 100, "min"), { min: 100, max: 100 }); + assert.deepEqual(normalizeRideFilterRange(200, 100, "max"), { min: 200, max: 200 }); +}); diff --git a/web/frontend/ride-filters-logic.ts b/web/frontend/ride-filters-logic.ts new file mode 100644 index 0000000..f3593c1 --- /dev/null +++ b/web/frontend/ride-filters-logic.ts @@ -0,0 +1,15 @@ +export type RideFilterRangeFormat = "distance" | "duration" | "cotacol"; +export type RideFilterRangeHandle = "min" | "max"; + +export const formatRideFilterValue = (format: RideFilterRangeFormat, value: number): string => { + if (format === "distance") return `${value} km`; + if (format === "cotacol") return `${value} points`; + const hours = Math.floor(value / 60); + const minutes = value % 60; + return `${hours}h ${String(minutes).padStart(2, "0")}m`; +}; + +export const normalizeRideFilterRange = (min: number, max: number, changed: RideFilterRangeHandle): { min: number; max: number } => { + if (min <= max) return { min, max }; + return changed === "min" ? { min: max, max } : { min, max: min }; +}; diff --git a/web/frontend/ride-filters.ts b/web/frontend/ride-filters.ts new file mode 100644 index 0000000..24c23e8 --- /dev/null +++ b/web/frontend/ride-filters.ts @@ -0,0 +1,49 @@ +import { requireElement } from "./dom.js"; +import { + formatRideFilterValue, + normalizeRideFilterRange, + type RideFilterRangeFormat, + type RideFilterRangeHandle, +} from "./ride-filters-logic.js"; + +const mountRangeFilter = (container: HTMLElement): void => { + const format = container.dataset.rangeFormat as RideFilterRangeFormat | undefined; + if (!format) throw new Error("Missing ride filter range format"); + const minInput = requireElement(container.querySelector<HTMLInputElement>("[data-range-min]"), "ride filter minimum input"); + const maxInput = requireElement(container.querySelector<HTMLInputElement>("[data-range-max]"), "ride filter maximum input"); + const track = requireElement(container.querySelector<HTMLElement>("[data-range-track]"), "ride filter range track"); + const group = requireElement(container.parentElement, "ride filter group"); + const minOutput = requireElement(group.querySelector<HTMLOutputElement>('[data-range-value="min"]'), "ride filter minimum output"); + const maxOutput = requireElement(group.querySelector<HTMLOutputElement>('[data-range-value="max"]'), "ride filter maximum output"); + const lowerBound = Number(minInput.min); + const upperBound = Number(minInput.max); + + const update = (changed?: RideFilterRangeHandle): void => { + const range = normalizeRideFilterRange(Number(minInput.value), Number(maxInput.value), changed ?? "min"); + minInput.value = String(range.min); + maxInput.value = String(range.max); + const span = Math.max(upperBound - lowerBound, 1); + const start = ((range.min - lowerBound) / span) * 100; + const end = ((range.max - lowerBound) / span) * 100; + track.style.setProperty("--range-start", `${start}%`); + track.style.setProperty("--range-end", `${end}%`); + const minLabel = formatRideFilterValue(format, range.min); + const maxLabel = formatRideFilterValue(format, range.max); + minOutput.value = minLabel; + maxOutput.value = maxLabel; + minOutput.textContent = minLabel; + maxOutput.textContent = maxLabel; + minInput.setAttribute("aria-valuetext", minLabel); + maxInput.setAttribute("aria-valuetext", maxLabel); + }; + + minInput.addEventListener("input", () => update("min")); + maxInput.addEventListener("input", () => update("max")); + update(); +}; + +export const mountRideFilters = (): void => { + document.querySelectorAll<HTMLElement>("[data-range-filter]").forEach(mountRangeFilter); +}; + +mountRideFilters(); diff --git a/web/ride-filters.go b/web/ride-filters.go new file mode 100644 index 0000000..189a72e --- /dev/null +++ b/web/ride-filters.go @@ -0,0 +1,307 @@ +package web + +import ( + "net/url" + "strconv" + "time" + + "github.com/martinlehoux/biking_home/ride" + "github.com/martinlehoux/biking_home/rides" +) + +const ( + rideFilterDistanceMinKm = 10 + rideFilterDistanceMaxKm = 300 + rideFilterDistanceStepKm = 10 + rideFilterDurationMinMinutes = 0 + rideFilterDurationMaxMinutes = 24 * 60 + rideFilterDurationStepMinutes = 30 + rideFilterCotacolMinPoints = 0 + rideFilterCotacolMaxPoints = 5000 + rideFilterCotacolStepPoints = 50 +) + +const ( + rideFilterDistanceMinParam = "distance_min" + rideFilterDistanceMaxParam = "distance_max" + rideFilterDurationMinParam = "duration_min" + rideFilterDurationMaxParam = "duration_max" + rideFilterDateFromParam = "date_from" + rideFilterDateToParam = "date_to" + rideFilterCotacolMinParam = "cotacol_min" + rideFilterCotacolMaxParam = "cotacol_max" + rideFilterRouteParam = "route" +) + +const ( + rideRouteFilterAny = "" + rideRouteFilterAvailable = "available" + rideRouteFilterMissing = "missing" +) + +type RideFilters struct { + DistanceMinKm *int + DistanceMaxKm *int + DurationMinMinutes *int + DurationMaxMinutes *int + DateFrom *time.Time + DateToExclusive *time.Time + CotacolMinPoints *int + CotacolMaxPoints *int + Route string +} + +type RideFilterView struct { + Active bool + DistanceMinKm int + DistanceMaxKm int + DurationMinMinutes int + DurationMaxMinutes int + DateFrom string + DateTo string + CotacolMinPoints int + CotacolMaxPoints int + Route string + ResetURL string +} + +func parseRideFilters(query url.Values) RideFilters { + filters := RideFilters{ + DistanceMinKm: parseRideFilterInt(query.Get(rideFilterDistanceMinParam), rideFilterDistanceMinKm, rideFilterDistanceMaxKm, rideFilterDistanceStepKm), + DistanceMaxKm: parseRideFilterInt(query.Get(rideFilterDistanceMaxParam), rideFilterDistanceMinKm, rideFilterDistanceMaxKm, rideFilterDistanceStepKm), + DurationMinMinutes: parseRideFilterInt(query.Get(rideFilterDurationMinParam), rideFilterDurationMinMinutes, rideFilterDurationMaxMinutes, rideFilterDurationStepMinutes), + DurationMaxMinutes: parseRideFilterInt(query.Get(rideFilterDurationMaxParam), rideFilterDurationMinMinutes, rideFilterDurationMaxMinutes, rideFilterDurationStepMinutes), + CotacolMinPoints: parseRideFilterInt(query.Get(rideFilterCotacolMinParam), rideFilterCotacolMinPoints, rideFilterCotacolMaxPoints, rideFilterCotacolStepPoints), + CotacolMaxPoints: parseRideFilterInt(query.Get(rideFilterCotacolMaxParam), rideFilterCotacolMinPoints, rideFilterCotacolMaxPoints, rideFilterCotacolStepPoints), + Route: parseRideRouteFilter(query.Get(rideFilterRouteParam)), + } + filters.DateFrom = parseRideFilterDate(query.Get(rideFilterDateFromParam), false) + filters.DateToExclusive = parseRideFilterDate(query.Get(rideFilterDateToParam), true) + filters.DistanceMinKm = clearRideFilterBound(filters.DistanceMinKm, rideFilterDistanceMinKm) + filters.DistanceMaxKm = clearRideFilterBound(filters.DistanceMaxKm, rideFilterDistanceMaxKm) + filters.DurationMinMinutes = clearRideFilterBound(filters.DurationMinMinutes, rideFilterDurationMinMinutes) + filters.DurationMaxMinutes = clearRideFilterBound(filters.DurationMaxMinutes, rideFilterDurationMaxMinutes) + filters.CotacolMinPoints = clearRideFilterBound(filters.CotacolMinPoints, rideFilterCotacolMinPoints) + filters.CotacolMaxPoints = clearRideFilterBound(filters.CotacolMaxPoints, rideFilterCotacolMaxPoints) + if filters.DateFrom != nil && filters.DateToExclusive != nil && !filters.DateFrom.Before(*filters.DateToExclusive) { + filters.DateFrom = nil + filters.DateToExclusive = nil + } + if filters.DistanceMinKm != nil && filters.DistanceMaxKm != nil && *filters.DistanceMinKm > *filters.DistanceMaxKm { + filters.DistanceMinKm = nil + filters.DistanceMaxKm = nil + } + if filters.DurationMinMinutes != nil && filters.DurationMaxMinutes != nil && *filters.DurationMinMinutes > *filters.DurationMaxMinutes { + filters.DurationMinMinutes = nil + filters.DurationMaxMinutes = nil + } + if filters.CotacolMinPoints != nil && filters.CotacolMaxPoints != nil && *filters.CotacolMinPoints > *filters.CotacolMaxPoints { + filters.CotacolMinPoints = nil + filters.CotacolMaxPoints = nil + } + return filters +} + +func clearRideFilterBound(value *int, bound int) *int { + if value != nil && *value == bound { + return nil + } + return value +} + +func parseRideFilterInt(value string, min, max, step int) *int { + if value == "" { + return nil + } + parsed, err := strconv.Atoi(value) + if err != nil || parsed < min || parsed > max || (parsed-min)%step != 0 { + return nil + } + return &parsed +} + +func parseRideFilterDate(value string, end bool) *time.Time { + if value == "" { + return nil + } + parsed, err := time.Parse(dateFormat, value) + if err != nil { + return nil + } + if end { + parsed = parsed.AddDate(0, 0, 1) + } + return &parsed +} + +func parseRideRouteFilter(value string) string { + if value == rideRouteFilterAvailable || value == rideRouteFilterMissing { + return value + } + return rideRouteFilterAny +} + +func buildRideFilterView(filters RideFilters, sort RideSort) RideFilterView { + dateFrom := "" + if filters.DateFrom != nil { + dateFrom = filters.DateFrom.Format(dateFormat) + } + dateTo := "" + if filters.DateToExclusive != nil { + dateTo = filters.DateToExclusive.AddDate(0, 0, -1).Format(dateFormat) + } + return RideFilterView{ + Active: filters.Active(), + DistanceMinKm: rideFilterValue(filters.DistanceMinKm, rideFilterDistanceMinKm), + DistanceMaxKm: rideFilterValue(filters.DistanceMaxKm, rideFilterDistanceMaxKm), + DurationMinMinutes: rideFilterValue(filters.DurationMinMinutes, rideFilterDurationMinMinutes), + DurationMaxMinutes: rideFilterValue(filters.DurationMaxMinutes, rideFilterDurationMaxMinutes), + DateFrom: dateFrom, + DateTo: dateTo, + CotacolMinPoints: rideFilterValue(filters.CotacolMinPoints, rideFilterCotacolMinPoints), + CotacolMaxPoints: rideFilterValue(filters.CotacolMaxPoints, rideFilterCotacolMaxPoints), + Route: filters.Route, + ResetURL: rideFiltersResetURL(sort), + } +} + +func rideFilterValue(value *int, fallback int) int { + if value == nil { + return fallback + } + return *value +} + +func formatRideFilterInt(value int) string { + return strconv.Itoa(value) +} + +func formatRideFilterDistance(value int) string { + return strconv.Itoa(value) + " km" +} + +func formatRideFilterDuration(value int) string { + hours := value / 60 + minutes := value % 60 + return strconv.Itoa(hours) + "h " + formatTwoDigitNumber(minutes) + "m" +} + +func formatRideFilterCotacol(value int) string { + return strconv.Itoa(value) + " points" +} + +func formatTwoDigitNumber(value int) string { + if value < 10 { + return "0" + strconv.Itoa(value) + } + return strconv.Itoa(value) +} + +func rideFilterQuery(filters RideFilters) url.Values { + query := url.Values{} + if filters.DistanceMinKm != nil { + query.Set(rideFilterDistanceMinParam, formatRideFilterInt(*filters.DistanceMinKm)) + } + if filters.DistanceMaxKm != nil { + query.Set(rideFilterDistanceMaxParam, formatRideFilterInt(*filters.DistanceMaxKm)) + } + if filters.DurationMinMinutes != nil { + query.Set(rideFilterDurationMinParam, formatRideFilterInt(*filters.DurationMinMinutes)) + } + if filters.DurationMaxMinutes != nil { + query.Set(rideFilterDurationMaxParam, formatRideFilterInt(*filters.DurationMaxMinutes)) + } + if filters.DateFrom != nil { + query.Set(rideFilterDateFromParam, filters.DateFrom.Format(dateFormat)) + } + if filters.DateToExclusive != nil { + query.Set(rideFilterDateToParam, filters.DateToExclusive.AddDate(0, 0, -1).Format(dateFormat)) + } + if filters.CotacolMinPoints != nil { + query.Set(rideFilterCotacolMinParam, formatRideFilterInt(*filters.CotacolMinPoints)) + } + if filters.CotacolMaxPoints != nil { + query.Set(rideFilterCotacolMaxParam, formatRideFilterInt(*filters.CotacolMaxPoints)) + } + if filters.Route != rideRouteFilterAny { + query.Set(rideFilterRouteParam, filters.Route) + } + return query +} + +func rideFiltersResetURL(sort RideSort) string { + if sort.Column == rideSortStarted && sort.Descending { + return "/" + } + query := url.Values{} + query.Set("sort", sort.Column) + query.Set("dir", rideSortDirection(sort)) + return "/?" + query.Encode() +} + +func (filters RideFilters) Active() bool { + return filters.DistanceMinKm != nil || + filters.DistanceMaxKm != nil || + filters.DurationMinMinutes != nil || + filters.DurationMaxMinutes != nil || + filters.DateFrom != nil || + filters.DateToExclusive != nil || + filters.CotacolMinPoints != nil || + filters.CotacolMaxPoints != nil || + filters.Route != rideRouteFilterAny +} + +func filterRides(items []rides.Ride, filters RideFilters) []rides.Ride { + if !filters.Active() { + return items + } + filtered := make([]rides.Ride, 0, len(items)) + for _, item := range items { + if rideMatchesFilters(item, filters) { + filtered = append(filtered, item) + } + } + return filtered +} + +func rideMatchesFilters(item rides.Ride, filters RideFilters) bool { + if filters.DistanceMinKm != nil && item.DistanceM < float64(*filters.DistanceMinKm)*1000 { + return false + } + if filters.DistanceMaxKm != nil && item.DistanceM > float64(*filters.DistanceMaxKm)*1000 { + return false + } + if filters.DurationMinMinutes != nil && item.MovingTimeS < int64(*filters.DurationMinMinutes)*60 { + return false + } + if filters.DurationMaxMinutes != nil && item.MovingTimeS > int64(*filters.DurationMaxMinutes)*60 { + return false + } + startDate := item.StartDate.UTC() + if filters.DateFrom != nil && startDate.Before(*filters.DateFrom) { + return false + } + if filters.DateToExclusive != nil && !startDate.Before(*filters.DateToExclusive) { + return false + } + if filters.CotacolMinPoints != nil || filters.CotacolMaxPoints != nil { + cotacol, ready := item.CotacolScore() + if !ready || item.CotacolAlgorithmVersion() != ride.CotacolAlgorithmVersion || item.DistanceM <= 0 { + return false + } + if filters.CotacolMinPoints != nil && cotacol < float64(*filters.CotacolMinPoints) { + return false + } + if filters.CotacolMaxPoints != nil && cotacol > float64(*filters.CotacolMaxPoints) { + return false + } + } + switch filters.Route { + case rideRouteFilterAvailable: + return item.GPXPath != "" + case rideRouteFilterMissing: + return item.GPXPath == "" + default: + return true + } +} diff --git a/web/ride-filters_test.go b/web/ride-filters_test.go new file mode 100644 index 0000000..c7b9133 --- /dev/null +++ b/web/ride-filters_test.go @@ -0,0 +1,256 @@ +package web + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "github.com/martinlehoux/biking_home/ride" + "github.com/martinlehoux/biking_home/rides" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseRideFilters(t *testing.T) { + query := url.Values{ + rideFilterDistanceMinParam: {"20"}, + rideFilterDistanceMaxParam: {"100"}, + rideFilterDurationMinParam: {"30"}, + rideFilterDurationMaxParam: {"180"}, + rideFilterDateFromParam: {"2026-08-01"}, + rideFilterDateToParam: {"2026-08-31"}, + rideFilterCotacolMinParam: {"50"}, + rideFilterCotacolMaxParam: {"500"}, + rideFilterRouteParam: {rideRouteFilterAvailable}, + } + + filters := parseRideFilters(query) + + assert.Equal(t, 20, *filters.DistanceMinKm) + assert.Equal(t, 100, *filters.DistanceMaxKm) + assert.Equal(t, 30, *filters.DurationMinMinutes) + assert.Equal(t, 180, *filters.DurationMaxMinutes) + assert.Equal(t, "2026-08-01T00:00:00Z", filters.DateFrom.Format(time.RFC3339)) + assert.Equal(t, "2026-09-01T00:00:00Z", filters.DateToExclusive.Format(time.RFC3339)) + assert.Equal(t, 50, *filters.CotacolMinPoints) + assert.Equal(t, 500, *filters.CotacolMaxPoints) + assert.Equal(t, rideRouteFilterAvailable, filters.Route) + assert.True(t, filters.Active()) +} + +func TestParseRideFiltersIgnoresInvalidValues(t *testing.T) { + query := url.Values{ + rideFilterDistanceMinParam: {"15"}, + rideFilterDistanceMaxParam: {"305"}, + rideFilterDurationMinParam: {"31"}, + rideFilterDurationMaxParam: {"31"}, + rideFilterDateFromParam: {"2026-09-01"}, + rideFilterDateToParam: {"2026-08-01"}, + rideFilterCotacolMinParam: {"25"}, + rideFilterCotacolMaxParam: {"25"}, + rideFilterRouteParam: {"unknown"}, + } + + filters := parseRideFilters(query) + + assert.False(t, filters.Active()) + assert.Nil(t, filters.DistanceMinKm) + assert.Nil(t, filters.DistanceMaxKm) + assert.Nil(t, filters.DurationMinMinutes) + assert.Nil(t, filters.DurationMaxMinutes) + assert.Nil(t, filters.DateFrom) + assert.Nil(t, filters.DateToExclusive) + assert.Nil(t, filters.CotacolMinPoints) + assert.Nil(t, filters.CotacolMaxPoints) + assert.Empty(t, filters.Route) +} + +func TestParseRideFiltersTreatsFullRangesAsInactive(t *testing.T) { + filters := parseRideFilters(url.Values{ + rideFilterDistanceMinParam: {"10"}, + rideFilterDistanceMaxParam: {"300"}, + rideFilterDurationMinParam: {"0"}, + rideFilterDurationMaxParam: {"1440"}, + rideFilterCotacolMinParam: {"0"}, + rideFilterCotacolMaxParam: {"5000"}, + }) + + assert.False(t, filters.Active()) + assert.Nil(t, filters.DistanceMinKm) + assert.Nil(t, filters.DistanceMaxKm) + assert.Nil(t, filters.DurationMinMinutes) + assert.Nil(t, filters.DurationMaxMinutes) + assert.Nil(t, filters.CotacolMinPoints) + assert.Nil(t, filters.CotacolMaxPoints) +} + +func TestFilterRidesUsesInclusiveBoundsAndMovingTime(t *testing.T) { + items := []rides.Ride{ + {Name: "lower boundary", DistanceM: 20_000, MovingTimeS: 1_800, StartDate: time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC), GPXPath: "lower.gpx"}, + {Name: "upper boundary", DistanceM: 100_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "upper.gpx"}, + {Name: "outside distance", DistanceM: 110_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-distance.gpx"}, + {Name: "outside moving time", DistanceM: 100_000, MovingTimeS: 10_801, StartDate: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC), GPXPath: "outside-duration.gpx"}, + {Name: "outside date", DistanceM: 100_000, MovingTimeS: 10_800, StartDate: time.Date(2026, 9, 1, 12, 0, 0, 0, time.UTC), GPXPath: "outside-date.gpx"}, + } + filters := parseRideFilters(url.Values{ + rideFilterDistanceMinParam: {"20"}, + rideFilterDistanceMaxParam: {"100"}, + rideFilterDurationMinParam: {"30"}, + rideFilterDurationMaxParam: {"180"}, + rideFilterDateFromParam: {"2026-08-01"}, + rideFilterDateToParam: {"2026-08-31"}, + }) + + filtered := filterRides(items, filters) + + require.Len(t, filtered, 2) + assert.Equal(t, "lower boundary", filtered[0].Name) + assert.Equal(t, "upper boundary", filtered[1].Name) +} + +func TestFilterRidesByRouteAvailability(t *testing.T) { + items := []rides.Ride{ + {Name: "recorded", GPXPath: "recorded.gpx"}, + {Name: "metadata only"}, + } + + available := filterRides(items, parseRideFilters(url.Values{rideFilterRouteParam: {rideRouteFilterAvailable}})) + missing := filterRides(items, parseRideFilters(url.Values{rideFilterRouteParam: {rideRouteFilterMissing}})) + + require.Len(t, available, 1) + assert.Equal(t, "recorded", available[0].Name) + require.Len(t, missing, 1) + assert.Equal(t, "metadata only", missing[0].Name) +} + +func TestFilterRidesByCotacolExcludesUnavailableScores(t *testing.T) { + _, db := newWebTestServer(t) + current := rides.Ride{ + ExternalID: "strava:550e8400-e29b-41d4-a716-446655440000", + GPXPath: testGPXPath(t, "current-cotacol.gpx"), + Name: "Current score", + Type: "Ride", + StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC), + DistanceM: 20_000, + } + stale := rides.Ride{ + ExternalID: "strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8", + GPXPath: testGPXPath(t, "stale-cotacol.gpx"), + Name: "Stale score", + Type: "Ride", + StartDate: time.Date(2026, 8, 2, 7, 0, 0, 0, time.UTC), + DistanceM: 20_000, + } + unavailable := rides.Ride{ + ExternalID: "strava:7ba7b810-9dad-41d1-80b4-00c04fd430c8", + Name: "No score", + Type: "VirtualRide", + StartDate: time.Date(2026, 8, 3, 7, 0, 0, 0, time.UTC), + DistanceM: 20_000, + } + require.NoError(t, rides.Save(db, current)) + require.NoError(t, rides.Save(db, stale)) + require.NoError(t, rides.Save(db, unavailable)) + _, err := db.Exec(` + UPDATE rides + SET cotacol_score = CASE external_id + WHEN ? THEN 250 + WHEN ? THEN 250 + END, + cotacol_algo_version = CASE external_id + WHEN ? THEN ? + WHEN ? THEN 'old' + END + WHERE external_id IN (?, ?) + `, current.ExternalID, stale.ExternalID, current.ExternalID, ride.CotacolAlgorithmVersion, stale.ExternalID, current.ExternalID, stale.ExternalID) + require.NoError(t, err) + items, err := rides.List(db) + require.NoError(t, err) + filters := parseRideFilters(url.Values{rideFilterCotacolMinParam: {"200"}, rideFilterCotacolMaxParam: {"300"}}) + + filtered := filterRides(items, filters) + + require.Len(t, filtered, 1) + assert.Equal(t, current.ExternalID, filtered[0].ExternalID) +} + +func TestHandlerRendersRideFilterControls(t *testing.T) { + server, _ := newWebTestServer(t) + request := httptest.NewRequest(http.MethodGet, "/", nil) + response := httptest.NewRecorder() + + server.Handler().ServeHTTP(response, request) + + body := response.Body.String() + assert.Equal(t, http.StatusOK, response.Code) + assert.Contains(t, body, `id="ride-filters"`) + assert.Contains(t, body, `name="distance_min"`) + assert.Contains(t, body, `name="duration_min"`) + assert.Contains(t, body, `name="date_from"`) + assert.Contains(t, body, `name="cotacol_min"`) + assert.Contains(t, body, `name="route"`) + assert.Contains(t, body, `<script type="module" src="/static/ride-filters.js"></script>`) +} + +func TestRideSortHeadersPreserveFilters(t *testing.T) { + filters := parseRideFilters(url.Values{ + rideFilterDistanceMinParam: {"50"}, + rideFilterDistanceMaxParam: {"150"}, + rideFilterRouteParam: {rideRouteFilterAvailable}, + }) + + headers := rideSortHeadersWithFilters(RideSort{Column: rideSortDistance, Descending: true}, filters) + + assert.Equal(t, "/?dir=asc&distance_max=150&distance_min=50&route=available&sort=name", headers[0].URL) +} + +func TestHandlerFiltersRidesBeforeRenderingSortedResults(t *testing.T) { + server, db := newWebTestServer(t) + save := func(externalID, name, path string, distance float64) { + require.NoError(t, rides.Save(db, rides.Ride{ + ExternalID: externalID, + GPXPath: testGPXPath(t, path), + Name: name, + Type: "Ride", + StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC), + DistanceM: distance, + })) + } + save("strava:550e8400-e29b-41d4-a716-446655440000", "Long Ride", "long-filtered.gpx", 40_000) + save("strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8", "Short Ride", "short-filtered.gpx", 20_000) + save("strava:7ba7b810-9dad-41d1-80b4-00c04fd430c8", "Too Far Ride", "far-filtered.gpx", 320_000) + + request := httptest.NewRequest(http.MethodGet, "/?distance_min=10&distance_max=100&sort=distance&dir=asc", nil) + response := httptest.NewRecorder() + server.Handler().ServeHTTP(response, request) + + body := response.Body.String() + assert.Equal(t, http.StatusOK, response.Code) + assert.Less(t, strings.Index(body, "Short Ride"), strings.Index(body, "Long Ride")) + assert.NotContains(t, body, "Too Far Ride") +} + +func TestHandlerRendersFilteredEmptyState(t *testing.T) { + server, db := newWebTestServer(t) + require.NoError(t, rides.Save(db, rides.Ride{ + ExternalID: "strava:8ba7b810-9dad-41d1-80b4-00c04fd430c8", + GPXPath: testGPXPath(t, "empty-filter.gpx"), + Name: "Short Ride", + Type: "Ride", + StartDate: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC), + DistanceM: 20_000, + })) + + request := httptest.NewRequest(http.MethodGet, "/?distance_min=300&distance_max=300", nil) + response := httptest.NewRecorder() + server.Handler().ServeHTTP(response, request) + + body := response.Body.String() + assert.Equal(t, http.StatusOK, response.Code) + assert.Contains(t, body, "No rides match the selected filters.") + assert.Contains(t, body, `href="/">Clear filters.</a>`) + assert.NotContains(t, body, "No rides stored yet.") +} diff --git a/web/server.go b/web/server.go index 496e44d..4bc74e3 100644 --- a/web/server.go +++ b/web/server.go @@ -67,14 +67,24 @@ func (s *Server) ListenAndServe(addr string) error { func (s *Server) handleRides(w http.ResponseWriter, r *http.Request) { rideSort := parseRideSort(r.URL.Query()) + rideFilters := parseRideFilters(r.URL.Query()) items, err := s.listRides(rideSort) if err != nil { slog.Error("Failed to load rides", "sort", rideSort.Column, "descending", rideSort.Descending, "error", err) http.Error(w, "failed to load rides", http.StatusInternalServerError) return } + items = filterRides(items, rideFilters) + if rideFilters.Active() { + slog.Info("Applied ride library filters", "matches", len(items)) + } views := buildRideViews(items) - kcore.RenderPage(r.Context(), RidesPage(views, rideSortHeaders(rideSort)), w) + kcore.RenderPage(r.Context(), RidesPage(RidesPageData{ + Items: views, + Headers: rideSortHeadersWithFilters(rideSort, rideFilters), + Filters: buildRideFilterView(rideFilters, rideSort), + Sort: rideSort, + }), w) } func (s *Server) handleRide(w http.ResponseWriter, r *http.Request) { diff --git a/web/server_test.go b/web/server_test.go index 57f0d4a..823e4a8 100644 --- a/web/server_test.go +++ b/web/server_test.go @@ -95,7 +95,8 @@ func TestHandlerRendersRidesPage(t *testing.T) { server.Handler().ServeHTTP(response, req) assert.Equal(t, http.StatusOK, response.Code) - assert.Contains(t, response.Body.String(), "All rides") + assert.NotContains(t, response.Body.String(), "All rides") + assert.NotContains(t, response.Body.String(), "Your imported rides, ready for climb analysis.") assert.Contains(t, response.Body.String(), "Long Ride") assert.NotContains(t, response.Body.String(), "Short Ride") assert.Contains(t, response.Body.String(), "Cotacol") diff --git a/web/static/style.css b/web/static/style.css index b984da8..ef6348b 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -86,6 +86,30 @@ th { color: var(--color-subtle); font-size: .78rem; letter-spacing: .08em; text- .sort-indicator { display: inline-block; margin-left: .2rem; } .numeric { text-align: right; white-space: nowrap; } .empty { padding: 2.5rem 1rem; text-align: center; color: var(--color-subtle); } +.ride-filters-panel { margin-top: 1.25rem; } +.ride-filters { display: grid; gap: 1.25rem; margin-top: 1.25rem; } +.ride-filters > .section-heading { flex-wrap: wrap; } +.ride-filter-ranges { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem 1.5rem; } +.ride-filter-group { min-width: 0; margin: 0; padding: 0; border: 0; } +.ride-filter-group legend { padding: 0; color: var(--color-forest); font-size: .9rem; font-weight: 750; } +.range-values { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; margin-top: .45rem; color: var(--color-muted); font-size: .85rem; } +.range-values output { color: var(--color-foreground); font-weight: 700; } +.range-control { position: relative; height: 2rem; margin: .35rem .55rem 0; } +.range-track { position: absolute; top: 50%; right: 0; left: 0; height: .35rem; transform: translateY(-50%); border-radius: 999px; background: linear-gradient(to right, var(--color-border-subtle) 0%, var(--color-border-subtle) var(--range-start, 0%), var(--color-accent) var(--range-start, 0%), var(--color-accent) var(--range-end, 100%), var(--color-border-subtle) var(--range-end, 100%), var(--color-border-subtle) 100%); pointer-events: none; } +.range-input { position: absolute; inset: 0; width: 100%; height: 2rem; margin: 0; padding: 0; border: 0; background: transparent; outline: none; pointer-events: none; touch-action: none; } +.range-input-min { z-index: 2; } +.range-input-max { z-index: 1; } +.range-input::-webkit-slider-runnable-track { height: .35rem; background: transparent; } +.range-input::-moz-range-track { height: .35rem; background: transparent; } +.range-input::-webkit-slider-thumb { width: 1rem; height: 1rem; margin-top: -.325rem; border: 2px solid var(--color-surface); border-radius: 50%; background: var(--color-accent); box-shadow: 0 .15rem .4rem var(--color-panel-shadow); appearance: none; pointer-events: auto; cursor: pointer; } +.range-input::-moz-range-thumb { width: 1rem; height: 1rem; border: 2px solid var(--color-surface); border-radius: 50%; background: var(--color-accent); box-shadow: 0 .15rem .4rem var(--color-panel-shadow); appearance: none; pointer-events: auto; cursor: pointer; } +.range-input:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 .2rem var(--color-focus); } +.range-input:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 .2rem var(--color-focus); } +.ride-filter-meta { display: grid; grid-template-columns: minmax(0, 2fr) minmax(12rem, 1fr); align-items: end; gap: 1rem; } +.date-filter-inputs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; margin-top: .5rem; } +.route-filter { align-self: end; } +select { border: 1px solid var(--color-input-border); border-radius: .55rem; padding: .65rem .7rem; font: inherit; color: inherit; background: var(--color-surface); } +.ride-filter-actions { display: flex; justify-content: flex-end; gap: .75rem; flex-wrap: wrap; } .map-panel { padding: 0; overflow: hidden; } .ride-map { min-height: 24rem; height: min(65vh, 40rem); } .ride-map-pass-icon { display: grid; place-items: center; width: 1.75rem !important; height: 1.75rem !important; border: 2px solid var(--color-surface); border-radius: 50%; background: var(--color-leaf); color: var(--color-on-dark); box-shadow: 0 .15rem .4rem var(--color-panel-shadow); } @@ -159,4 +183,5 @@ dt { color: var(--color-subtle); font-size: .78rem; font-weight: 700; letter-spa dd { margin: .25rem 0 0; font-size: 1.2rem; font-weight: 700; } @media (width <= 900px) { .ride-detail-columns { grid-template-columns: 1fr; } } @media (width <= 700px) { .container { width: min(100% - 1rem, 70rem); padding-top: 1.5rem; } .site-header { padding-inline: 1rem; } th:nth-child(n+4), td:nth-child(n+4) { display: none; } .panel { padding: .8rem; } } -@media (width <= 500px) { dl { grid-template-columns: repeat(2, 1fr); } .official-climb-summary-stats { gap: .55rem; } .official-climb-card-title { display: block; } } +@media (width <= 700px) { .ride-filter-ranges, .ride-filter-meta { grid-template-columns: 1fr; } } +@media (width <= 500px) { dl { grid-template-columns: repeat(2, 1fr); } .official-climb-summary-stats { gap: .55rem; } .official-climb-card-title { display: block; } .date-filter-inputs { grid-template-columns: 1fr; } } 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) { diff --git a/web/templates_templ.go b/web/templates_templ.go index b16554d..05ae20a 100644 --- a/web/templates_templ.go +++ b/web/templates_templ.go @@ -58,7 +58,7 @@ func Layout(title string, content templ.Component) templ.Component { }) } -func RidesPage(items []RideView, headers []RideSortHeader) templ.Component { +func RidesPage(data RidesPageData) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -79,7 +79,7 @@ func RidesPage(items []RideView, headers []RideSortHeader) templ.Component { templ_7745c5c3_Var3 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = Layout("Rides", RidesContent(items, headers)).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Rides", RidesContent(data)).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -87,7 +87,7 @@ func RidesPage(items []RideView, headers []RideSortHeader) templ.Component { }) } -func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { +func RidesContent(data RidesPageData) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -108,23 +108,579 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<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\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"eyebrow\">Ride library</p><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=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if len(items) == 0 { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"empty\">No rides stored yet. <a href=\"/sync\">Import your first Strava rides.</a></div>") + var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(data.Filters.ResetURL) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Reset</a></div></div><input type=\"hidden\" name=\"sort\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Sort.Column) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 37, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <input type=\"hidden\" name=\"dir\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(rideSortDirection(data.Sort)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 38, Col: 71} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><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\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterDistance(data.Filters.DistanceMinKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 42, Col: 135} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</output><span aria-hidden=\"true\">to</span><output data-range-value=\"max\" for=\"distance-max\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterDistance(data.Filters.DistanceMaxKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 42, Col: 284} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceMinKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 45, Col: 150} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceMaxKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 45, Col: 203} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceStepKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 45, Col: 258} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.DistanceMinKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 45, Col: 316} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-range-min aria-label=\"Minimum distance\"> <input id=\"distance-max\" class=\"range-input range-input-max\" type=\"range\" name=\"distance_max\" min=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceMinKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 46, Col: 150} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceMaxKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 46, Col: 203} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDistanceStepKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 46, Col: 258} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.DistanceMaxKm)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 46, Col: 316} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" 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\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterDuration(data.Filters.DurationMinMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 51, Col: 140} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</output><span aria-hidden=\"true\">to</span><output data-range-value=\"max\" for=\"duration-max\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterDuration(data.Filters.DurationMaxMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 51, Col: 294} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationMinMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 54, Col: 155} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationMaxMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 54, Col: 213} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationStepMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 54, Col: 273} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.DurationMinMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 54, Col: 336} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-range-min aria-label=\"Minimum moving duration\"> <input id=\"duration-max\" class=\"range-input range-input-max\" type=\"range\" name=\"duration_max\" min=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationMinMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 55, Col: 155} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationMaxMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 55, Col: 213} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterDurationStepMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 55, Col: 273} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.DurationMaxMinutes)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 55, Col: 336} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" 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\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterCotacol(data.Filters.CotacolMinPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 60, Col: 136} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</output><span aria-hidden=\"true\">to</span><output data-range-value=\"max\" for=\"cotacol-max\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterCotacol(data.Filters.CotacolMaxPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 60, Col: 286} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</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=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolMinPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 63, Col: 151} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolMaxPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 63, Col: 207} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolStepPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 63, Col: 265} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.CotacolMinPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 63, Col: 326} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-range-min aria-label=\"Minimum Cotacol\"> <input id=\"cotacol-max\" class=\"range-input range-input-max\" type=\"range\" name=\"cotacol_max\" min=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolMinPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 64, Col: 151} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" max=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolMaxPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 64, Col: 207} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" step=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(rideFilterCotacolStepPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 64, Col: 265} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideFilterInt(data.Filters.CotacolMaxPoints)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 64, Col: 326} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" 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=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(data.Filters.DateFrom) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 71, Col: 154} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></label><label for=\"ride-date-to\">To<input id=\"ride-date-to\" type=\"date\" name=\"date_to\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(data.Filters.DateTo) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 71, Col: 273} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></label></div></fieldset><label class=\"route-filter\" for=\"ride-route\">Route <select id=\"ride-route\" name=\"route\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Filters.Route == rideRouteFilterAny { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"\" selected>Any route</option> ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"\">Any route</option> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Filters.Route == rideRouteFilterAvailable { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(rideRouteFilterAvailable) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 81, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" selected>Recorded route</option> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(rideRouteFilterAvailable) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 83, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Recorded route</option> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Filters.Route == rideRouteFilterMissing { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var42 string + templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(rideRouteFilterMissing) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 86, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" selected>Metadata only</option>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<option value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var43 string + templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(rideRouteFilterMissing) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 88, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Metadata only</option>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</select></label></div></form></section><section class=\"panel\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Items) == 0 { + if data.Filters.Active { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"empty\">No rides match the selected filters. <a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var44 templ.SafeURL = templ.URL(data.Filters.ResetURL) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var44))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Clear filters.</a></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"empty\">No rides stored yet. <a href=\"/sync\">Import your first Strava rides.</a></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table><thead><tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, header := range headers { - var templ_7745c5c3_Var5 = []any{header.Class} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) + for _, header := range data.Headers { + var templ_7745c5c3_Var45 = []any{header.Class} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var45...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -132,12 +688,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String()) + var templ_7745c5c3_Var46 string + templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var45).String()) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -145,12 +701,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(header.AriaSort) + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(header.AriaSort) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 42, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 106, Col: 72} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -158,8 +714,8 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(header.URL) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8))) + var templ_7745c5c3_Var48 templ.SafeURL = templ.URL(header.URL) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var48))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -167,12 +723,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(header.Label) + var templ_7745c5c3_Var49 string + templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(header.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 42, Col: 141} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 106, Col: 141} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -180,12 +736,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(header.Indicator) + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(header.Indicator) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 42, Col: 209} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 106, Col: 209} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -198,13 +754,13 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, item := range items { + for _, item := range data.Items { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td><strong><a href=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 templ.SafeURL = templ.URL(rideDetailURL(item.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var11))) + var templ_7745c5c3_Var51 templ.SafeURL = templ.URL(rideDetailURL(item.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var51))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -212,12 +768,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) + var templ_7745c5c3_Var52 string + templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 79} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 79} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -225,12 +781,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(item.Type) + var templ_7745c5c3_Var53 string + templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(item.Type) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 117} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 117} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -238,12 +794,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideDate(item.StartDate)) + var templ_7745c5c3_Var54 string + templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideDate(item.StartDate)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 168} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 168} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -251,12 +807,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatDistance(item.DistanceM)) + var templ_7745c5c3_Var55 string + templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(formatDistance(item.DistanceM)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 227} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 227} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -264,12 +820,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(item.MovingTimeS)) + var templ_7745c5c3_Var56 string + templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(item.MovingTimeS)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 288} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 288} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -277,12 +833,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(formatElevation(item.TotalElevationGainM)) + var templ_7745c5c3_Var57 string + templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(formatElevation(item.TotalElevationGainM)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 358} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 358} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -290,12 +846,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(item.Cotacol) + var templ_7745c5c3_Var58 string + templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(item.Cotacol) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 399} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 399} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -303,12 +859,12 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(item.CotacolPer100Km) + var templ_7745c5c3_Var59 string + templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(item.CotacolPer100Km) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 47, Col: 448} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 111, Col: 448} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -322,7 +878,7 @@ func RidesContent(items []RideView, headers []RideSortHeader) templ.Component { return templ_7745c5c3_Err } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</section>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</section><script type=\"module\" src=\"/static/ride-filters.js\"></script>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -346,9 +902,9 @@ func RideDetailPage(data RideDetailView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var60 := templ.GetChildren(ctx) + if templ_7745c5c3_Var60 == nil { + templ_7745c5c3_Var60 = templ.NopComponent } ctx = templ.ClearChildren(ctx) templ_7745c5c3_Err = Layout("Ride detail", RideDetailContent(data)).Render(ctx, templ_7745c5c3_Buffer) @@ -375,21 +931,21 @@ func RideDetailContent(data RideDetailView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var21 := templ.GetChildren(ctx) - if templ_7745c5c3_Var21 == nil { - templ_7745c5c3_Var21 = templ.NopComponent + templ_7745c5c3_Var61 := templ.GetChildren(ctx) + if templ_7745c5c3_Var61 == nil { + templ_7745c5c3_Var61 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"toolbar\"><div><h1 class=\"ride-detail-title\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.Ride.Name) + var templ_7745c5c3_Var62 string + templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(data.Ride.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 61, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 126, Col: 53} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -397,12 +953,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var23 string - templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(data.Ride.Type) + var templ_7745c5c3_Var63 string + templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(data.Ride.Type) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 61, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 126, Col: 92} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -410,12 +966,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var24 string - templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideDate(data.Ride.StartDate)) + var templ_7745c5c3_Var64 string + templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(formatRideDate(data.Ride.StartDate)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 61, Col: 135} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 126, Col: 135} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -428,12 +984,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var25 string - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(data.RouteError) + var templ_7745c5c3_Var65 string + templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(data.RouteError) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 65, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 130, Col: 38} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -447,12 +1003,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(data.Notice) + var templ_7745c5c3_Var66 string + templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(data.Notice) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 68, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 133, Col: 35} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -466,12 +1022,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(data.ActionError) + var templ_7745c5c3_Var67 string + templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(data.ActionError) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 71, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 136, Col: 39} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -484,12 +1040,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var28 string - templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(formatDistance(data.Ride.DistanceM)) + var templ_7745c5c3_Var68 string + templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(formatDistance(data.Ride.DistanceM)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 76, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 141, Col: 67} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -497,12 +1053,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var29 string - templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(data.Ride.MovingTimeS)) + var templ_7745c5c3_Var69 string + templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(data.Ride.MovingTimeS)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 77, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 142, Col: 72} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -510,12 +1066,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var30 string - templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(formatElevation(data.Ride.TotalElevationGainM)) + var templ_7745c5c3_Var70 string + templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(formatElevation(data.Ride.TotalElevationGainM)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 78, Col: 79} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 143, Col: 79} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -534,12 +1090,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var31 string - templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbNumber(climb.Index)) + var templ_7745c5c3_Var71 string + templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbNumber(climb.Index)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 91, Col: 114} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 156, Col: 114} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -547,12 +1103,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var32 string - templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(climb.OfficialName) + var templ_7745c5c3_Var72 string + templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(climb.OfficialName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 91, Col: 151} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 156, Col: 151} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -560,12 +1116,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var33 string - templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.DistanceKm)) + var templ_7745c5c3_Var73 string + templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.DistanceKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 93, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 158, Col: 75} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -573,12 +1129,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var34 string - templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(formatCotacol(climb.Cotacol)) + var templ_7745c5c3_Var74 string + templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(formatCotacol(climb.Cotacol)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 94, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 159, Col: 65} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -586,12 +1142,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var35 string - templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(formatSlope(climb.SlopePercent)) + var templ_7745c5c3_Var75 string + templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(formatSlope(climb.SlopePercent)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 95, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 160, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -599,12 +1155,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var36 string - templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.StartIndex)) + var templ_7745c5c3_Var76 string + templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.StartIndex)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 100, Col: 125} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 165, Col: 125} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -612,12 +1168,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var37 string - templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.EndIndex)) + var templ_7745c5c3_Var77 string + templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.EndIndex)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 100, Col: 179} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 165, Col: 179} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -625,12 +1181,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var38 string - templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(officialProfileLabel(climb.OfficialName)) + var templ_7745c5c3_Var78 string + templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(officialProfileLabel(climb.OfficialName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 100, Col: 259} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 165, Col: 259} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -648,8 +1204,8 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var39 = []any{rideDetailColumnsClass(len(data.Profile.UnmatchedClimbs()))} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var39...) + var templ_7745c5c3_Var79 = []any{rideDetailColumnsClass(len(data.Profile.UnmatchedClimbs()))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var79...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -657,12 +1213,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var40 string - templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var39).String()) + var templ_7745c5c3_Var80 string + templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var79).String()) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -675,12 +1231,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var41 string - templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbCount(len(data.Profile.UnmatchedClimbs()))) + var templ_7745c5c3_Var81 string + templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbCount(len(data.Profile.UnmatchedClimbs()))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 119, Col: 89} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 184, Col: 89} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -688,12 +1244,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var42 string - templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbCount(len(data.Profile.UnmatchedClimbs()))) + var templ_7745c5c3_Var82 string + templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbCount(len(data.Profile.UnmatchedClimbs()))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 123, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 188, Col: 100} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -706,12 +1262,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var43 string - templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.Index)) + var templ_7745c5c3_Var83 string + templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.Index)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 128, Col: 99} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 193, Col: 99} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -719,12 +1275,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var44 string - templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbNumber(climb.Index)) + var templ_7745c5c3_Var84 string + templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(formatClimbNumber(climb.Index)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 130, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 195, Col: 82} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -732,12 +1288,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var45 string - templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(climb.Name) + var templ_7745c5c3_Var85 string + templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(climb.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 130, Col: 103} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 195, Col: 103} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -745,12 +1301,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var46 string - templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.StartKm)) + var templ_7745c5c3_Var86 string + templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.StartKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 131, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 196, Col: 92} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -758,12 +1314,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var47 string - templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.EndKm)) + var templ_7745c5c3_Var87 string + templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.EndKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 131, Col: 133} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 196, Col: 133} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -771,12 +1327,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var48 string - templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(climb.Category) + var templ_7745c5c3_Var88 string + templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(climb.Category) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 133, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 198, Col: 68} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -784,12 +1340,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var49 string - templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.DistanceKm)) + var templ_7745c5c3_Var89 string + templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.DistanceKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 133, Col: 115} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 198, Col: 115} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -797,12 +1353,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var50 string - templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(formatSlope(climb.SlopePercent)) + var templ_7745c5c3_Var90 string + templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinStringErrs(formatSlope(climb.SlopePercent)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 133, Col: 154} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 198, Col: 154} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -810,12 +1366,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var51 string - templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(formatCotacol(climb.Cotacol)) + var templ_7745c5c3_Var91 string + templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinStringErrs(formatCotacol(climb.Cotacol)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 133, Col: 198} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 198, Col: 198} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -823,8 +1379,8 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var52 templ.SafeURL = templ.URL(officialClimbCreateURL(data.Ride.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var52))) + var templ_7745c5c3_Var92 templ.SafeURL = templ.URL(officialClimbCreateURL(data.Ride.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var92))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -832,12 +1388,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var53 string - templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.StartIndex)) + var templ_7745c5c3_Var93 string + templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.StartIndex)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 137, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 202, Col: 92} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -845,12 +1401,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var54 string - templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.EndIndex)) + var templ_7745c5c3_Var94 string + templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(formatRouteIndex(climb.EndIndex)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 138, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 203, Col: 87} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -858,12 +1414,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var55 string - templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.StartKm)) + var templ_7745c5c3_Var95 string + templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.StartKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 140, Col: 428} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 205, Col: 428} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -871,12 +1427,12 @@ func RideDetailContent(data RideDetailView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var56 string - templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.EndKm)) + var templ_7745c5c3_Var96 string + templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(formatProfileDistance(climb.EndKm)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 141, Col: 414} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 206, Col: 414} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -945,9 +1501,9 @@ func SyncPage(data SyncPageData) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var57 := templ.GetChildren(ctx) - if templ_7745c5c3_Var57 == nil { - templ_7745c5c3_Var57 = templ.NopComponent + templ_7745c5c3_Var97 := templ.GetChildren(ctx) + if templ_7745c5c3_Var97 == nil { + templ_7745c5c3_Var97 = templ.NopComponent } ctx = templ.ClearChildren(ctx) templ_7745c5c3_Err = Layout("Sync Strava", SyncContent(data)).Render(ctx, templ_7745c5c3_Buffer) @@ -974,9 +1530,9 @@ func SyncContent(data SyncPageData) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var58 := templ.GetChildren(ctx) - if templ_7745c5c3_Var58 == nil { - templ_7745c5c3_Var58 = templ.NopComponent + templ_7745c5c3_Var98 := templ.GetChildren(ctx) + if templ_7745c5c3_Var98 == nil { + templ_7745c5c3_Var98 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"eyebrow\">Data import</p><h1>Sync Strava rides</h1><p class=\"lead\">Choose a date range. New rides are downloaded as GPX files and recorded in the local library.</p>") @@ -988,12 +1544,12 @@ func SyncContent(data SyncPageData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var59 string - templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(data.Notice) + var templ_7745c5c3_Var99 string + templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(data.Notice) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 187, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 252, Col: 52} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1012,12 +1568,12 @@ func SyncContent(data SyncPageData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var60 string - templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(data.Error) + var templ_7745c5c3_Var100 string + templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(data.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 192, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 257, Col: 49} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1040,8 +1596,8 @@ func SyncContent(data SyncPageData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var61 templ.SafeURL = templ.URL("/strava/login?return_to=/sync") - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var61))) + var templ_7745c5c3_Var101 templ.SafeURL = templ.URL("/strava/login?return_to=/sync") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var101))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1054,12 +1610,12 @@ func SyncContent(data SyncPageData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var62 string - templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(data.From) + var templ_7745c5c3_Var102 string + templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(data.From) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 203, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 268, Col: 64} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1067,12 +1623,12 @@ func SyncContent(data SyncPageData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var63 string - templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(data.To) + var templ_7745c5c3_Var103 string + templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(data.To) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 204, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates.templ`, Line: 269, Col: 58} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/views.go b/web/views.go index b728de8..de4d38f 100644 --- a/web/views.go +++ b/web/views.go @@ -18,6 +18,13 @@ type RideView struct { CotacolPer100Km string } +type RidesPageData struct { + Items []RideView + Headers []RideSortHeader + Filters RideFilterView + Sort RideSort +} + type RideDetailView struct { RideView Route GeoJSONFeatureCollection @@ -168,6 +175,10 @@ func (s RideSort) databaseColumn() (rides.SortColumn, bool) { } func rideSortHeaders(current RideSort) []RideSortHeader { + return rideSortHeadersWithFilters(current, RideFilters{}) +} + +func rideSortHeadersWithFilters(current RideSort, filters RideFilters) []RideSortHeader { columns := []struct { key string label string @@ -188,13 +199,9 @@ func rideSortHeaders(current RideSort) []RideSortHeader { if active { descending = !current.Descending } - query := url.Values{} + query := rideFilterQuery(filters) query.Set("sort", column.key) - if descending { - query.Set("dir", "desc") - } else { - query.Set("dir", "asc") - } + query.Set("dir", rideSortDirection(RideSort{Column: column.key, Descending: descending})) headerClass := column.class ariaSort := "none" indicator := "" @@ -219,6 +226,13 @@ func rideSortHeaders(current RideSort) []RideSortHeader { return headers } +func rideSortDirection(sort RideSort) string { + if sort.Descending { + return "desc" + } + return "asc" +} + func rideDetailURL(id int64) string { return fmt.Sprintf("/rides/%d", id) } |