"use client"; import { useState, useEffect, useMemo } from "react"; import { Search, Plus, Loader2, Dumbbell, X } from "lucide-react"; import ExerciseCard from "@/components/exercises/ExerciseCard"; import AddExerciseForm from "@/components/exercises/AddExerciseForm"; import { Exercise } from "@prisma/client"; import { scoreExercise } from "@/lib/exerciseSearch"; export default function ExercisesClient() { const [exercises, setExercises] = useState([]); const [filteredExercises, setFilteredExercises] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const [selectedMuscleGroup, setSelectedMuscleGroup] = useState( null ); const [selectedType, setSelectedType] = useState(null); const [showAddForm, setShowAddForm] = useState(false); // Build filter tags dynamically from all exercises const muscleGroups = useMemo(() => { const set = new Set(); for (const ex of exercises) { try { const groups = JSON.parse(ex.muscleGroups || "[]") as string[]; groups.forEach((g) => set.add(g.toLowerCase())); } catch {} } return Array.from(set).sort(); }, [exercises]); const equipmentTypes = useMemo(() => { const set = new Set(); for (const ex of exercises) { if (ex.type) set.add(ex.type.toLowerCase()); } return Array.from(set).sort(); }, [exercises]); useEffect(() => { const fetchExercises = async () => { try { const response = await fetch("/api/exercises"); const data = await response.json(); setExercises(data); setFilteredExercises(data); } catch (error) { console.error("Failed to fetch exercises:", error); } finally { setLoading(false); } }; fetchExercises(); }, []); useEffect(() => { let filtered = exercises; // Apply muscle group filter if (selectedMuscleGroup) { filtered = filtered.filter((ex) => { try { const muscleGroups = JSON.parse(ex.muscleGroups || "[]"); return muscleGroups.includes(selectedMuscleGroup); } catch { return false; } }); } // Apply equipment type filter if (selectedType) { filtered = filtered.filter( (ex) => ex.type?.toLowerCase() === selectedType ); } // Apply fuzzy search with abbreviation expansion if (searchQuery.trim()) { filtered = filtered .map((ex) => ({ exercise: ex, score: scoreExercise(searchQuery, ex.name) })) .filter((item) => item.score >= 0) .sort((a, b) => a.score - b.score) .map((item) => item.exercise); } else { filtered = filtered.sort((a, b) => a.name.localeCompare(b.name)); } setFilteredExercises(filtered); }, [searchQuery, selectedMuscleGroup, selectedType, exercises]); const handleExerciseAdded = (newExercise: Exercise) => { setExercises([...exercises, newExercise]); setShowAddForm(false); }; return (
{/* Controls */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-10 py-2.5 bg-zinc-900 border border-zinc-800 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-white/20" /> {searchQuery && ( )}
{/* Add Exercise Form */} {showAddForm && (

Add Custom Exercise

)} {/* Equipment Type Filter */}
Equipment {equipmentTypes.map((type) => ( ))}
{/* Muscle Group Filter */}
Muscle {muscleGroups.map((group) => ( ))}
{/* Grid */} {loading ? (
) : filteredExercises.length === 0 ? (

No exercises found

{searchQuery || selectedMuscleGroup || selectedType ? "Try adjusting your filters" : "Add your first exercise to get started"}

) : (
{filteredExercises.map((exercise) => ( ))}
)}
); }