| | import React from "react"; |
| | import { motion } from "framer-motion"; |
| | import { |
| | FileSearch, |
| | Cpu, |
| | TableProperties, |
| | CheckCircle2, |
| | Loader2, |
| | } from "lucide-react"; |
| | import { cn } from "@/lib/utils"; |
| |
|
| | const steps = [ |
| | { id: "upload", label: "Received", icon: FileSearch }, |
| | { id: "analyze", label: "Analysis", icon: Cpu }, |
| | { id: "extract", label: "Extraction", icon: TableProperties }, |
| | { id: "complete", label: "Done", icon: CheckCircle2 }, |
| | ]; |
| |
|
| | export default function ProcessingStatus({ isProcessing, isComplete, currentStage }) { |
| | const getCurrentStep = () => { |
| | if (isComplete) return 4; |
| | if (!isProcessing) return 0; |
| | |
| | |
| | if (currentStage === "extraction") return 3; |
| | if (currentStage === "analysis") return 2; |
| | if (currentStage === "received") return 1; |
| | |
| | |
| | return 2; |
| | }; |
| |
|
| | const currentStep = getCurrentStep(); |
| |
|
| | if (!isProcessing && !isComplete) return null; |
| |
|
| | return ( |
| | <motion.div |
| | initial={{ opacity: 0, y: -10 }} |
| | animate={{ opacity: 1, y: 0 }} |
| | className="bg-white rounded-xl border border-slate-200 px-4 py-3" |
| | > |
| | <div className="flex items-center justify-between gap-2"> |
| | {steps.map((step, index) => { |
| | const isActive = index + 1 === currentStep; |
| | const isCompleted = index + 1 < currentStep || isComplete; |
| | const Icon = step.icon; |
| | |
| | return ( |
| | <React.Fragment key={step.id}> |
| | <div className="flex items-center gap-2"> |
| | <motion.div |
| | initial={false} |
| | animate={{ |
| | scale: (isActive && !isComplete) ? 1.05 : 1, |
| | backgroundColor: isCompleted |
| | ? "rgb(16 185 129)" |
| | : (isActive && !isComplete) |
| | ? "rgb(99 102 241)" |
| | : "rgb(241 245 249)", |
| | }} |
| | className={cn( |
| | "h-8 w-8 rounded-lg flex items-center justify-center transition-colors", |
| | (isCompleted || isActive) && "shadow-md" |
| | )} |
| | style={{ |
| | boxShadow: (isActive && !isComplete) |
| | ? "0 4px 8px -2px rgba(99, 102, 241, 0.3)" |
| | : isCompleted |
| | ? "0 4px 8px -2px rgba(16, 185, 129, 0.3)" |
| | : "none", |
| | }} |
| | > |
| | {(isActive && !isComplete) ? ( |
| | <motion.div |
| | animate={{ rotate: 360 }} |
| | transition={{ duration: 1.5, repeat: Infinity, ease: "linear" }} |
| | > |
| | <Loader2 className="h-4 w-4 text-white" /> |
| | </motion.div> |
| | ) : isCompleted ? ( |
| | <CheckCircle2 className="h-4 w-4 text-white" /> |
| | ) : ( |
| | <Icon className={cn("h-4 w-4 text-slate-400")} /> |
| | )} |
| | </motion.div> |
| | <span |
| | className={cn( |
| | "text-xs font-medium hidden sm:inline", |
| | isActive ? "text-indigo-600" : isCompleted ? "text-emerald-600" : "text-slate-400" |
| | )} |
| | > |
| | {step.label} |
| | </span> |
| | </div> |
| | |
| | {index < steps.length - 1 && ( |
| | <div className="flex-1 h-0.5 mx-1 relative overflow-hidden rounded-full bg-slate-100"> |
| | <motion.div |
| | initial={{ width: 0 }} |
| | animate={{ |
| | width: isCompleted ? "100%" : isActive ? "50%" : "0%", |
| | }} |
| | transition={{ duration: 0.5 }} |
| | className={cn( |
| | "absolute inset-y-0 left-0", |
| | isCompleted ? "bg-emerald-500" : "bg-indigo-500" |
| | )} |
| | /> |
| | </div> |
| | )} |
| | </React.Fragment> |
| | ); |
| | })} |
| | </div> |
| | </motion.div> |
| | ); |
| | } |
| |
|