feat(06-01): Exercise recommendations API endpoint + frontend components (coach-assisted suggestions)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import ExerciseCard from './ExerciseCard'
|
||||
import './exerciseRecommendations.css'
|
||||
|
||||
const normalizeGroupLabel = (item) => {
|
||||
return item.group || item.category || item.level || item.progression_level || 'Recommended'
|
||||
}
|
||||
|
||||
const groupRecommendations = (items) => {
|
||||
if (!Array.isArray(items)) return []
|
||||
const groups = items.reduce((acc, item) => {
|
||||
const label = normalizeGroupLabel(item)
|
||||
if (!acc[label]) acc[label] = []
|
||||
acc[label].push(item)
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return Object.entries(groups).map(([title, recommendations]) => ({
|
||||
id: title,
|
||||
title,
|
||||
recommendations
|
||||
}))
|
||||
}
|
||||
|
||||
function RecommendationPanel({
|
||||
title = 'Recommended Exercises',
|
||||
subtitle,
|
||||
recommendations = [],
|
||||
groups,
|
||||
layout = 'grid',
|
||||
onSelect,
|
||||
emptyMessage = 'No recommendations available yet.',
|
||||
className = ''
|
||||
}) {
|
||||
const resolvedGroups = Array.isArray(groups) && groups.length > 0
|
||||
? groups
|
||||
: groupRecommendations(recommendations)
|
||||
|
||||
const hasContent = resolvedGroups.some(group => group.recommendations?.length)
|
||||
|
||||
return (
|
||||
<section className={`recommendation-panel ${className}`}>
|
||||
<div className="recommendation-panel-header">
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
{subtitle && <p>{subtitle}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!hasContent && (
|
||||
<div className="recommendation-empty">{emptyMessage}</div>
|
||||
)}
|
||||
|
||||
{hasContent && (
|
||||
<div className="recommendation-panel-body">
|
||||
{resolvedGroups.map(group => (
|
||||
<div key={group.id || group.title} className="recommendation-group">
|
||||
<div className="recommendation-group-header">
|
||||
<h3>{group.title}</h3>
|
||||
{group.description && <span>{group.description}</span>}
|
||||
</div>
|
||||
<div className={`recommendation-list recommendation-list--${layout}`}>
|
||||
{(group.recommendations || group.items || []).map(item => (
|
||||
<ExerciseCard
|
||||
key={item.id || `${group.title}-${item.name}`}
|
||||
exercise={item}
|
||||
onSelect={onSelect}
|
||||
compact={layout === 'list'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecommendationPanel
|
||||
Reference in New Issue
Block a user