import { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { api } from '../api' const DIFFICULTY_COLORS = { beginner: 'var(--green)', intermediate: 'var(--accent)', advanced: 'var(--accent-light)', } const CATEGORY_ICONS = { strength: 'πŸ‹οΈ', cardio: 'πŸƒ', flexibility: '🧘', hybrid: '⚑', } const STATUS_LABELS = { pending: { label: 'Queued', color: 'var(--amber)' }, crawling: { label: 'Fetching...', color: 'var(--accent)' }, extracting: { label: 'Analyzing...', color: 'var(--accent-light)' }, ready: { label: 'Ready', color: 'var(--green)' }, failed: { label: 'Failed', color: 'var(--red)' }, } export default function CatalogDetail() { const { id } = useParams() const navigate = useNavigate() const [program, setProgram] = useState(null) const [userPrograms, setUserPrograms] = useState([]) const [loading, setLoading] = useState(true) const [adopting, setAdopting] = useState(false) const [message, setMessage] = useState(null) useEffect(() => { loadData() }, [id]) async function loadData() { setLoading(true) try { const [p, mine] = await Promise.all([ api.getCatalogProgram(id), api.listPrograms(), ]) setProgram(p) setUserPrograms(mine) } catch (err) { setMessage({ type: 'error', text: err.message || 'Could not load program' }) } finally { setLoading(false) } } async function handleAdopt() { setAdopting(true) setMessage(null) const today = new Date().toISOString().slice(0, 10) try { const result = await api.adoptProgram(id, today) setMessage({ type: 'success', text: 'Program started! Redirecting…' }) setTimeout(() => navigate(`/programs/${result.id}`), 800) } catch (err) { setMessage({ type: 'error', text: err.message }) setAdopting(false) } } if (loading) { return
Loading...
} if (!program) { return (

Program not found.

) } const adopted = userPrograms.some(p => p.status === 'active' && p.catalog_id === program.id) const activeRecord = userPrograms.find(p => p.status === 'active' && p.catalog_id === program.id) const status = STATUS_LABELS[program.crawl_status] || STATUS_LABELS.pending const groupedWorkouts = groupByWeek(program.workouts) return (
{/* Back */} {/* Header card */}

{CATEGORY_ICONS[program.category] || 'πŸ“‹'} {program.name}

{status.label}
{program.description && (

{program.description}

)} {/* Meta tags */}
{program.duration_weeks && ( {program.duration_weeks} weeks )} {program.frequency_per_week && ( {program.frequency_per_week}x/week )} {program.difficulty && ( {program.difficulty} )} {(program.equipment || []).map(eq => ( {eq} ))}
{program.source_url && ( πŸ”— Original source β†— )}
{/* Message */} {message && (
{message.text}
)} {/* Adopt button */} {program.crawl_status === 'ready' && !adopted && ( )} {adopted && activeRecord && ( )} {program.crawl_status !== 'ready' && (
{program.crawl_status === 'failed' ? `Crawl failed: ${program.crawl_error || 'unknown error'}` : 'Program is being researched. Check back soon.'}
)} {/* Progression rules */} {program.progression_rules && (
πŸ“ˆ Progression

{program.progression_rules}

)} {/* Workouts preview */} {groupedWorkouts.length > 0 && ( <>

Workouts

{groupedWorkouts.map(([week, workouts]) => (
Week {week}
{workouts.map(w => (
{w.rest_day ? '😴 Rest Day' : (w.workout_name || `Day ${w.day_number}`)} {!w.rest_day && ( {(w.exercises || []).length} exercises )}
{!w.rest_day && (w.exercises || []).map((ex, i) => (
{ex.name} {ex.sets && ex.reps && `${ex.sets}Γ—${ex.reps}`}
))} {w.notes && (

πŸ’‘ {w.notes}

)}
))}
))} {program.duration_weeks && groupedWorkouts.length < program.duration_weeks && (

Showing {groupedWorkouts.length} of {program.duration_weeks} weeks β€” pattern repeats across the program.

)} )}
) } const tagStyle = { fontSize: '0.72rem', fontWeight: 600, padding: '3px 10px', borderRadius: 'var(--r-full)', background: 'var(--divider)', color: 'var(--text-2)', } function groupByWeek(workouts) { const groups = {} for (const w of (workouts || [])) { const week = w.week_number if (!groups[week]) groups[week] = [] groups[week].push(w) } // Sort workouts within each week by day_number for (const week of Object.keys(groups)) { groups[week].sort((a, b) => a.day_number - b.day_number) } return Object.entries(groups).sort(([a], [b]) => Number(a) - Number(b)) }