import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useJellyfinContext } from '../context/JellyfinContext/JellyfinContext'; import { usePageTitle } from '../context/PageTitleContext/PageTitleContext'; import { MediaItem } from '../api/jellyfin'; import { Loader } from '../components/Loader'; import './Genres.css'; interface Genre { Id: string; Name: string; Type: string; UserData?: { UnplayedItemCount: number; }; IsMainGenre?: boolean; IsSubGenre?: boolean; HasSubGenres?: boolean; ParentGenre?: string; } interface GenreSection { mainGenre: Genre; subGenres: Genre[]; } export const GenresHierarchical = () => { const { setPageTitle } = usePageTitle(); const api = useJellyfinContext(); const [genreSections, setGenreSections] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setPageTitle('Genres'); }, [setPageTitle]); useEffect(() => { const fetchHierarchicalGenres = async () => { setLoading(true); setError(null); try { console.log('Fetching hierarchical genres from backend API...'); const response = await fetch('/api/jellyfin/genres'); if (!response.ok) { throw new Error(`Backend API failed: ${response.status} ${response.statusText}`); } const data = await response.json(); const genres: Genre[] = data.genres || []; console.log(`Fetched ${genres.length} genres from backend API`); // Organize genres into hierarchical sections const sections: GenreSection[] = []; const genreMap = new Map(); // First pass: create sections for main genres genres.forEach(genre => { if (genre.IsMainGenre) { genreMap.set(genre.Name, { mainGenre: genre, subGenres: [] }); } }); // Second pass: add sub-genres to their parent main genres genres.forEach(genre => { if (genre.IsSubGenre && genre.ParentGenre) { const section = genreMap.get(genre.ParentGenre); if (section) { section.subGenres.push(genre); } } }); // Convert map to array and sort by main genre popularity const sectionsArray = Array.from(genreMap.values()).sort((a, b) => { const aCount = a.mainGenre.UserData?.UnplayedItemCount || 0; const bCount = b.mainGenre.UserData?.UnplayedItemCount || 0; return bCount - aCount; }); // Sort sub-genres within each section by popularity sectionsArray.forEach(section => { section.subGenres.sort((a, b) => { const aCount = a.UserData?.UnplayedItemCount || 0; const bCount = b.UserData?.UnplayedItemCount || 0; return bCount - aCount; }); }); console.log(`Organized into ${sectionsArray.length} genre sections`); setGenreSections(sectionsArray); } catch (err) { console.error('Error fetching hierarchical genres:', err); setError('Failed to load genres'); } finally { setLoading(false); } }; if (api.auth.token) { fetchHierarchicalGenres(); } }, [api]); const handleGenreClick = (genre: Genre) => { console.log('Genre clicked:', genre.Name, 'Type:', genre.Type); // For now, navigate to the same genre page - this could be enhanced // to handle main vs sub-genre filtering differently const genreName = encodeURIComponent(genre.Name || ''); window.location.href = `/genre/${genreName}`; }; if (loading) { return ; } if (error) { return
{error}
; } return (

Music Genres

Browse your music by genre - click main genres for broad selection or sub-genres for specific styles

{genreSections.length === 0 ? (

No genres found in your music library

) : (
{genreSections.map((section) => (
{/* Main Genre */}
handleGenreClick(section.mainGenre)} >

{section.mainGenre.Name}

{section.mainGenre.UserData?.UnplayedItemCount || 0} track{(section.mainGenre.UserData?.UnplayedItemCount || 0) !== 1 ? 's' : ''} {section.subGenres.length > 0 && ` • ${section.subGenres.length} sub-genres`}

{/* Sub-genres row */} {section.subGenres.length > 0 && (
{section.subGenres.map((subGenre) => (
handleGenreClick(subGenre)} >

{subGenre.Name}

{subGenre.UserData?.UnplayedItemCount || 0} track{(subGenre.UserData?.UnplayedItemCount || 0) !== 1 ? 's' : ''}

))}
)}
))}
)}
); };