Files
stripstream-librarian/apps/backoffice/app/components/JobProgress.tsx
Froidefond Julien c421f427b0 fix(ui): Progress bar height too small for label text
Changed from size=md (8px) to size=lg (32px) to properly display
the percentage label inside the progress bar.
2026-03-06 14:42:06 +01:00

126 lines
3.6 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { StatusBadge, Badge, ProgressBar } from "./ui";
interface ProgressEvent {
job_id: string;
status: string;
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;
}
interface JobProgressProps {
jobId: string;
onComplete?: () => void;
}
export function JobProgress({ jobId, onComplete }: JobProgressProps) {
const [progress, setProgress] = useState<ProgressEvent | null>(null);
const [error, setError] = useState<string | null>(null);
const [isComplete, setIsComplete] = useState(false);
useEffect(() => {
const eventSource = new EventSource(`/api/jobs/${jobId}/stream`);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
const progressData: ProgressEvent = {
job_id: data.id,
status: data.status,
current_file: data.current_file,
progress_percent: data.progress_percent,
processed_files: data.processed_files,
total_files: data.total_files,
stats_json: data.stats_json,
};
setProgress(progressData);
if (data.status === "success" || data.status === "failed" || data.status === "cancelled") {
setIsComplete(true);
eventSource.close();
onComplete?.();
}
} catch (err) {
setError("Failed to parse SSE data");
}
};
eventSource.onerror = (err) => {
console.error("SSE error:", err);
eventSource.close();
setError("Connection lost");
};
return () => {
eventSource.close();
};
}, [jobId, onComplete]);
if (error) {
return (
<div className="p-4 bg-error-soft text-error rounded-lg text-sm">
Error: {error}
</div>
);
}
if (!progress) {
return (
<div className="p-4 text-muted text-sm">
Loading progress...
</div>
);
}
const percent = progress.progress_percent ?? 0;
const processed = progress.processed_files ?? 0;
const total = progress.total_files ?? 0;
return (
<div className="p-4 bg-card rounded-lg border border-line">
<div className="flex items-center justify-between mb-3">
<StatusBadge status={progress.status} />
{isComplete && (
<Badge variant="success">Complete</Badge>
)}
</div>
<ProgressBar value={percent} showLabel size="lg" className="mb-3" />
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted mb-3">
<span>{processed} / {total} files</span>
{progress.current_file && (
<span className="truncate max-w-md" title={progress.current_file}>
Current: {progress.current_file.length > 40
? progress.current_file.substring(0, 40) + "..."
: progress.current_file}
</span>
)}
</div>
{progress.stats_json && (
<div className="flex flex-wrap gap-3 text-xs">
<Badge variant="primary">Scanned: {progress.stats_json.scanned_files}</Badge>
<Badge variant="success">Indexed: {progress.stats_json.indexed_files}</Badge>
<Badge variant="warning">Removed: {progress.stats_json.removed_files}</Badge>
{progress.stats_json.errors > 0 && (
<Badge variant="error">Errors: {progress.stats_json.errors}</Badge>
)}
</div>
)}
</div>
);
}