import React, { useEffect, useState, useCallback, useRef } from 'react'; import AlbumCard from '../components/AlbumCard'; import GenreFilterBar from '../components/GenreFilterBar'; import { getAlbumList, getAlbumsByGenre, SubsonicAlbum } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; type SortType = 'alphabeticalByName' | 'alphabeticalByArtist'; const PAGE_SIZE = 30; async function fetchByGenres(genres: string[]): Promise { const results = await Promise.all(genres.map(g => getAlbumsByGenre(g, 500, 0))); const seen = new Set(); return results.flat().filter(a => { if (seen.has(a.id)) return false; seen.add(a.id); return true; }); } export default function Albums() { const { t } = useTranslation(); const [albums, setAlbums] = useState([]); const [sort, setSort] = useState('alphabeticalByName'); const [loading, setLoading] = useState(true); const [page, setPage] = useState(0); const [hasMore, setHasMore] = useState(true); const [selectedGenres, setSelectedGenres] = useState([]); const observerTarget = useRef(null); const filtered = selectedGenres.length > 0; const load = useCallback(async (sortType: SortType, offset: number, append = false) => { setLoading(true); try { const data = await getAlbumList(sortType, PAGE_SIZE, offset); if (append) setAlbums(prev => [...prev, ...data]); else setAlbums(data); setHasMore(data.length === PAGE_SIZE); } finally { setLoading(false); } }, []); const loadFiltered = useCallback(async (genres: string[], sortType: SortType) => { setLoading(true); try { const data = await fetchByGenres(genres); const sorted = [...data].sort((a, b) => sortType === 'alphabeticalByArtist' ? a.artist.localeCompare(b.artist) : a.name.localeCompare(b.name) ); setAlbums(sorted); setHasMore(false); } finally { setLoading(false); } }, []); useEffect(() => { if (filtered) loadFiltered(selectedGenres, sort); else { setPage(0); load(sort, 0); } }, [sort, filtered, selectedGenres, load, loadFiltered]); const loadMore = useCallback(() => { if (loading || !hasMore || filtered) return; const next = page + 1; setPage(next); load(sort, next * PAGE_SIZE, true); }, [loading, hasMore, page, sort, load, filtered]); useEffect(() => { const observer = new IntersectionObserver( entries => { if (entries[0].isIntersecting) loadMore(); }, { rootMargin: '200px' } ); if (observerTarget.current) observer.observe(observerTarget.current); return () => observer.disconnect(); }, [loadMore]); const sortOptions: { value: SortType; label: string }[] = [ { value: 'alphabeticalByName', label: t('albums.sortByName') }, { value: 'alphabeticalByArtist', label: t('albums.sortByArtist') }, ]; return (

{t('albums.title')}

{sortOptions.map(o => ( ))}
{loading && albums.length === 0 ? (
) : ( <>
{albums.map(a => )}
{!filtered && (
{loading && hasMore &&
}
)} )}
); }