import { notFound } from "next/navigation"; import Link from "next/link"; import { apiFetch } from "../../../lib/api"; interface JobDetailPageProps { params: Promise<{ id: string }>; } interface JobDetails { id: string; library_id: string | null; type: string; status: string; created_at: string; started_at: string | null; finished_at: string | null; current_file: string | null; progress_percent: number | null; processed_files: number | null; total_files: number | null; stats_json: { scanned_files: number; indexed_files: number; removed_files: number; errors: number; } | null; error_opt: string | null; } interface JobError { id: string; file_path: string; error_message: string; created_at: string; } async function getJobDetails(jobId: string): Promise { try { return await apiFetch(`/index/jobs/${jobId}`); } catch { return null; } } async function getJobErrors(jobId: string): Promise { try { return await apiFetch(`/index/jobs/${jobId}/errors`); } catch { return []; } } function formatDuration(start: string, end: string | null): string { const startDate = new Date(start); const endDate = end ? new Date(end) : new Date(); const diff = endDate.getTime() - startDate.getTime(); if (diff < 60000) return `${Math.floor(diff / 1000)}s`; if (diff < 3600000) return `${Math.floor(diff / 60000)}m ${Math.floor((diff % 60000) / 1000)}s`; return `${Math.floor(diff / 3600000)}h ${Math.floor((diff % 3600000) / 60000)}m`; } function formatSpeed(stats: { scanned_files: number } | null, duration: number): string { if (!stats || duration === 0) return "-"; const filesPerSecond = stats.scanned_files / (duration / 1000); return `${filesPerSecond.toFixed(1)} f/s`; } export default async function JobDetailPage({ params }: JobDetailPageProps) { const { id } = await params; const [job, errors] = await Promise.all([ getJobDetails(id), getJobErrors(id), ]); if (!job) { notFound(); } const duration = job.started_at ? new Date(job.finished_at || new Date()).getTime() - new Date(job.started_at).getTime() : 0; return ( <>
← Back to jobs

Job Details

{/* Overview Card */}

Overview

ID {job.id}
Type {job.type}
Status {job.status}
Library {job.library_id || "All libraries"}
{/* Timeline Card */}

Timeline

Created {new Date(job.created_at).toLocaleString()}
Started {job.started_at ? new Date(job.started_at).toLocaleString() : "Pending..."}
Finished {job.finished_at ? new Date(job.finished_at).toLocaleString() : job.started_at ? "Running..." : "Waiting..." }
{job.started_at && (
Duration: {formatDuration(job.started_at, job.finished_at)}
)}
{/* Progress Card */} {(job.status === "running" || job.status === "success" || job.status === "failed") && (

Progress

{job.total_files && job.total_files > 0 && ( <>
{job.progress_percent || 0}%
{job.processed_files || 0} Processed
{job.total_files} Total
{job.total_files - (job.processed_files || 0)} Remaining
)} {job.current_file && (
Current file: {job.current_file}
)}
)} {/* Statistics Card */} {job.stats_json && (

Statistics

{job.stats_json.scanned_files} Scanned
{job.stats_json.indexed_files} Indexed
{job.stats_json.removed_files} Removed
0 ? 'error' : ''}`}> {job.stats_json.errors} Errors
{job.started_at && (
Speed: {formatSpeed(job.stats_json, duration)}
)}
)} {/* Errors Card */} {errors.length > 0 && (

Errors ({errors.length})

{errors.map((error) => (
{error.file_path} {error.error_message} {new Date(error.created_at).toLocaleString()}
))}
)} {/* Error Message */} {job.error_opt && (

Error

{job.error_opt}
)}
); }