Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields (activity name/type, lap duration/HR, structured workout raw JSON) from RawJSON at API-response time instead of storing redundant columns - Add a fully configurable chart color system (pace/HR main-line colors, 4 effort-kind colors, tint/darken/brighten intensity knobs) under Profile > Chart colors - Rename training types and fix their display order (Easy, Long, 60'/30' Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed - Add an Efficiency Factor progression metric; fix Progression chart axes to use tight non-zero-based domains, m:ss/km pace formatting, and rounded ticks instead of raw floating-point labels - Expose the raw get_workout_by_id() payload in the raw-data viewer alongside activity/lap/detail JSON; enlarge the modal and shrink array indentation for readability - Fix "last sync" reporting a meaningless activity count: record one combined sync run per manual "Sync now" and count genuinely new activities instead of re-listing whatever Garmin returned for the queried window - Let a Review Queue activity be manually cleared back to Unclassified, and make "Reset all" available even while disconnected from Garmin Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,13 +13,14 @@ import (
|
||||
)
|
||||
|
||||
// workoutKindResponse combines a workout kind's rule/metadata with its pace
|
||||
// range and expected HR zone (stored separately in workout_type_paces),
|
||||
// since the frontend always edits and displays them together.
|
||||
// and HR range (stored separately in workout_type_paces), since the frontend
|
||||
// always edits and displays them together.
|
||||
type workoutKindResponse struct {
|
||||
store.WorkoutKind
|
||||
PaceMinSecPerKm *float64 `json:"pace_min_sec_per_km"`
|
||||
PaceMaxSecPerKm *float64 `json:"pace_max_sec_per_km"`
|
||||
ExpectedHRZone *int `json:"expected_hr_zone"`
|
||||
HRMinPctHRR *float64 `json:"hr_min_pct_hrr"`
|
||||
HRMaxPctHRR *float64 `json:"hr_max_pct_hrr"`
|
||||
}
|
||||
|
||||
func (s *Server) toWorkoutKindResponse(r *http.Request, k store.WorkoutKind) (workoutKindResponse, error) {
|
||||
@@ -31,7 +32,8 @@ func (s *Server) toWorkoutKindResponse(r *http.Request, k store.WorkoutKind) (wo
|
||||
WorkoutKind: k,
|
||||
PaceMinSecPerKm: pace.PaceMinSecPerKm,
|
||||
PaceMaxSecPerKm: pace.PaceMaxSecPerKm,
|
||||
ExpectedHRZone: pace.ExpectedHRZone,
|
||||
HRMinPctHRR: pace.HRMinPctHRR,
|
||||
HRMaxPctHRR: pace.HRMaxPctHRR,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -44,7 +46,8 @@ type workoutKindRequest struct {
|
||||
IsActive *bool `json:"is_active"`
|
||||
PaceMinSecPerKm *float64 `json:"pace_min_sec_per_km"`
|
||||
PaceMaxSecPerKm *float64 `json:"pace_max_sec_per_km"`
|
||||
ExpectedHRZone *int `json:"expected_hr_zone"`
|
||||
HRMinPctHRR *float64 `json:"hr_min_pct_hrr"`
|
||||
HRMaxPctHRR *float64 `json:"hr_max_pct_hrr"`
|
||||
}
|
||||
|
||||
func (req workoutKindRequest) validate() (classify.Node, error) {
|
||||
@@ -58,8 +61,11 @@ func (req workoutKindRequest) validate() (classify.Node, error) {
|
||||
if err := node.Validate(); err != nil {
|
||||
return node, errors.New("invalid rule: " + err.Error())
|
||||
}
|
||||
if req.ExpectedHRZone != nil && (*req.ExpectedHRZone < 1 || *req.ExpectedHRZone > 5) {
|
||||
return node, errors.New("expected_hr_zone must be between 1 and 5")
|
||||
if req.PaceMinSecPerKm != nil && req.PaceMaxSecPerKm != nil && *req.PaceMinSecPerKm >= *req.PaceMaxSecPerKm {
|
||||
return node, errors.New("pace_min_sec_per_km must be less than pace_max_sec_per_km")
|
||||
}
|
||||
if req.HRMinPctHRR != nil && req.HRMaxPctHRR != nil && *req.HRMinPctHRR >= *req.HRMaxPctHRR {
|
||||
return node, errors.New("hr_min_pct_hrr must be less than hr_max_pct_hrr")
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
@@ -148,7 +154,8 @@ func (s *Server) handleUpdateWorkoutKind(w http.ResponseWriter, r *http.Request)
|
||||
WorkoutKindID: id,
|
||||
PaceMinSecPerKm: req.PaceMinSecPerKm,
|
||||
PaceMaxSecPerKm: req.PaceMaxSecPerKm,
|
||||
ExpectedHRZone: req.ExpectedHRZone,
|
||||
HRMinPctHRR: req.HRMinPctHRR,
|
||||
HRMaxPctHRR: req.HRMaxPctHRR,
|
||||
}); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user