feat(04-04-visual-distinction): Add custom vs program workout badges on WorkoutSelectPage
- Fetch custom workouts for authenticated user - Display 'Anpassad' (custom) or 'Program' badge on each workout card - Add badge component with orange accent for custom, muted color for program - Badge positioned bottom-right of workout icon - Responsive styling consistent with Gravl dark theme - All build checks pass
This commit is contained in:
+9
-8
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"lastRun": "2026-03-01T11:31:00+01:00",
|
"lastRun": "2026-03-01T17:38:00+01:00",
|
||||||
"status": "in_progress",
|
"status": "completed",
|
||||||
"phase": "04-workout-modification",
|
"phase": "04-workout-modification",
|
||||||
"activeTask": "04-03-frontend-workout-edit",
|
"activeTask": "04-03-frontend-workout-edit",
|
||||||
"tasksCompleted": ["01-input-ux", "02-flexible-sets", "03-design-polish", "04-01-schema-migration", "04-02-backend-api"],
|
"tasksCompleted": ["01-input-ux", "02-flexible-sets", "03-design-polish", "04-01-schema-migration", "04-02-backend-api", "04-03-frontend-workout-edit"],
|
||||||
"nextTask": "04-03-frontend-workout-edit",
|
"nextTask": "04-04-visual-distinction",
|
||||||
"agentSession": "swift-trail",
|
"agentSession": "claude-code-frontend",
|
||||||
"agentType": "claude-coding-agent",
|
"agentType": "claude-code-local-exec",
|
||||||
"spawnTime": "2026-03-01T11:31:00+01:00",
|
"spawnTime": "2026-03-01T17:38:00+01:00",
|
||||||
"notes": "Spawned Claude coding agent (swift-trail) with exec+pty in background. Task: Build Edit Workout button, ExercisePicker modal, swap/add exercise flows, fork confirmation dialog, and save to custom_workouts API. Monitoring progress."
|
"result": "Phase 04-03 complete. Edit workflow implemented: ExercisePicker modal, swap/add/remove exercise flows, fork confirmation dialog, API integration (POST/PUT custom-workouts). All success criteria met. Ready for 04-04.",
|
||||||
|
"notes": "Previous attempt hit Gemini quota limit. Recovered at 17:38. Advancing to 04-04: Add visual distinction badges (custom vs program) on WorkoutSelectPage."
|
||||||
}
|
}
|
||||||
|
|||||||
+67
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-67
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -11,8 +11,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Gravl - Träning</title>
|
<title>Gravl - Träning</title>
|
||||||
<script type="module" crossorigin src="/assets/index-hhKetRGz.js"></script>
|
<script type="module" crossorigin src="/assets/index-DLV768U5.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-my_lGtI5.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-VYqTaBQ1.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -2958,3 +2958,43 @@
|
|||||||
border: 2px solid var(--border); display: flex; align-items: center; justify-content: center;
|
border: 2px solid var(--border); display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
.warmup-item.done .warmup-check { background: var(--success); border-color: var(--success); color: white; }
|
.warmup-item.done .warmup-check { background: var(--success); border-color: var(--success); color: white; }
|
||||||
|
|
||||||
|
/* Workout badge styling */
|
||||||
|
.workout-badge-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -6px;
|
||||||
|
right: -6px;
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
font-weight: 600;
|
||||||
|
padding: var(--space-1) var(--space-2);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||||
|
white-space: nowrap;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-badge.custom {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-badge.program {
|
||||||
|
background: var(--text-muted);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workout-select-card:hover .workout-badge {
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ const getWorkoutColor = (name) => {
|
|||||||
|
|
||||||
function WorkoutSelectPage({ onBack, onSelectWorkout }) {
|
function WorkoutSelectPage({ onBack, onSelectWorkout }) {
|
||||||
const [program, setProgram] = useState(null)
|
const [program, setProgram] = useState(null)
|
||||||
|
const [customWorkouts, setCustomWorkouts] = useState([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [selectedWorkout, setSelectedWorkout] = useState(null)
|
const [selectedWorkout, setSelectedWorkout] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProgram()
|
fetchProgram()
|
||||||
|
fetchCustomWorkouts()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const fetchProgram = async () => {
|
const fetchProgram = async () => {
|
||||||
@@ -36,6 +38,24 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchCustomWorkouts = async () => {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
if (!token) return
|
||||||
|
const res = await fetch(`${API_URL}/custom-workouts`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
setCustomWorkouts(data || [])
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch custom workouts:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWorkoutCustom = (programDayId) => {
|
||||||
|
return customWorkouts.some(cw => cw.source_program_day_id === programDayId)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelect = (workout) => {
|
const handleSelect = (workout) => {
|
||||||
setSelectedWorkout(workout)
|
setSelectedWorkout(workout)
|
||||||
}
|
}
|
||||||
@@ -76,6 +96,7 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
|
|||||||
const color = getWorkoutColor(workout.name)
|
const color = getWorkoutColor(workout.name)
|
||||||
const isSelected = selectedWorkout?.id === workout.id
|
const isSelected = selectedWorkout?.id === workout.id
|
||||||
const exerciseCount = workout.exercises?.filter(e => e.name).length || 0
|
const exerciseCount = workout.exercises?.filter(e => e.name).length || 0
|
||||||
|
const isCustom = isWorkoutCustom(workout.id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -84,8 +105,13 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
|
|||||||
style={{ '--workout-color': color }}
|
style={{ '--workout-color': color }}
|
||||||
onClick={() => handleSelect(workout)}
|
onClick={() => handleSelect(workout)}
|
||||||
>
|
>
|
||||||
<div className="workout-icon" style={{ background: color }}>
|
<div className="workout-badge-container">
|
||||||
<Icon name={iconName} size={28} />
|
<div className="workout-icon" style={{ background: color }}>
|
||||||
|
<Icon name={iconName} size={28} />
|
||||||
|
</div>
|
||||||
|
<span className={`workout-badge ${isCustom ? 'custom' : 'program'}`}>
|
||||||
|
{isCustom ? 'Anpassad' : 'Program'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="workout-details">
|
<div className="workout-details">
|
||||||
<h3>{workout.name}</h3>
|
<h3>{workout.name}</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user