import React, { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp } from 'lucide-react'; import { getAlbumList, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import CachedImage from '../components/CachedImage'; import { playAlbum } from '../utils/playAlbum'; import { useTranslation } from 'react-i18next'; const PAGE_SIZE = 50; interface ArtistEntry { id: string; name: string; coverArt?: string; totalPlays: number; } function deriveTopArtists(albums: SubsonicAlbum[]): ArtistEntry[] { const map = new Map(); for (const a of albums) { const plays = a.playCount ?? 0; if (plays === 0) continue; const entry = map.get(a.artistId); if (entry) { entry.totalPlays += plays; if (!entry.coverArt && a.coverArt) entry.coverArt = a.coverArt; } else { map.set(a.artistId, { id: a.artistId, name: a.artist, coverArt: a.coverArt, totalPlays: plays }); } } return [...map.values()].sort((a, b) => b.totalPlays - a.totalPlays); } function formatPlays(n: number, t: ReturnType['t']): string { return t('mostPlayed.plays', { n: n.toLocaleString() }) as string; } export default function MostPlayed() { const { t } = useTranslation(); const navigate = useNavigate(); const musicLibraryFilterVersion = useAuthStore(s => s.musicLibraryFilterVersion); const [albums, setAlbums] = useState([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(true); const [sortAsc, setSortAsc] = useState(false); // false = most plays first const topArtists = deriveTopArtists(albums).slice(0, 10); const load = useCallback(async () => { setLoading(true); setAlbums([]); setHasMore(true); try { const result = await getAlbumList('frequent', PAGE_SIZE, 0); setAlbums(result); setHasMore(result.length === PAGE_SIZE); } catch {} setLoading(false); }, [musicLibraryFilterVersion]); useEffect(() => { load(); }, [load]); const loadMore = async () => { if (loadingMore || !hasMore) return; setLoadingMore(true); try { const result = await getAlbumList('frequent', PAGE_SIZE, albums.length); setAlbums(prev => [...prev, ...result]); setHasMore(result.length === PAGE_SIZE); } catch {} setLoadingMore(false); }; const sorted = sortAsc ? [...albums].reverse() : albums; const withPlays = sorted.filter(a => (a.playCount ?? 0) > 0); return (

{t('mostPlayed.title')}

{/* ── Top Artists ── */} {!loading && topArtists.length > 0 && (

{t('mostPlayed.topArtists')}

{topArtists.map((artist, i) => ( ))}
)} {/* ── Top Albums ── */}

{t('mostPlayed.topAlbums')}

{loading ? (
) : withPlays.length === 0 ? (
{t('mostPlayed.noData')}
) : ( <>
{withPlays.map((album, i) => (
navigate(`/album/${album.id}`)} onContextMenu={e => { e.preventDefault(); playAlbum(album.id); }} > {sortAsc ? withPlays.length - i : i + 1} {album.coverArt ? ( ) : (
)}
{album.name} { e.stopPropagation(); navigate(`/artist/${album.artistId}`); }} > {album.artist}
{album.year && {album.year}} {(album.playCount ?? 0).toLocaleString()}
))}
{hasMore && ( )} )}
); }