'use client' import { useEffect, useState, useCallback } from 'react' import type { Movie } from '@/types' import MovieDetailModal from './MovieDetailModal' import FilterPanel from '@/components/FilterPanel' import DoomScrollView, { type DoomScrollItem } from '@/components/DoomScrollView' interface Props { libraryId: string } export default function MoviesView({ libraryId }: Props) { const [movies, setMovies] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [selectedIndex, setSelectedIndex] = useState(null) const [search, setSearch] = useState('') const [selectedTagIds, setSelectedTagIds] = useState>(new Set()) const [assignments, setAssignments] = useState>({}) const [filterRefreshKey, setFilterRefreshKey] = useState(0) const [showFilters, setShowFilters] = useState(true) const [doomScrollActive, setDoomScrollActive] = useState(false) const [doomScrollItems, setDoomScrollItems] = useState([]) const toggleTag = (tagId: string) => setSelectedTagIds((prev) => { const next = new Set(prev) next.has(tagId) ? next.delete(tagId) : next.add(tagId) return next }) const fetchMovies = useCallback(() => { fetch(`/api/movies?libraryId=${encodeURIComponent(libraryId)}`) .then((r) => r.json()) .then((data) => { setMovies(data) setLoading(false) }) .catch(() => { setError('Failed to load movies') setLoading(false) }) }, [libraryId]) useEffect(() => { fetchMovies() }, [fetchMovies]) const fetchAssignments = useCallback(() => { fetch(`/api/tags/library-assignments?libraryId=${encodeURIComponent(libraryId)}`) .then((r) => r.json()) .then(setAssignments) .catch(() => {}) }, [libraryId]) useEffect(() => { fetchAssignments() }, [fetchAssignments]) const filtered = movies.filter((movie) => { if (search && !movie.title.toLowerCase().includes(search.toLowerCase())) return false if (selectedTagIds.size > 0) { const movieTags = assignments[`${libraryId}:${movie.id}`] ?? [] if (![...selectedTagIds].every((id) => movieTags.includes(id))) return false } return true }) const selected = selectedIndex !== null ? filtered[selectedIndex] ?? null : null const handleDeleted = (movieId: string) => { setSelectedIndex(null) setMovies((prev) => prev.filter((m) => m.id !== movieId)) } const filtersActive = search !== '' || selectedTagIds.size > 0 const handleDoomScroll = () => { // Use filtered movies — respects any active search/tag filters automatically const items: DoomScrollItem[] = filtered.map((m) => ({ url: `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(m.videoPath)}`, name: m.title, mediaType: 'video' as const, })) setDoomScrollItems(items) setDoomScrollActive(true) } return ( <> {doomScrollActive && doomScrollItems.length > 0 && ( setDoomScrollActive(false)} /> )}
{showFilters && (
)}
{loading ? ( ) : error ? (
{error}
) : movies.length === 0 ? (

No movies found

Each movie should be a folder containing a video file.

) : (
{filtered.map((movie, idx) => ( ))}
)} {selected && selectedIndex !== null && ( setSelectedIndex(null)} onPrev={selectedIndex > 0 ? () => setSelectedIndex((i) => (i !== null ? i - 1 : null)) : undefined} onNext={selectedIndex < filtered.length - 1 ? () => setSelectedIndex((i) => (i !== null ? i + 1 : null)) : undefined} onTagsChanged={() => { setFilterRefreshKey((k) => k + 1); fetchAssignments() }} onDeleted={handleDeleted} /> )}
) } function LoadingGrid() { return (
{Array.from({ length: 12 }).map((_, i) => (
))}
) }