import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { api } from '../api'
const DIFFICULTY_COLORS = {
beginner: 'var(--green)',
intermediate: 'var(--blue)',
advanced: 'var(--purple)',
}
const CATEGORY_ICONS = {
strength: 'ποΈ',
cardio: 'π',
flexibility: 'π§',
hybrid: 'β‘',
}
const STATUS_LABELS = {
pending: { label: 'Queued', color: 'var(--amber)' },
crawling: { label: 'Fetching...', color: 'var(--blue)' },
extracting: { label: 'Analyzing...', color: 'var(--purple)' },
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))
}