package web
import "slices"
templ Layout(title string, content templ.Component) {
{ title } · biking_home
@content
}
templ RidesPage(data RidesPageData) {
@Layout("Rides", RidesContent(data))
}
templ RidesContent(data RidesPageData) {
Ride library
if len(data.Items) == 0 {
if data.Filters.Active {
} else {
}
} else {
for _, header := range data.Headers {
| { header.Label }{ header.Indicator } |
}
for _, item := range data.Items {
{ item.Ride.Name } { formatRideCategories(item.Ride) } | { formatRideDate(item.Ride.StartDate) } | { formatDistance(item.Ride.DistanceM) } | { item.MovingTime } | { formatSpeed(item.Ride.AverageSpeedMps) } | { formatElevation(item.Ride.TotalElevationGainM) } | { item.Cotacol } | { item.CotacolPer100Km } |
}
}
}
templ AnalyticsPage(data AnalyticsPageData) {
@Layout("Analytics", AnalyticsContent(data))
}
templ AnalyticsContent(data AnalyticsPageData) {
Analytics
Ride analytics
One point per ride — first chart: average speed vs. Cotacol per 100 km; second chart: normalized difficulty (Cotacol per 100 km) vs. moving duration.
Chart
Speed vs. Cotacol per 100 km
if len(data.Points) > 0 {
{ formatRideCount(len(data.Points)) }
}
if len(data.Points) == 0 {
if data.Filter.Active {
} else {
}
} else {
}
Chart
Normalized difficulty vs moving duration
if len(data.Points) > 0 {
{ formatRideCount(len(data.Points)) }
}
if len(data.Points) == 0 {
if data.Filter.Active {
} else {
}
} else {
}
@templ.JSONScript("analytics-points", data.Points)
}
templ PreviewPage(data PreviewPageData) {
@Layout("Preview", PreviewContent(data))
}
templ PreviewContent(data PreviewPageData) {
if data.Error != "" {
{ data.Error }
}
}
templ RideDetailPage(data RideDetailView) {
@Layout("Ride detail", RideDetailContent(data))
}
templ RideDetailContent(data RideDetailView) {
if data.RouteError != "" {
{ data.RouteError }
}
if data.Notice != "" {
{ data.Notice }
}
if data.ActionError != "" {
{ data.ActionError }
}
- Distance
- { formatDistance(data.Ride.DistanceM) }
- Moving time
- { data.MovingTime }
- Speed
- { formatSpeed(data.Ride.AverageSpeedMps) }
- Elevation
- { formatElevation(data.Ride.TotalElevationGainM) }
- Cotacol
- { data.Cotacol }
if !data.Preview {
}
if data.HasRoute {
if len(data.Profile.OfficialClimbs()) > 0 {
for _, climb := range data.Profile.OfficialClimbs() {
Climb { formatClimbNumber(climb.Index) }{ climb.OfficialName }
- Length
- { formatProfileDistance(climb.DistanceKm) }
- Cotacol
- { formatCotacol(climb.Cotacol) }
- Avg slope
- { formatSlope(climb.SlopePercent) }
⌄
}
}
if len(data.Profile.UnmatchedClimbs()) > 0 {
Next steps
Climbs to match
{ formatClimbCount(len(data.Profile.UnmatchedClimbs())) }
for _, climb := range data.Profile.UnmatchedClimbs() {
Climb { formatClimbNumber(climb.Index) }{ climb.Name }
{ formatProfileDistance(climb.StartKm) }–{ formatProfileDistance(climb.EndKm) }
{ climb.Category } · { formatProfileDistance(climb.DistanceKm) } at { formatSlope(climb.SlopePercent) } · Cotacol { formatCotacol(climb.Cotacol) }
Waiting for an official climb match.
}
}
Elevation profile
Hover or focus the profile to inspect the ride at that distance.
}
if data.HasRoute {
@templ.JSONScript("ride-route", data.Route)
@templ.JSONScript("ride-profile", data.Profile)
@templ.JSONScript("ride-passes", data.Passes)
}
}
templ SyncPage(data SyncPageData) {
@Layout("Sync Strava", SyncContent(data))
}
templ SyncContent(data SyncPageData) {
Data import
Sync Strava rides
Choose a date range. New rides are downloaded as GPX files and recorded in the local library.
if data.Notice != "" {
{ data.Notice }
} else {
}
if data.Error != "" {
{ data.Error }
} else {
}
if !data.HasAuth {
Strava authorization is required before the first sync.
Authorize Strava
} else {
Importing rides0 / 0
0 imported · 0 skipped
}
}
templ DataQualityPage(data DataQualityPageData) {
@Layout("Data quality", DataQualityContent(data))
}
templ DataQualityContent(data DataQualityPageData) {
Curation
Data quality
Categorise rides that are missing bike or ride type. Select rides, pick categories, and apply in bulk.
if data.Notice != "" {
{ data.Notice }
}
{ formatRideCount(data.Total) }
if data.Total == 0 {
All rides are categorised.
} else if len(data.Items) == 0 {
No uncategorised rides match the current view.
} else {
}
}
templ ClimbsPage(data ClimbsPageData) {
@Layout("Climbs", ClimbsContent(data))
}
templ ClimbsContent(data ClimbsPageData) {
Official climbs
if len(data.Items) == 0 {
No official climbs yet. Create one from a ride detail page.
} else {
}
}
templ ClimbDetailPage(data ClimbDetailPageData) {
@Layout("Climb", ClimbDetailContent(data))
}
templ ClimbDetailContent(data ClimbDetailPageData) {
- Attempts
- { data.Stats.Attempts }
- Distance
- { data.Stats.Distance }
- Elevation
- { data.Stats.ElevationG }
- Avg slope
- { data.Stats.Slope }
- Cotacol
- { data.Stats.Cotacol }
- Best time
- { data.Stats.BestTime }
if len(data.Profile.Elevations) > 0 {
Hover or focus the profile to inspect the climb at a distance.
<=0%
0–3%
3–6%
6–9%
9%+
}
{ formatClimbCount(len(data.Attempts)) }
if len(data.Attempts) == 0 {
No attempts recorded yet. Open a ride containing this climb to record one.
} else {
| Date | Ride | Time | Distance | Cotacol |
for _, attempt := range data.Attempts {
| { attempt.Date } | { attempt.RideName } | { attempt.Duration } | { attempt.Distance } | { attempt.Cotacol } |
}
}
@templ.JSONScript("climb-profile-data", data.Profile)
@templ.JSONScript("climb-endpoints", data.Endpoints)
}