// frontend\src\components\ui\TaskList.tsx 'use client' export type TaskItem = { id: string status: 'idle' | 'queued' | 'running' | 'done' | 'error' | 'cancelled' title: string text?: string done?: number total?: number err?: string cancellable?: boolean fading?: boolean queuedAtMs?: number startedAtMs?: number } type Props = { tasks: TaskItem[] onCancel?: (id: string) => void } function pct(done?: number, total?: number) { const d = Number(done ?? 0) const t = Number(total ?? 0) if (!t || t <= 0) return 0 return Math.max(0, Math.min(100, Math.round((d / t) * 100))) } export default function TaskList({ tasks, onCancel }: Props) { const visible = [...(tasks || [])] .filter((t) => t.status !== 'idle') .sort((a, b) => { const aMs = Number(a.queuedAtMs ?? a.startedAtMs ?? 0) const bMs = Number(b.queuedAtMs ?? b.startedAtMs ?? 0) return aMs - bMs }) return (
Hintergrundaufgaben
Laufende Hintergrundaufgaben (z.B. Assets/Previews).
{visible.length === 0 ? (
Keine laufenden Aufgaben.
) : null} {visible.map((t) => { const p = pct(t.done, t.total) const isRunning = t.status === 'running' const hasProgress = isRunning && (t.total ?? 0) > 0 const title = (t.title ?? '').trim() const suffix = (t.text ?? '').trim() const isQueued = t.status === 'queued' const canCancel = (isRunning || isQueued) && t.cancellable && onCancel return (
{/* Left action / icon column */}
{canCancel ? ( ) : t.status === 'done' ? ( ) : ( )}
{/* Right content */}
{/* Mobile: Title oben */}
{title || 'Aufgabe'}
{/* Desktop: Title + suffix in einer Zeile */}
{title || 'Aufgabe'} {suffix ? ( · {suffix} ) : null}
{t.status === 'queued' ? ( Wartet ) : t.status === 'done' ? ( Fertig ) : t.status === 'cancelled' ? ( Abgebrochen ) : t.status === 'error' ? ( Fehler ) : null} {hasProgress ? (
{t.done ?? 0}/{t.total ?? 0} {p}%
) : null}
{/* Mobile: suffix unter dem Title */} {suffix ? (
{suffix}
) : null} {t.status === 'error' && t.err ? (
{t.err}
) : null}
) })}
) }