'use client'; import { motion } from 'framer-motion'; import { Loader2, Clock, CheckCircle2, AlertCircle, XCircle, Play, Pause, Timer, Zap, BarChart3, Film, ArrowRight, RefreshCw, X, } from 'lucide-react'; import Link from 'next/link'; import { useState, useEffect } from 'react'; import { useRenderQueue, useCancelRender } from '@/hooks/use-api'; // ── Render Pipeline Aşamaları ── const RENDER_STAGES = [ { key: 'VIDEO_GENERATION', label: 'Video Üretimi', icon: Film, color: 'violet' }, { key: 'TTS_GENERATION', label: 'Seslendirme', icon: Zap, color: 'cyan' }, { key: 'MUSIC_GENERATION', label: 'Müzik', icon: Zap, color: 'amber' }, { key: 'AMBIENT_GENERATION', label: 'Ortam Sesi', icon: Zap, color: 'emerald' }, { key: 'MEDIA_MERGE', label: 'Birleştirme', icon: Zap, color: 'blue' }, { key: 'SUBTITLE_OVERLAY', label: 'Altyazı', icon: Zap, color: 'pink' }, { key: 'FINALIZATION', label: 'Son İşlem', icon: Zap, color: 'indigo' }, { key: 'UPLOAD', label: 'Yükleme', icon: CheckCircle2, color: 'emerald' }, ]; const STATUS_CONFIG: Record = { QUEUED: { label: 'Kuyrukta', color: 'text-amber-400', bg: 'bg-amber-500/12 border-amber-500/25', icon: Clock }, PROCESSING: { label: 'İşleniyor', color: 'text-cyan-400', bg: 'bg-cyan-500/12 border-cyan-500/25', icon: Play }, COMPLETED: { label: 'Tamamlandı', color: 'text-emerald-400', bg: 'bg-emerald-500/12 border-emerald-500/25', icon: CheckCircle2 }, FAILED: { label: 'Başarısız', color: 'text-red-400', bg: 'bg-red-500/12 border-red-500/25', icon: AlertCircle }, CANCELLED: { label: 'İptal Edildi', color: 'text-slate-400', bg: 'bg-slate-500/12 border-slate-500/25', icon: XCircle }, }; const stagger = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.06 } }, }; const fadeUp = { hidden: { opacity: 0, y: 14 }, show: { opacity: 1, y: 0, transition: { duration: 0.45, ease: [0.16, 1, 0.3, 1] as const } }, }; function formatDuration(ms: number | null | undefined): string { if (!ms) return '—'; const sec = ms / 1000; if (sec < 60) return `${sec.toFixed(1)}s`; const min = Math.floor(sec / 60); const remainSec = Math.round(sec % 60); return `${min}dk ${remainSec}s`; } function timeAgo(date: string): string { if (!date) return ''; const diff = Date.now() - new Date(date).getTime(); if (isNaN(diff)) return ''; const sec = Math.floor(diff / 1000); const min = Math.floor(sec / 60); if (min < 1) return 'az önce'; if (min < 60) return `${min}dk önce`; const hours = Math.floor(min / 60); if (hours < 24) return `${hours}sa önce`; const days = Math.floor(hours / 24); return `${days}gün önce`; } function getStageIndex(stage: string | null | undefined): number { if (!stage) return -1; return RENDER_STAGES.findIndex((s) => s.key === stage); } // ── Stats Card ── function StatCard({ label, value, icon: Icon, color, sub }: { label: string; value: string | number; icon: React.ElementType; color: string; sub?: string; }) { return (
{value}
{label} {sub && {sub}}
); } // ── Stage Stepper ── function StageStepper({ currentStage, status }: { currentStage?: string | null; status: string }) { const activeIdx = getStageIndex(currentStage); return (
{RENDER_STAGES.map((stage, idx) => { const isCompleted = idx < activeIdx; const isCurrent = idx === activeIdx; const isPending = idx > activeIdx; return (
{isCompleted ? '✓' : idx + 1}
{stage.label}
{idx < RENDER_STAGES.length - 1 && (
)}
); })}
); } // ── Active Job Card ── function ActiveJobCard({ job, onCancel, isCancelling }: { job: any; onCancel: (projectId: string) => void; isCancelling: boolean; }) { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); const statusCfg = STATUS_CONFIG[job.status] || STATUS_CONFIG.QUEUED; const StatusIcon = statusCfg.icon; return (
{/* Header */}
{statusCfg.label} Deneme #{job.attemptNumber}/{job.maxAttempts}
{job.project.title}
{job.project?.targetDuration}s video {job.project?.videoStyle} {mounted ? timeAgo(job.createdAt) : '--'}
{/* İptal */}
{/* Stage Stepper */} {job.status === 'PROCESSING' && ( )} {/* Kuyrukta bekliyor — animasyonlu bar */} {job.status === 'QUEUED' && (
Kuyrukta bekleniyor...
)} {/* Son Loglar */} {job.logs && job.logs.length > 0 && (
{job.logs.slice(0, 3).map((log: any) => (
{log.message} {log.durationMs && ( {formatDuration(log.durationMs)} )}
))}
)}
); } // ── Recent Job Row ── function RecentJobRow({ job }: { job: any }) { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); const statusCfg = STATUS_CONFIG[job.status] || STATUS_CONFIG.COMPLETED; const StatusIcon = statusCfg.icon; return (
{job.project.title}
{statusCfg.label} {job.processingTimeMs && ⏱ {formatDuration(job.processingTimeMs)}} {timeAgo(job.completedAt || job.createdAt)}
{job.errorMessage && (

{job.errorMessage}

)}
); } // ═══════════════════════════════════════════════════════ // ANA SAYFA — Render Kuyruk Monitörü // ═══════════════════════════════════════════════════════ export default function RenderQueuePage() { const { data, isLoading, error, refetch } = useRenderQueue(); const cancelMutation = useCancelRender(); const handleCancel = (projectId: string) => { if (confirm('Bu render işlemini iptal etmek istediğinize emin misiniz?')) { cancelMutation.mutate(projectId, { onSuccess: () => refetch() }); } }; if (isLoading) { return (

Render kuyruğu yükleniyor...

); } if (error) { return (

Yüklenemedi

Render kuyruğu verileri alınamadı. Backend çalışıyor olmalı.

); } const { stats, activeJobs, recentJobs } = data || { stats: { total: 0, queued: 0, processing: 0, completed: 0, failed: 0, cancelled: 0, avgProcessingTimeMs: null }, activeJobs: [], recentJobs: [] }; const successRate = stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0; return ( {/* ── Başlık ── */}

Render Monitör

Video üretim süreçlerini canlı takip edin

{/* ── İstatistik Kartları ── */} {/* ── Durum Dağılımı — Donut Benzeri Görsel ── */}

Durum Dağılımı

{/* Horizontal stacked bar */}
{stats.completed > 0 && (
)} {stats.processing > 0 && (
)} {stats.queued > 0 && (
)} {stats.failed > 0 && (
)} {stats.cancelled > 0 && (
)}
{stats.total} toplam
{/* Lejant */}
{[ { label: 'Tamamlanan', count: stats.completed, color: 'bg-emerald-500' }, { label: 'İşleniyor', count: stats.processing, color: 'bg-cyan-500' }, { label: 'Kuyrukta', count: stats.queued, color: 'bg-amber-500' }, { label: 'Başarısız', count: stats.failed, color: 'bg-red-500' }, { label: 'İptal', count: stats.cancelled, color: 'bg-slate-500' }, ].map((item) => (
{item.label} ({item.count})
))}
{/* ── Aktif İşler ── */}

Aktif İşler {(stats.queued > 0 || stats.processing > 0) && ( {stats.queued + stats.processing} aktif )}

{activeJobs.length === 0 ? (

Şu anda aktif render işlemi yok

Bir projeyi onayladığınızda burada görünecek

) : (
{activeJobs.map((job: any) => ( ))}
)}
{/* ── Geçmiş İşler ── */}

Geçmiş İşler

{recentJobs.length === 0 ? (

Henüz tamamlanan render işlemi yok

) : (
{recentJobs.map((job: any) => ( ))}
)}
); }