diff --git a/docker/ui/src/components/HeroCinemaBanner.tsx b/docker/ui/src/components/HeroCinemaBanner.tsx index 86d8b1b..d73fb55 100644 --- a/docker/ui/src/components/HeroCinemaBanner.tsx +++ b/docker/ui/src/components/HeroCinemaBanner.tsx @@ -1,8 +1,18 @@ -import { Disc, Play, Sparkles, Film, Shield, Zap, CheckCircle } from 'lucide-react' +import { Disc, Play, Sparkles, Film, Shield, Zap, CheckCircle, Download, FileSpreadsheet } from 'lucide-react' import { RadialProgress } from './ui/RadialProgress' import { TypeBadge, StatusBadge } from './ui/Badge' import { Button } from './ui/Button' +interface JobMeta { + year?: number + overview?: string + poster_path?: string + genres?: string[] + runtime?: number + type?: string + source?: string +} + interface Job { id: string type: 'cd' | 'dvd' | 'bluray' | 'uhd' @@ -13,10 +23,18 @@ interface Job { progress: number title?: string can_retry?: boolean + meta?: JobMeta | null +} + +function posterUrl(meta?: JobMeta | null): string | null { + const p = meta?.poster_path + if (!p) return null + return p.startsWith('http') ? p : `https://image.tmdb.org/t/p/w500${p}` } interface HeroCinemaBannerProps { aktiverJob?: Job | null + latestJob?: Job | null onDetailClick?: (jobId: string) => void onCancelClick?: (jobId: string) => void totalJobsCount: number @@ -25,68 +43,74 @@ interface HeroCinemaBannerProps { export function HeroCinemaBanner({ aktiverJob, + latestJob, onDetailClick, onCancelClick, totalJobsCount, completedCount, }: HeroCinemaBannerProps) { + // Fall 1: Ein Job rippt oder komprimiert gerade live if (aktiverJob) { const isTranscoding = aktiverJob.status === 'transcoding' const statusText = isTranscoding ? 'HandBrake Kompression' : 'MakeMKV Extraktion' + const poster = posterUrl(aktiverJob.meta) return ( -
- {/* Background Ambient Glow Effects */} -
-
+
+
+
- {/* Left: 3D Metallic Disc Graphic */}
+ {/* 3D Spinning Disc Graphic */}
-
-
- + {poster ? ( +
+ +
+ +
-
-
- NOW RIPPING -
+ ) : ( +
+
+ +
+
+ )}
+ + NOW RIPPING + - - {statusText} -

{aktiverJob.title || 'Aktiver Ripping-Prozess'}

-

- Laufwerk: {aktiverJob.device} +

+ Laufwerk: {aktiverJob.device} - Gestartet: {new Date(aktiverJob.startTime).toLocaleTimeString('de-DE')} + {statusText}

- {/* Quality & Audio Badges Mockup */} -
- HDR10+ - DOLBY ATMOS - LOSSLESS MKV +
+ 4K / HD READY + LOSSLESS MKV + AUTO-METADATA
- {/* Right: Radial Progress Ring & Controls */}
- + -
+
+
+
+
+ +
+
+ + Erfolgreiche Rips: {completedCount} +
+
+
+ + MakeMKV Lossless +
+
+
+
+ ) + } + + // Standby Banner return (
-
-
@@ -123,7 +209,7 @@ export function HeroCinemaBanner({ Ready for Disc - System Status: Standby + System Standby

Rippy Cinema Studio @@ -133,18 +219,6 @@ export function HeroCinemaBanner({

- -
-
- - Erledigte Jobs: {completedCount} -
-
-
- - MakeMKV Lossless 1:1 -
-
) diff --git a/docker/ui/src/pages/Dashboard.tsx b/docker/ui/src/pages/Dashboard.tsx index 587418d..b49d0ec 100644 --- a/docker/ui/src/pages/Dashboard.tsx +++ b/docker/ui/src/pages/Dashboard.tsx @@ -100,6 +100,7 @@ export default function Dashboard() { }, []) const aktiverJob = jobs.find(j => j.status === 'processing' || j.status === 'transcoding') + const latestJob = jobs.find(j => j.status === 'completed') || jobs[0] || null const stats = { pending: jobs.filter(j => j.status === 'pending').length, @@ -123,9 +124,10 @@ export default function Dashboard() { subtitle="Dein Heimkino-Control-Center für Discs, Rips & Medien-Server" /> - {/* Hero Cinema Banner (3D Spinning Disc, Live Progress & Showcase) */} + {/* Hero Cinema Banner (Aktiver Rip ODER zuletzt geripptes Medium Showcase) */} setDetailJobId(id)} onCancelClick={id => api.post(`/jobs/${id}/cancel`).catch(() => {})} totalJobsCount={jobs.length} @@ -133,19 +135,19 @@ export default function Dashboard() { /> {/* System Telemetrie Leiste */} - +
- - Encoder: + + Encoder: {workers.length === 0 ? ( - kein Worker + kein Worker ) : ( workers.flatMap(w => w.encoders).filter((e, i, a) => a.indexOf(e) === i).map(e => ( {ENCODER_BADGES[e]?.label || e} @@ -154,29 +156,29 @@ export default function Dashboard() {
- - Aktiv: + + Aktiv: {aktiverJob ? ( - + {aktiverJob.status === 'transcoding' ? 'Komprimieren' : 'Rippen'} · {aktiverJob.progress} % ) : ( - kein Job + kein Job )}
{plaetze.length > 0 && (
- - Frei: + + Frei: {plaetze.map(p => ( {p.name.startsWith('Media') ? 'Media' : 'Arbeit'} {p.frei_gb} GB @@ -196,45 +198,45 @@ export default function Dashboard() { icon={Clock} label="Warteschlange" value={stats.pending.toString()} - badgeStyle="bg-amber-500/15 text-amber-500 border-amber-500/20" + badgeStyle="bg-amber-500/20 text-amber-400 border-amber-500/30 shadow-[0_0_15px_rgba(245,158,11,0.2)]" />
{/* Recent Jobs: Choice between Visual Poster Grid & Classic Table */} - +
Neueste Jobs - ({jobs.length} insgesamt) + ({jobs.length} insgesamt)
{/* View Mode Toggle Button */} -
+
-

- Laufwerk: {job.device} +

+ {job.device}

@@ -331,7 +334,7 @@ export default function Dashboard() {
-
+
@@ -379,27 +382,27 @@ export default function Dashboard() { /* Table View */
- + - - - - - - - + + + + + + + - + {jobs.slice(0, 10).map(job => ( - + - -
TypTitelStatusGerätFortschrittStartzeitAktionTypTitelStatusGerätFortschrittStartzeitAktion
@@ -407,19 +410,19 @@ export default function Dashboard() { + {job.device} + {new Date(job.startTime).toLocaleString('de-DE')} {job.status === 'completed' && ( )} {(job.status === 'completed' || job.status === 'failed') && ( @@ -458,14 +461,14 @@ export default function Dashboard() { function StatCard({ icon: Icon, label, value, badgeStyle }: { icon: any, label: string, value: string, badgeStyle: string }) { return ( - + -
+
-

{label}

-

{value}

+

{label}

+

{value}