Displays the progress of the manga matching process, including progress bar, status, and time estimate.
export const MatchingProgressPanel: React.FC<MatchingProgressProps> = ({ isCancelling, progress, statusMessage, detailMessage, timeEstimate, onCancelProcess, bypassCache, freshSearch, disableControls = false,}) => { const progressPercent = progress.total ? Math.min(100, Math.round((progress.current / progress.total) * 100)) : 0; return ( <Card className="border-border/40 mb-8 overflow-hidden border shadow-md backdrop-blur-sm dark:bg-gray-800/60"> <CardHeader className="pb-4"> <CardTitle className="flex items-center justify-center gap-2 text-center text-2xl"> {isCancelling ? ( <> <AlertOctagon className="h-5 w-5 text-amber-500" /> Cancelling... </> ) : ( <> <Loader2 className="h-5 w-5 animate-spin text-blue-500" /> {statusMessage || "Matching your manga..."} </> )} </CardTitle> {detailMessage && ( <CardDescription className="text-center text-base"> {detailMessage} </CardDescription> )} </CardHeader> <CardContent className="space-y-6 pb-6"> {/* Progress Bar - with animation */} <div className="space-y-2"> <div className="flex justify-between text-sm"> <span> {progress.current} of {progress.total} </span> <span>{progressPercent}%</span> </div> <Progress value={progressPercent} className="h-3 w-full" /> </div> {/* Time Estimate */} {progress.current > 0 && timeEstimate.estimatedRemainingSeconds > 0 && ( <div className="bg-muted/50 rounded-lg p-3 text-center text-sm"> <p className="font-medium"> Estimated time remaining:{" "} <span className="text-primary"> {formatTimeRemaining(timeEstimate.estimatedRemainingSeconds)} </span> </p> </div> )} {/* Current Manga Display */} {progress.currentTitle && ( <motion.div initial={{ opacity: 0, y: 5 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} className="bg-primary/5 rounded-lg p-4 text-center" > <p className="text-sm"> Currently processing:{" "} <span className="text-primary font-semibold"> {progress.currentTitle} </span> </p> </motion.div> )} {/* Cache indicator */} {(bypassCache || freshSearch) && ( <div className="rounded-lg bg-blue-50 p-3 text-center text-sm dark:bg-blue-900/20"> <p className="flex items-center justify-center gap-2 font-medium text-blue-700 dark:text-blue-300"> <RotateCcw className="h-4 w-4 animate-spin" /> Performing fresh searches from AniList </p> </div> )} </CardContent> <CardFooter className="flex justify-center pt-0 pb-6"> <Button variant={isCancelling ? "outline" : "destructive"} size="lg" onClick={onCancelProcess} disabled={isCancelling || disableControls} className="w-full max-w-xs transition-all duration-200" > {isCancelling ? "Cancelling..." : "Cancel Process"} </Button> </CardFooter> </Card> );}; Copy
export const MatchingProgressPanel: React.FC<MatchingProgressProps> = ({ isCancelling, progress, statusMessage, detailMessage, timeEstimate, onCancelProcess, bypassCache, freshSearch, disableControls = false,}) => { const progressPercent = progress.total ? Math.min(100, Math.round((progress.current / progress.total) * 100)) : 0; return ( <Card className="border-border/40 mb-8 overflow-hidden border shadow-md backdrop-blur-sm dark:bg-gray-800/60"> <CardHeader className="pb-4"> <CardTitle className="flex items-center justify-center gap-2 text-center text-2xl"> {isCancelling ? ( <> <AlertOctagon className="h-5 w-5 text-amber-500" /> Cancelling... </> ) : ( <> <Loader2 className="h-5 w-5 animate-spin text-blue-500" /> {statusMessage || "Matching your manga..."} </> )} </CardTitle> {detailMessage && ( <CardDescription className="text-center text-base"> {detailMessage} </CardDescription> )} </CardHeader> <CardContent className="space-y-6 pb-6"> {/* Progress Bar - with animation */} <div className="space-y-2"> <div className="flex justify-between text-sm"> <span> {progress.current} of {progress.total} </span> <span>{progressPercent}%</span> </div> <Progress value={progressPercent} className="h-3 w-full" /> </div> {/* Time Estimate */} {progress.current > 0 && timeEstimate.estimatedRemainingSeconds > 0 && ( <div className="bg-muted/50 rounded-lg p-3 text-center text-sm"> <p className="font-medium"> Estimated time remaining:{" "} <span className="text-primary"> {formatTimeRemaining(timeEstimate.estimatedRemainingSeconds)} </span> </p> </div> )} {/* Current Manga Display */} {progress.currentTitle && ( <motion.div initial={{ opacity: 0, y: 5 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} className="bg-primary/5 rounded-lg p-4 text-center" > <p className="text-sm"> Currently processing:{" "} <span className="text-primary font-semibold"> {progress.currentTitle} </span> </p> </motion.div> )} {/* Cache indicator */} {(bypassCache || freshSearch) && ( <div className="rounded-lg bg-blue-50 p-3 text-center text-sm dark:bg-blue-900/20"> <p className="flex items-center justify-center gap-2 font-medium text-blue-700 dark:text-blue-300"> <RotateCcw className="h-4 w-4 animate-spin" /> Performing fresh searches from AniList </p> </div> )} </CardContent> <CardFooter className="flex justify-center pt-0 pb-6"> <Button variant={isCancelling ? "outline" : "destructive"} size="lg" onClick={onCancelProcess} disabled={isCancelling || disableControls} className="w-full max-w-xs transition-all duration-200" > {isCancelling ? "Cancelling..." : "Cancel Process"} </Button> </CardFooter> </Card> );};
<MatchingProgressPanel isCancelling={false} progress={progress} statusMessage="Matching..." detailMessage={detail} timeEstimate={estimate} onCancelProcess={handleCancel}/> Copy
<MatchingProgressPanel isCancelling={false} progress={progress} statusMessage="Matching..." detailMessage={detail} timeEstimate={estimate} onCancelProcess={handleCancel}/>
The props for the MatchingProgressPanel component.
The rendered matching progress panel React element.
Displays the progress of the manga matching process, including progress bar, status, and time estimate.
Source
Example