// screens-jobs.jsx function Jobs({ navigate }) { const [tab, setTab] = React.useState('all'); const [jobs, setJobs] = React.useState(window.ZAMPP_DATA.JOBS); const [lastFetch, setLastFetch] = React.useState(Date.now()); const normalizeJob = (j) => { const statusMap = { waiting: 'queued', active: 'running', completed: 'done', failed: 'failed' }; const kindMap = { proxy: 'Proxy', thumbnail: 'Thumbnail', conform: 'Conform', transcode: 'Transcode' }; const meta = j.metadata || {}; return { ...j, status: statusMap[j.status] || j.status, kind: kindMap[j.type] || j.type || 'Job', asset: j.asset_name || meta.filename || '—', eta: '—', node: meta.node || '—', priority: meta.priority || 'normal', error: j.error || null, progress: j.progress || 0, }; }; const refresh = React.useCallback(() => { window.ZAMPP_API.fetch('/jobs') .then(raw => { const norm = (raw || []).map(normalizeJob); window.ZAMPP_DATA.JOBS = norm; setJobs(norm); setLastFetch(Date.now()); }) .catch(() => {}); }, []); React.useEffect(() => { const i = setInterval(refresh, 5000); return () => clearInterval(i); }, [refresh]); const handleRetry = React.useCallback((job) => { window.ZAMPP_API.fetch('/jobs/' + job.id + '/retry', { method: 'POST' }) .then(() => refresh()) .catch(e => alert('Retry failed: ' + e.message)); }, [refresh]); const handleDelete = React.useCallback((job) => { if (!window.confirm('Remove this job from the queue?')) return; window.ZAMPP_API.fetch('/jobs/' + job.id, { method: 'DELETE' }) .then(() => setJobs(prev => prev.filter(j => j.id !== job.id))) .catch(e => alert('Delete failed: ' + e.message)); }, []); const counts = { all: jobs.length, running: jobs.filter(j => j.status === 'running').length, queued: jobs.filter(j => j.status === 'queued').length, done: jobs.filter(j => j.status === 'done').length, failed: jobs.filter(j => j.status === 'failed').length, }; const filtered = tab === 'all' ? jobs : jobs.filter(j => j.status === tab); return (