import React, { useEffect, useState, useCallback } from 'react' import { useParams } from 'react-router-dom' import { Search, RefreshCw, Grid, List, Loader2 } from 'lucide-react' import { getLibraryItems, scanLibrary } from '../api/libraries' import { getMe } from '../api/me' import BookCard from '../components/library/BookCard' const PAGE_SIZE = 48 export default function Library() { const { libraryId } = useParams<{ libraryId: string }>() const [items, setItems] = useState([]) const [total, setTotal] = useState(0) const [page, setPage] = useState(0) const [search, setSearch] = useState('') const [loading, setLoading] = useState(false) const [scanning, setScanning] = useState(false) const [progressMap, setProgressMap] = useState>({}) const [view, setView] = useState<'grid' | 'list'>('grid') const [filterTag, setFilterTag] = useState('') const load = useCallback(async () => { if (!libraryId) return setLoading(true) try { const data = await getLibraryItems(libraryId, { page, limit: PAGE_SIZE, search: search || undefined }) setItems(data.results || []) setTotal(data.total || 0) } finally { setLoading(false) } }, [libraryId, page, search]) useEffect(() => { load() }, [load]) useEffect(() => { getMe().then((me) => { const map: Record = {} for (const p of me.mediaProgress || []) map[p.libraryItemId] = p setProgressMap(map) }).catch(() => {}) }, []) const handleScan = async () => { if (!libraryId) return setScanning(true) await scanLibrary(libraryId).catch(() => {}) setTimeout(() => { setScanning(false); load() }, 3000) } const searchDebounce = useCallback( (() => { let t: ReturnType | undefined return (v: string) => { clearTimeout(t); t = setTimeout(() => { setSearch(v); setPage(0) }, 300) } })(), [] ) const displayed = filterTag ? items.filter((i) => (i.media?.tags || []).includes(filterTag)) : items const allTags = [...new Set(items.flatMap((i) => i.media?.tags || []))] // Inject progress into items const enriched = displayed.map((i) => ({ ...i, _progress: progressMap[i.id] })) return (
{/* Toolbar */}
searchDebounce(e.target.value)} />
{allTags.length > 0 && ( )}
{/* Stats */}

{total} {total === 1 ? 'Eintrag' : 'Einträge'} {filterTag && ` · Filter: ${filterTag}`}

{/* Grid */} {loading ? (
) : enriched.length === 0 ? (

Keine Einträge gefunden

Klicke auf „Scan" um die Bibliothek zu durchsuchen.

) : view === 'grid' ? (
{enriched.map((item) => )}
) : (
{enriched.map((item) => { const meta = item.media?.metadata || {} const title = meta.title || item.relPath || 'Unbekannt' const author = meta.authors?.[0]?.name || '' const p = item._progress return (

{title}

{author &&

{author}

}
{p && !p.isFinished && (
)} {p?.isFinished && Fertig}
) })}
)} {/* Pagination */} {total > PAGE_SIZE && (
{page + 1} / {Math.ceil(total / PAGE_SIZE)}
)}
) }