import { Media, MediaCategory } from '@/types'; import MediaCard from './MediaCard'; import MediaListItem from './MediaListItem'; import { LayoutGrid, List, Star, ChevronLeft, ChevronRight, ArrowUpDown, Plus, Search } from 'lucide-react'; import { Button } from '@/components/ui/button'; import React, { useState, useMemo, useEffect } from 'react'; import { createMedia, type CreateMediaInput } from '@/api'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { cn } from '@/lib/utils'; import { AnimatePresence } from 'motion/react'; interface BrowseViewProps { mediaList: Media[]; onMediaClick: (media: Media) => void; onAddMedia: (media: Media) => void; activeCategory: MediaCategory; } export default function BrowseView({ mediaList, onMediaClick, onAddMedia, activeCategory }: BrowseViewProps) { const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(12); const [sortBy, setSortBy] = useState('default'); // Add Media Dialog State const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); const [newMedia, setNewMedia] = useState({ title: '', year: '', poster: '', banner: '', description: '', rating: '', category: activeCategory as MediaCategory, type: 'Movie' as string, status: 'Released' as string, aspectRatio: '2/3' as '2/3' | '16/9' | '1/1', runtime: '', director: '', writer: '', releaseDate: '', genres: '' as string, tags: '' as string, studios: '' as string }); // Update category, default aspect ratio, and default type when activeCategory changes useEffect(() => { let defaultAspect: '2/3' | '16/9' | '1/1' = '2/3'; let defaultType = 'Movie'; if (activeCategory === 'Music') { defaultAspect = '1/1'; defaultType = 'Album'; } else if (activeCategory === 'Games') { defaultAspect = '16/9'; defaultType = 'Game'; } else if (activeCategory === 'Adult') { defaultAspect = '16/9'; defaultType = 'Movie'; } else if (activeCategory === 'Anime') { defaultType = 'TV'; } else if (activeCategory === 'Books') { defaultType = 'Hardcover'; } else if (activeCategory === 'Consoles') { defaultType = 'Console'; } setNewMedia(prev => ({ ...prev, category: activeCategory, aspectRatio: defaultAspect, type: defaultType })); }, [activeCategory]); const handleAddSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!newMedia.title || !newMedia.poster) return; // Convert category from plural to singular to match API format const categoryMap: Record = { 'Anime': 'Anime', 'Movies': 'Movie', 'Music': 'Music', 'Books': 'Book', 'Consoles': 'Console', 'Games': 'Game', 'Adult': 'Adult' }; const mediaInput: CreateMediaInput = { title: newMedia.title, year: parseInt(newMedia.year) || new Date().getFullYear(), poster: newMedia.poster, banner: newMedia.banner || null, description: newMedia.description || null, rating: newMedia.rating ? parseFloat(newMedia.rating) : null, category: categoryMap[newMedia.category] || newMedia.category, type: newMedia.type, status: newMedia.status, aspectRatio: newMedia.aspectRatio, runtime: newMedia.runtime ? parseInt(newMedia.runtime) : null, director: newMedia.director || null, writer: newMedia.writer || null, releaseDate: newMedia.releaseDate || null, genres: newMedia.genres ? newMedia.genres.split(',').map(g => g.trim()) : [], tags: newMedia.tags ? newMedia.tags.split(',').map(t => t.trim()) : [], studios: newMedia.studios ? newMedia.studios.split(',').map(s => s.trim()) : [] }; const createdMedia = await createMedia(mediaInput); if (createdMedia) { onAddMedia(createdMedia); } setNewMedia({ title: '', year: '', poster: '', banner: '', description: '', rating: '', category: activeCategory, type: 'Movie', status: 'Released', aspectRatio: '2/3', runtime: '', director: '', writer: '', releaseDate: '', genres: '', tags: '', studios: '' }); setIsAddDialogOpen(false); }; // Filter states const [selectedGenre, setSelectedGenre] = useState(null); const [selectedStudio, setSelectedStudio] = useState(null); // Extract unique values for filters const allGenres = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.genres || []))), [mediaList]); const allStudios = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.studios || []))), [mediaList]); const filteredMedia = useMemo(() => { return mediaList.filter(media => { if (selectedGenre && !media.genres?.includes(selectedGenre)) return false; if (selectedStudio && !media.studios?.includes(selectedStudio)) return false; return true; }); }, [mediaList, selectedGenre, selectedStudio]); // Reset to first page when mediaList or filters change useEffect(() => { setCurrentPage(1); }, [filteredMedia, sortBy]); const sortedMedia = useMemo(() => { const list = [...filteredMedia]; if (sortBy === 'title-asc') { return list.sort((a, b) => a.title.localeCompare(b.title)); } if (sortBy === 'title-desc') { return list.sort((a, b) => b.title.localeCompare(a.title)); } return list; }, [filteredMedia, sortBy]); const totalPages = Math.ceil(sortedMedia.length / itemsPerPage); const paginatedMedia = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; return sortedMedia.slice(startIndex, startIndex + itemsPerPage); }, [sortedMedia, 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 (
{/* Filters Bar */}
{/* Genre Filter */} setSelectedGenre(null)}>All Genres {allGenres.sort().map(genre => ( setSelectedGenre(genre)}>{genre} ))} {/* Studio Filter */} setSelectedStudio(null)}>All Studios {allStudios.sort().map(studio => ( setSelectedStudio(studio)}>{studio} ))} {(selectedGenre || selectedStudio) && ( )}
Add New Media Manually add a new item to your {activeCategory} library.
setNewMedia(prev => ({ ...prev, title: e.target.value }))} placeholder="e.g. Mob Psycho 100" className="bg-zinc-50 border-zinc-100 rounded-xl h-11 focus:ring-[#6d28d9]" required />
setNewMedia(prev => ({ ...prev, year: e.target.value }))} placeholder="2024" className="bg-zinc-50 border-zinc-100 rounded-xl h-11 focus:ring-[#6d28d9]" />
setNewMedia(prev => ({ ...prev, poster: e.target.value }))} placeholder="https://example.com/poster.jpg" className="bg-zinc-50 border-zinc-100 rounded-xl h-11 focus:ring-[#6d28d9]" required />
setNewMedia(prev => ({ ...prev, banner: e.target.value }))} placeholder="https://example.com/banner.jpg" className="bg-zinc-50 border-zinc-100 rounded-xl h-11 focus:ring-[#6d28d9]" />