Files
flyer-crawler.projectium.com/components/ProcessingStatus.tsx

226 lines
10 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { LoadingSpinner } from './LoadingSpinner';
import { CheckCircleIcon } from './icons/CheckCircleIcon';
import { ExclamationTriangleIcon } from './icons/ExclamationTriangleIcon';
import { StageStatus, ProcessingStage } from '../types';
interface ProcessingStatusProps {
stages: ProcessingStage[];
estimatedTime: number;
currentFile?: string | null;
pageProgress?: {current: number, total: number} | null;
bulkProgress?: number;
bulkFileCount?: {current: number, total: number} | null;
}
interface StageIconProps {
status: StageStatus;
isCritical: boolean;
}
const StageIcon: React.FC<StageIconProps> = ({ status, isCritical }) => {
switch (status) {
case 'in-progress':
return <div className="w-5 h-5 text-brand-primary"><LoadingSpinner /></div>;
case 'completed':
return <CheckCircleIcon className="w-5 h-5 text-green-500" />;
case 'pending':
return <div className="w-5 h-5 rounded-full border-2 border-gray-400 dark:border-gray-600"></div>;
case 'error':
return isCritical ? (
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
) : (
<ExclamationTriangleIcon className="w-5 h-5 text-yellow-500" />
);
default:
return null;
}
};
export const ProcessingStatus: React.FC<ProcessingStatusProps> = ({ stages, estimatedTime, currentFile, pageProgress, bulkProgress, bulkFileCount }) => {
const [timeRemaining, setTimeRemaining] = useState(estimatedTime);
useEffect(() => {
setTimeRemaining(estimatedTime); // Reset when component gets new props
const timer = setInterval(() => {
setTimeRemaining(prevTime => (prevTime > 0 ? prevTime - 1 : 0));
}, 1000);
return () => clearInterval(timer);
}, [estimatedTime]);
const getStatusTextColor = (status: StageStatus, isCritical: boolean) => {
switch (status) {
case 'in-progress':
return 'text-brand-primary font-semibold';
case 'completed':
return 'text-gray-700 dark:text-gray-300';
case 'pending':
return 'text-gray-400 dark:text-gray-500';
case 'error':
return isCritical ? 'text-red-500 font-semibold' : 'text-yellow-600 dark:text-yellow-400';
default:
return '';
}
}
// Render new layout for bulk processing
if (currentFile) {
const extractionStage = stages.find(s => s.name === 'Extracting All Items from Flyer' && s.status === 'in-progress' && s.progress);
const stageList = (
<ul className="space-y-3">
{stages.map((stage, index) => {
const isCritical = stage.critical ?? true;
return (
<li key={index}>
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<StageIcon status={stage.status} isCritical={isCritical} />
</div>
<span className={`text-sm ${getStatusTextColor(stage.status, isCritical)}`}>
{stage.name}
{!isCritical && <span className="text-gray-400 dark:text-gray-500 text-xs italic"> (optional)</span>}
<span className="text-gray-400 dark:text-gray-500 ml-1">{stage.detail}</span>
</span>
</div>
</li>
);
})}
</ul>
);
return (
<div className="p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 min-h-[400px] flex flex-col justify-center">
<h2 className="text-xl font-bold text-gray-800 dark:text-white mb-6 text-center">
Processing Steps for: <br/>
<span className="font-normal text-base text-gray-600 dark:text-gray-300 truncate mt-1 block max-w-sm">{currentFile}</span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full max-w-4xl mx-auto">
{/* Left Column: Spinners and Progress Bars */}
<div className="flex flex-col justify-center items-center space-y-4">
<div className="w-24 h-24 text-brand-primary">
<LoadingSpinner />
</div>
{/* Overall Progress */}
{bulkFileCount && (
<div className="w-full">
<p className="text-sm text-center text-gray-500 dark:text-gray-400 mb-1">
File {bulkFileCount.current} of {bulkFileCount.total}
</p>
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div
className="bg-brand-primary h-2.5 rounded-full"
style={{ width: `${bulkProgress || 0}%`, transition: 'width 0.5s ease-in-out' }}
></div>
</div>
</div>
)}
{/* PDF Conversion Progress */}
{pageProgress && pageProgress.total > 1 && (
<div className="w-full">
<p className="text-xs text-left text-gray-500 dark:text-gray-400 mb-1">
Converting PDF: Page {pageProgress.current} of {pageProgress.total}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5 dark:bg-gray-700">
<div
className="bg-blue-500 h-1.5 rounded-full"
style={{ width: `${(pageProgress.current / pageProgress.total) * 100}%`, transition: 'width 0.2s ease-in-out' }}
></div>
</div>
</div>
)}
{/* Item Extraction Progress */}
{extractionStage && extractionStage.progress && (
<div className="w-full">
<p className="text-xs text-left text-gray-500 dark:text-gray-400 mb-1">
Analyzing page {extractionStage.progress.current} of {extractionStage.progress.total}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5 dark:bg-gray-700">
<div
className="bg-purple-500 h-1.5 rounded-full"
style={{ width: `${(extractionStage.progress.current / extractionStage.progress.total) * 100}%`, transition: 'width 0.5s ease-out' }}
></div>
</div>
</div>
)}
</div>
{/* Right Column: Checklist */}
<div className="flex items-center">
<div className="w-full">
{stageList}
</div>
</div>
</div>
</div>
);
}
// Original layout for single file processing
const title = 'Processing Your Flyer...';
const subTitle = `Estimated time remaining: ${Math.floor(timeRemaining / 60)}m ${timeRemaining % 60}s`;
return (
<div className="text-center p-8 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 h-full flex flex-col justify-center items-center min-h-[400px]">
<h2 className="text-xl font-bold mb-2 text-gray-800 dark:text-white">{title}</h2>
<p className="text-gray-500 dark:text-gray-400 mb-6 font-semibold text-brand-primary truncate max-w-full px-4">
{subTitle}
</p>
{pageProgress && pageProgress.total > 1 && (
<div className="w-full max-w-sm mb-6">
<p className="text-sm text-gray-500 dark:text-gray-400 mb-1 text-left">
Converting PDF: Page {pageProgress.current} of {pageProgress.total}
</p>
<div className="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${(pageProgress.current / pageProgress.total) * 100}%`, transition: 'width 0.2s ease-in-out' }}
></div>
</div>
</div>
)}
<div className="w-full max-w-sm text-left">
<ul className="space-y-3">
{stages.map((stage, index) => {
const isCritical = stage.critical ?? true;
return (
<li key={index}>
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<StageIcon status={stage.status} isCritical={isCritical} />
</div>
<span className={`text-sm ${getStatusTextColor(stage.status, isCritical)}`}>
{stage.name}
{!isCritical && <span className="text-gray-400 dark:text-gray-500 text-xs italic"> (optional)</span>}
<span className="text-gray-400 dark:text-gray-500 ml-1">{stage.detail}</span>
</span>
</div>
{stage.progress && stage.status === 'in-progress' && stage.progress.total > 1 && (
<div className="w-full mt-2 pl-8">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
Analyzing page {stage.progress.current} of {stage.progress.total}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5 dark:bg-gray-700">
<div
className="bg-purple-500 h-1.5 rounded-full"
style={{ width: `${(stage.progress.current / stage.progress.total) * 100}%`, transition: 'width 0.5s ease-out' }}
></div>
</div>
</div>
)}
</li>
);
})}
</ul>
</div>
</div>
);
};