import { useState, useEffect, useRef } from 'react' import { getDownloadHistory, getActiveDownloads, getUser, getScrapeJobs } from '../api' import Spinner from '../components/Spinner' export default function Downloads() { const [history, setHistory] = useState([]) const [active, setActive] = useState([]) const [scrapeJobs, setScrapeJobs] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const pollRef = useRef(null) const [usernames, setUsernames] = useState({}) useEffect(() => { loadAll() startPolling() return () => { if (pollRef.current) clearInterval(pollRef.current) } }, []) const loadAll = async () => { setLoading(true) setError(null) const [histData, activeData, scrapeData] = await Promise.all([ getDownloadHistory(), getActiveDownloads(), getScrapeJobs(), ]) if (histData.error) { setError(histData.error) setLoading(false) return } const histList = Array.isArray(histData) ? histData : histData.list || [] setHistory(histList) setActive(Array.isArray(activeData) ? activeData : []) setScrapeJobs(Array.isArray(scrapeData) ? scrapeData.filter(j => j.running) : []) setLoading(false) resolveUsernames(histList) } const startPolling = () => { pollRef.current = setInterval(async () => { const [activeData, scrapeData] = await Promise.all([ getActiveDownloads(), getScrapeJobs(), ]) if (!activeData.error) { const list = Array.isArray(activeData) ? activeData : [] setActive((prev) => { if (prev.length > 0 && list.length < prev.length) { getDownloadHistory().then((h) => { if (!h.error) setHistory(Array.isArray(h) ? h : h.list || []) }) } return list }) } if (!scrapeData.error) { setScrapeJobs(Array.isArray(scrapeData) ? scrapeData.filter(j => j.running) : []) } }, 2000) } const resolveUsernames = async (items) => { const ids = [...new Set(items.map((i) => i.userId || i.user_id).filter(Boolean))] for (const id of ids) { if (usernames[id]) continue const data = await getUser(id) if (data && !data.error && data.username) { setUsernames((prev) => ({ ...prev, [id]: data.username })) } } } const formatDate = (dateStr) => { if (!dateStr) return '--' const d = new Date(dateStr) return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }) } if (loading) return if (error) { return (

{error}

) } return (

Downloads

Manage and monitor media downloads

{/* Active Downloads */} {active.length > 0 && (

Active Downloads

{active.map((dl) => { const uid = dl.user_id const progress = dl.total > 0 ? Math.round((dl.completed / dl.total) * 100) : 0 return (

{usernames[uid] ? `@${usernames[uid]}` : `User ${uid}`}

{dl.completed || 0} / {dl.total || '?'} files {dl.errors > 0 && ( ({dl.errors} error{dl.errors !== 1 ? 's' : ''}) )}

{progress}%
{/* Progress Bar */}
) })}
)} {/* Scrape Jobs */} {scrapeJobs.length > 0 && (

Scrape Jobs

{scrapeJobs.map((job) => { const progress = job.progress.total > 0 ? Math.round((job.progress.completed / job.progress.total) * 100) : 0 return (
{job.type}

{job.folderName}

{progress}%

{job.progress.completed} / {job.progress.total} {job.type === 'forum' ? 'pages' : 'files'} {job.progress.errors > 0 && ( ({job.progress.errors} errors) )}

) })}
)} {/* Download History */}

History

{history.length === 0 && active.length === 0 ? (

No download history yet

Start downloading from the Users page

) : history.length === 0 ? null : (
{/* Table Header - hidden on mobile */}
User Files Status Date
{/* Table Rows */} {history.map((item, index) => { const uid = item.userId || item.user_id return (
{/* Desktop row */}
{usernames[uid] ? `@${usernames[uid]}` : `User ${uid}`}
{item.fileCount || item.file_count || 0} {formatDate(item.lastDownload || item.last_download || item.completedAt || item.created_at)}
{/* Mobile row */}
{usernames[uid] ? `@${usernames[uid]}` : `User ${uid}`} {item.fileCount || item.file_count || 0} files
) })}
)}
) } function StatusBadge({ status }) { const styles = { complete: 'bg-green-500/10 text-green-400', completed: 'bg-green-500/10 text-green-400', running: 'bg-blue-500/10 text-blue-400', error: 'bg-red-500/10 text-red-400', } return ( {status} ) }