import { useEffect, useState } from 'react' import { Clock, Activity, AlertCircle, CheckCircle, Disc } from 'lucide-react' import { api } from '../lib/api' import { useDarkMode } from '../context/ThemeContext' import DeviceDiscovery from '../components/DeviceDiscovery' import LiveLogSection from '../components/LiveLogSection' interface Job { id: string type: 'cd' | 'dvd' | 'bluray' status: 'pending' | 'processing' | 'transcoding' | 'canceling' | 'completed' | 'failed' device: string startTime: string endTime?: string progress: number title?: string } async function fetchJobs(): Promise { try { const response = await api.get('/jobs') return response.data } catch { return [] } } function StatusBadge({ status }: { status: string }) { const { theme } = useDarkMode() const styles = theme === 'dark' ? { pending: 'bg-amber-900/30 text-amber-400 ring-amber-700/20', processing: 'bg-blue-900/30 text-blue-400 ring-blue-700/20', transcoding: 'bg-purple-900/30 text-purple-400 ring-purple-700/20', canceling: 'bg-orange-900/30 text-orange-400 ring-orange-700/20', completed: 'bg-emerald-900/30 text-emerald-400 ring-emerald-700/20', failed: 'bg-rose-900/30 text-rose-400 ring-rose-700/20', } : { pending: 'bg-amber-100 text-amber-700 ring-amber-600/20', processing: 'bg-blue-100 text-blue-700 ring-blue-600/20', transcoding: 'bg-purple-100 text-purple-700 ring-purple-600/20', canceling: 'bg-orange-100 text-orange-700 ring-orange-600/20', completed: 'bg-emerald-100 text-emerald-700 ring-emerald-600/20', failed: 'bg-rose-100 text-rose-700 ring-rose-600/20', } const labels = { pending: 'Wartend', processing: 'Rippen', transcoding: 'Komprimieren', canceling: 'Wird abgebrochen…', completed: 'Fertig', failed: 'Fehler', } return ( {labels[status as keyof typeof labels] || status} ) } function TypeBadge({ type }: { type: string }) { const { theme } = useDarkMode() const colors = theme === 'dark' ? 'bg-slate-800 text-slate-300' : 'bg-slate-100 text-slate-700' const icons = { cd: Disc, dvd: Disc, bluray: Disc, } const Icon = icons[type as keyof typeof icons] || Disc return ( {type.toUpperCase()} ) } function ProgressBar({ progress }: { progress: number }) { const { theme } = useDarkMode() const bgColors = theme === 'dark' ? { empty: 'bg-slate-700', indicator: 'bg-gradient-to-r from-indigo-500 to-purple-600', done: 'bg-emerald-500', } : { empty: 'bg-slate-200', indicator: 'bg-gradient-to-r from-indigo-500 to-purple-600', done: 'bg-emerald-500', } const textColors = theme === 'dark' ? { label: 'text-slate-400', value: 'text-slate-200', } : { label: 'text-slate-500', value: 'text-slate-900', } return (
Fortschritt {progress}%
) } interface WorkerInfo { name: string encoders: string[] } const ENCODER_KURZ: Record = { 'cpu-x264': 'H.264 (CPU)', 'cpu-x265': 'H.265 (CPU)', 'vaapi': 'VAAPI ⚡', 'nvenc': 'NVENC ⚡', } export default function Dashboard() { const [jobs, setJobs] = useState([]) const [workers, setWorkers] = useState([]) const [loading, setLoading] = useState(true) const { theme } = useDarkMode() useEffect(() => { const loadData = async () => { try { setJobs(await fetchJobs()) } finally { setLoading(false) } } loadData() api.get('/capabilities').then(r => setWorkers(r.data.workers || [])).catch(() => {}) const interval = setInterval(loadData, 5000) return () => clearInterval(interval) }, []) const aktiverJob = jobs.find(j => j.status === 'processing' || j.status === 'transcoding') const stats = { pending: jobs.filter(j => j.status === 'pending').length, processing: jobs.filter(j => j.status === 'processing').length, completed: jobs.filter(j => j.status === 'completed').length, failed: jobs.filter(j => j.status === 'failed').length, } if (loading) { return (
) } return (
{/* Header */}

Dashboard

Übersicht über alle Ripping-Jobs und Geräte

{/* System-Leiste: Encoder + aktiver Job auf einen Blick */}
Encoder: {workers.length === 0 ? ( kein Worker ) : ( workers.flatMap(w => w.encoders).filter((e, i, a) => a.indexOf(e) === i).map(e => ( {ENCODER_KURZ[e] || e} )) )}
Aktiv: {aktiverJob ? ( {aktiverJob.status === 'transcoding' ? 'Komprimieren' : 'Rippen'} · {aktiverJob.progress} % ) : ( kein Job )}
{/* Laufwerke + erkannte Disc */} {/* Stats Cards */}
{/* Recent Jobs */} {/* Schnellaktionen + doppelte Geräte-Sektion sind raus (23.07.): alle vier Knöpfe und der zweite Details-Button waren funktionslos — die echte Geräte-Steuerung lebt in DeviceDiscovery oben. */}

Neueste Jobs

{jobs.length} Jobs insgesamt
{jobs.length === 0 ? ( ) : ( jobs.slice(0, 10).map(job => ( )) )}
Typ Titel Status Gerät Fortschritt Startzeit Aktion
Noch keine Jobs

Füge eine CD, DVD oder Blu-ray hinzu, um zu starten

{job.title || 'Unbekannt'}
{job.device} {new Date(job.startTime).toLocaleString('de-DE')} {job.status === 'failed' && ( )} {(job.status === 'pending' || job.status === 'processing' || job.status === 'transcoding') && ( )}
{/* Live-Log ganz unten — Detail-Ansicht, nicht Haupt-Info */}
) } function StatCard({ icon: Icon, label, value, color, darkMode }: { icon: any, label: string, value: string, color: string, darkMode: boolean }) { const { theme } = useDarkMode() const bgColors = darkMode ? { card: 'bg-slate-800 border-slate-700', iconBg: { amber: 'bg-amber-700', blue: 'bg-blue-700', emerald: 'bg-emerald-700', rose: 'bg-rose-700', }, iconText: { amber: 'text-amber-300', blue: 'text-blue-300', emerald: 'text-emerald-300', rose: 'text-rose-300', }, textLabel: 'text-slate-400', textValue: 'text-slate-200', } : { card: 'bg-white border-slate-200', iconBg: { amber: 'bg-amber-500', blue: 'bg-blue-500', emerald: 'bg-emerald-500', rose: 'bg-rose-500', }, iconText: { amber: 'text-amber-600', blue: 'text-blue-600', emerald: 'text-emerald-600', rose: 'text-rose-600', }, textLabel: 'text-slate-500', textValue: 'text-slate-900', } return (

{label}

{value}

) }