import { Staff, MediaCategory } from '@/types'; import { useState, useMemo, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Search, ArrowUpDown, User, ChevronLeft, ChevronRight, X, Filter } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { motion, AnimatePresence } from 'motion/react'; import { cn } from '@/lib/utils'; import { fetchAllCast } from '@/api'; interface CastViewProps { onPersonClick: (person: Staff) => void; enabledCategories: MediaCategory[]; itemsPerPage?: number; } export default function CastView({ onPersonClick, enabledCategories, itemsPerPage: initialItemsPerPage = 12 }: CastViewProps) { const navigate = useNavigate(); const [staffList, setStaffList] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(() => { return localStorage.getItem('castSearchQuery') || ''; }); const [sortBy, setSortBy] = useState<'name' | 'role' | 'birthDate' | 'height'>(() => { return (localStorage.getItem('castSortBy') as 'name' | 'role' | 'birthDate' | 'height') || 'name'; }); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => { return (localStorage.getItem('castSortOrder') as 'asc' | 'desc') || 'asc'; }); const [filterOccupation, setFilterOccupation] = useState(() => { return localStorage.getItem('castFilterOccupation') || ''; }); const [filterMediaType, setFilterMediaType] = useState(() => { return localStorage.getItem('castFilterMediaType') || ''; }); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); const [showFilters, setShowFilters] = useState(false); // Persist filters and sorts useEffect(() => { localStorage.setItem('castSearchQuery', searchQuery); }, [searchQuery]); useEffect(() => { localStorage.setItem('castSortBy', sortBy); }, [sortBy]); useEffect(() => { localStorage.setItem('castSortOrder', sortOrder); }, [sortOrder]); useEffect(() => { localStorage.setItem('castFilterOccupation', filterOccupation); }, [filterOccupation]); useEffect(() => { localStorage.setItem('castFilterMediaType', filterMediaType); }, [filterMediaType]); const handleResetFilters = () => { setSearchQuery(''); setSortBy('name'); setSortOrder('asc'); setFilterOccupation(''); setFilterMediaType(''); }; const hasActiveFilters = searchQuery || filterOccupation || filterMediaType || sortBy !== 'name' || sortOrder !== 'asc'; useEffect(() => { const loadCast = async () => { try { const cast = await fetchAllCast(); setStaffList(cast); } catch (error) { console.error('Failed to load cast:', error); } finally { setLoading(false); } }; loadCast(); }, []); const filteredStaff = useMemo(() => { let list = staffList.filter(s => { // Hide actors without linked media if (!s.filmography || s.filmography.length === 0) { return false; } // Filter by enabled categories based on media_types if (s.media_types && s.media_types.length > 0) { const hasEnabledMediaType = s.media_types.some(type => { const category = type.charAt(0).toUpperCase() + type.slice(1); return enabledCategories.includes(category as MediaCategory); }); if (!hasEnabledMediaType) { return false; } } // Filter by occupation if (filterOccupation && !s.occupations?.includes(filterOccupation)) { return false; } // Filter by media type if (filterMediaType && !s.media_types?.includes(filterMediaType)) { return false; } return s.name.toLowerCase().includes(searchQuery.toLowerCase()) || s.role.toLowerCase().includes(searchQuery.toLowerCase()) || s.mediaTitle?.toLowerCase().includes(searchQuery.toLowerCase()); }); // Sort list.sort((a, b) => { let comparison = 0; if (sortBy === 'name' || sortBy === 'role') { comparison = a[sortBy].localeCompare(b[sortBy]); } else if (sortBy === 'birthDate') { const dateA = a.birthDate ? new Date(a.birthDate).getTime() : 0; const dateB = b.birthDate ? new Date(b.birthDate).getTime() : 0; comparison = dateA - dateB; } else if (sortBy === 'height') { const heightA = a.height || 0; const heightB = b.height || 0; comparison = heightA - heightB; } return sortOrder === 'desc' ? -comparison : comparison; }); return list; }, [staffList, searchQuery, sortBy, sortOrder, filterOccupation, filterMediaType, enabledCategories]); // Get unique occupations and media types for filters const uniqueOccupations = useMemo(() => { const occupations = new Set(); staffList.forEach(s => s.occupations?.forEach(o => occupations.add(o))); return Array.from(occupations).sort(); }, [staffList]); const uniqueMediaTypes = useMemo(() => { const mediaTypes = new Set(); staffList.forEach(s => s.media_types?.forEach(m => mediaTypes.add(m))); return Array.from(mediaTypes).sort(); }, [staffList]); // Reset to first page when filters or sorting change useEffect(() => { setCurrentPage(1); }, [searchQuery, sortBy, sortOrder, filterOccupation, filterMediaType, itemsPerPage]); const totalPages = Math.ceil(filteredStaff.length / itemsPerPage); const paginatedStaff = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return filteredStaff.slice(startIndex, startIndex + itemsPerPage); }, [filteredStaff, currentPage, itemsPerPage]); const handlePrevPage = () => { setCurrentPage((prev) => Math.max(prev - 1, 1)); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleNextPage = () => { setCurrentPage((prev) => Math.min(prev + 1, totalPages)); window.scrollTo({ top: 0, behavior: 'smooth' }); }; return (

Cast & Staff

Discover the people behind your favorite media

setSearchQuery(e.target.value)} className="pl-10 w-full md:w-[300px] bg-zinc-100 border-none rounded-full h-11" />
{hasActiveFilters && ( )}
{showFilters && (
{searchQuery && ( Search: {searchQuery} )} {filterOccupation && ( Occupation: {filterOccupation} )} {filterMediaType && ( Media Type: {filterMediaType} )} {(sortBy !== 'name' || sortOrder !== 'asc') && ( Sort: {sortBy} ({sortOrder}) )}
)} {loading ? (

Loading cast...

) : filteredStaff.length === 0 ? (

No cast members found

) : (
{paginatedStaff.map((person) => ( onPersonClick(person)} >
{person.name}

{person.name}

{person.role}

{person.filmography && person.filmography.length > 0 && (
{person.filmography[0].title}

Latest Role

{person.filmography[0].title}

{person.filmography[0].role}

)}
))}
)} {/* Pagination Controls */} {filteredStaff.length > 0 && (
Items per page:
{currentPage} of {totalPages || 1}
)}
); }