"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { Card } from "@/components/ui/Card"; import { Badge } from "@/components/ui/Badge"; import { VideoPlayer } from "@/components/workout/VideoPlayer"; import { api } from "@/lib/api"; import type { GeneratedWorkout, WorkoutDetail, Exercise, PreviewDay, PreviewExercise, } from "@/lib/types"; interface DayCardProps { // Saved mode workout?: GeneratedWorkout; detail?: WorkoutDetail; onUpdate?: () => void; // Preview mode previewDay?: PreviewDay; previewDayIndex?: number; onPreviewDayChange?: (dayIndex: number, newDay: PreviewDay) => void; } function formatTime(seconds: number): string { const mins = Math.round(seconds / 60); if (mins < 60) return `${mins}m`; const h = Math.floor(mins / 60); const m = mins % 60; return m > 0 ? `${h}h ${m}m` : `${h}h`; } function EditIcon({ className = "" }: { className?: string }) { return ( ); } function TrashIcon({ className = "" }: { className?: string }) { return ( ); } function XIcon({ className = "" }: { className?: string }) { return ( ); } function mediaUrl(path: string): string { if (typeof window === "undefined") return path; if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") { return `${window.location.protocol}//${window.location.hostname}:8001${path}`; } return path; } function PlayIcon({ className = "" }: { className?: string }) { return ( ); } function VideoModal({ src, title, onClose }: { src: string; title: string; onClose: () => void }) { return (
e.stopPropagation()} >

{title}

); } function RefreshIcon({ className = "" }: { className?: string }) { return ( ); } // Swap exercise modal — works for both preview and saved mode function SwapModal({ exerciseId, currentName, onSwap, onClose, }: { exerciseId: number; currentName: string; onSwap: (newExercise: Exercise) => void; onClose: () => void; }) { const [alternatives, setAlternatives] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { api .getSimilarExercises(exerciseId) .then(setAlternatives) .catch((err) => console.error("Failed to load alternatives:", err)) .finally(() => setLoading(false)); }, [exerciseId]); return (
e.stopPropagation()} >

Replace: {currentName}

{loading ? (
Loading alternatives...
) : alternatives.length === 0 ? (
No alternatives found.
) : (
{alternatives.map((ex) => ( ))}
)}
); } export function DayCard({ workout, detail: externalDetail, onUpdate, previewDay, previewDayIndex, onPreviewDayChange, }: DayCardProps) { const isPreview = !!previewDay; // Saved mode state const [deleting, setDeleting] = useState(false); const [fetchedDetail, setFetchedDetail] = useState(null); const [regenerating, setRegenerating] = useState(false); // Video preview modal state const [videoPreview, setVideoPreview] = useState<{ src: string; title: string } | null>(null); // Swap modal state const [swapTarget, setSwapTarget] = useState<{ exerciseId: number; name: string; // For saved mode supersetExerciseId?: number; // For preview mode supersetIndex?: number; exerciseIndex?: number; } | null>(null); // Saved mode: fetch detail if not provided useEffect(() => { if (isPreview) return; if (externalDetail || !workout?.workout || workout.is_rest_day) return; let cancelled = false; api.getWorkoutDetail(workout.workout).then((data) => { if (!cancelled) setFetchedDetail(data); }).catch((err) => { console.error(`[DayCard] Failed to fetch detail for workout ${workout.workout}:`, err); }); return () => { cancelled = true; }; }, [isPreview, workout?.workout, workout?.is_rest_day, externalDetail]); const detail = externalDetail || fetchedDetail; // ============================================ // Preview mode handlers // ============================================ const handlePreviewDeleteDay = () => { if (previewDayIndex === undefined || !previewDay || !onPreviewDayChange) return; onPreviewDayChange(previewDayIndex, { ...previewDay, is_rest_day: true, focus_area: "Rest Day", target_muscles: [], workout_spec: undefined, }); }; const handlePreviewDeleteSuperset = (ssIdx: number) => { if (previewDayIndex === undefined || !previewDay?.workout_spec || !onPreviewDayChange) return; const newSupersets = previewDay.workout_spec.supersets.filter((_, i) => i !== ssIdx); onPreviewDayChange(previewDayIndex, { ...previewDay, workout_spec: { ...previewDay.workout_spec, supersets: newSupersets }, }); }; const handlePreviewDeleteExercise = (ssIdx: number, exIdx: number) => { if (previewDayIndex === undefined || !previewDay?.workout_spec || !onPreviewDayChange) return; const newSupersets = previewDay.workout_spec.supersets.map((ss, i) => { if (i !== ssIdx) return ss; const newExercises = ss.exercises.filter((_, j) => j !== exIdx); return { ...ss, exercises: newExercises }; }).filter((ss) => ss.exercises.length > 0); onPreviewDayChange(previewDayIndex, { ...previewDay, workout_spec: { ...previewDay.workout_spec, supersets: newSupersets }, }); }; const handlePreviewSwapExercise = (ssIdx: number, exIdx: number, ex: PreviewExercise) => { setSwapTarget({ exerciseId: ex.exercise_id, name: ex.exercise_name, supersetIndex: ssIdx, exerciseIndex: exIdx, }); }; const handlePreviewSwapConfirm = (newExercise: Exercise) => { if (!swapTarget || swapTarget.supersetIndex === undefined || swapTarget.exerciseIndex === undefined) return; if (previewDayIndex === undefined || !previewDay?.workout_spec || !onPreviewDayChange) return; const ssIdx = swapTarget.supersetIndex; const exIdx = swapTarget.exerciseIndex; const newSupersets = previewDay.workout_spec.supersets.map((ss, i) => { if (i !== ssIdx) return ss; const newExercises = ss.exercises.map((ex, j) => { if (j !== exIdx) return ex; return { ...ex, exercise_id: newExercise.id, exercise_name: newExercise.name, muscle_groups: newExercise.muscle_groups || "", }; }); return { ...ss, exercises: newExercises }; }); onPreviewDayChange(previewDayIndex, { ...previewDay, workout_spec: { ...previewDay.workout_spec, supersets: newSupersets }, }); setSwapTarget(null); }; const handlePreviewRegenerate = async () => { if (previewDayIndex === undefined || !previewDay || !onPreviewDayChange) return; setRegenerating(true); try { const newDay = await api.previewDay({ target_muscles: previewDay.target_muscles, focus_area: previewDay.focus_area, workout_type_id: previewDay.workout_type_id, date: previewDay.date, plan_id: previewDay.plan_id, }); onPreviewDayChange(previewDayIndex, newDay); } catch (err) { console.error("Failed to regenerate day:", err); } finally { setRegenerating(false); } }; // ============================================ // Saved mode handlers // ============================================ const handleDeleteDay = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!confirm("Remove this workout day?")) return; setDeleting(true); try { await api.deleteWorkoutDay(workout!.id); onUpdate?.(); } catch (err) { console.error("Failed to delete day:", err); } finally { setDeleting(false); } }; const handleDeleteSuperset = async (e: React.MouseEvent, supersetId: number) => { e.preventDefault(); e.stopPropagation(); try { await api.deleteSuperset(supersetId); onUpdate?.(); } catch (err) { console.error("Failed to delete superset:", err); } }; const handleDeleteExercise = async (e: React.MouseEvent, seId: number) => { e.preventDefault(); e.stopPropagation(); try { await api.deleteSupersetExercise(seId); onUpdate?.(); } catch (err) { console.error("Failed to delete exercise:", err); } }; const handleSavedSwapExercise = (seId: number, exerciseId: number, name: string) => (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setSwapTarget({ supersetExerciseId: seId, exerciseId, name }); }; const handleSavedSwapConfirm = async (newExercise: Exercise) => { if (!swapTarget?.supersetExerciseId) return; try { await api.swapExercise(swapTarget.supersetExerciseId, newExercise.id); setSwapTarget(null); onUpdate?.(); } catch (err) { console.error("Failed to swap exercise:", err); } }; // ============================================ // Render: Preview mode // ============================================ if (isPreview && previewDay) { if (previewDay.is_rest_day) return null; const spec = previewDay.workout_spec; const typeName = previewDay.workout_type_name?.replace(/_/g, " "); return ( <> {/* Top-right actions */}
{/* Header */}
{typeName && ( {typeName} )} {previewDay.focus_area && (

{previewDay.focus_area}

)}
{spec?.estimated_time && ( {formatTime(spec.estimated_time)} )}
{previewDay.warnings && previewDay.warnings.length > 0 && (

Warnings

    {previewDay.warnings.map((w, idx) => (
  • {w}
  • ))}
)} {/* Supersets */} {spec && spec.supersets.length > 0 && (
{spec.supersets.map((superset, si) => (
{superset.name}
{superset.rounds}x
{superset.exercises.map((ex, ei) => (
{ex.exercise_name} {ex.video_url && ( )}
{ex.reps ? `${ex.reps} reps` : ex.duration ? `${ex.duration}s` : ""}
))}
))}
)} {/* Muscle summary fallback */} {!spec && previewDay.target_muscles.length > 0 && (

{previewDay.target_muscles.join(", ")}

)}
{swapTarget && ( setSwapTarget(null)} /> )} {videoPreview && ( setVideoPreview(null)} /> )} ); } // ============================================ // Render: Saved mode // ============================================ if (!workout || workout.is_rest_day) return null; const typeName = workout.workout_type_name?.replace(/_/g, " "); const sortedSupersets = detail ? [...detail.supersets].sort((a, b) => a.order - b.order) : []; const cardContent = (
{typeName && ( {typeName} )} {workout.focus_area && (

{workout.focus_area}

)}
{detail?.estimated_time && ( {formatTime(detail.estimated_time)} )}
{sortedSupersets.length > 0 && (
{sortedSupersets.map((superset, si) => { const sortedExercises = [...superset.exercises].sort( (a, b) => a.order - b.order ); return (
{superset.name || `Set ${superset.order}`}
{superset.rounds}x {superset.id && ( )}
{sortedExercises.map((se, ei) => (
{se.exercise.name} {se.exercise.video_url && ( )}
{se.reps ? `${se.reps} reps` : se.duration ? `${se.duration}s` : ""} {se.id && (
)}
))}
); })}
)} {!detail && workout.target_muscles.length > 0 && (

{workout.target_muscles.join(", ")}

)} {/* Status badges for saved workouts */} {workout.status === "accepted" && (
Saved
)} {workout.status === "completed" && (
Completed
)}
); const modal = swapTarget ? ( setSwapTarget(null)} /> ) : null; const videoModal = videoPreview ? ( setVideoPreview(null)} /> ) : null; if (workout.workout) { return ( <> {cardContent} {modal} {videoModal} ); } return ( <> {cardContent} {modal} {videoModal} ); }