- Ajoute migration 0015 : colonne phase2_started_at sur index_jobs - Indexer : renseigne phase2_started_at lors du passage à generating_thumbnails - API : expose phase2_started_at et book_id dans IndexJobDetailResponse - Page détail : timeline avec durée de chaque phase (Discovery / Thumbnails) - Page détail : banners contextuels (success/failed/cancelled) avec résumé en une ligne - Page détail : description textuelle du type de job, durée dans l'overview - Page détail : stats normalisées selon le type (index vs thumbnail-only) - JobRow : affiche le type via JobTypeBadge (cohérence visuelle) - Badge : labels lisibles pour tous les types de jobs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
3.6 KiB
TypeScript
133 lines
3.6 KiB
TypeScript
import { ReactNode } from "react";
|
|
|
|
type BadgeVariant =
|
|
| "default"
|
|
| "primary"
|
|
| "secondary"
|
|
| "destructive"
|
|
| "outline"
|
|
| "success"
|
|
| "warning"
|
|
| "error"
|
|
| "muted"
|
|
| "unread"
|
|
| "in-progress"
|
|
| "completed";
|
|
|
|
interface BadgeProps {
|
|
children: ReactNode;
|
|
variant?: BadgeVariant;
|
|
className?: string;
|
|
}
|
|
|
|
const variantStyles: Record<BadgeVariant, string> = {
|
|
// shadcn/ui compatible
|
|
default: "bg-primary/90 text-primary-foreground border-transparent hover:bg-primary/80 backdrop-blur-md",
|
|
secondary: "bg-secondary/80 text-secondary-foreground border-transparent hover:bg-secondary/60 backdrop-blur-md",
|
|
destructive: "bg-destructive/90 text-destructive-foreground border-transparent hover:bg-destructive/80 backdrop-blur-md",
|
|
outline: "text-foreground border-border bg-background/50",
|
|
|
|
// Legacy + Additional variants
|
|
primary: "bg-primary/90 text-primary-foreground backdrop-blur-md",
|
|
success: "bg-success/90 text-success-foreground backdrop-blur-md",
|
|
warning: "bg-warning/90 text-white backdrop-blur-md",
|
|
error: "bg-destructive/90 text-destructive-foreground backdrop-blur-md",
|
|
muted: "bg-muted/60 text-muted-foreground backdrop-blur-md",
|
|
|
|
// Status badges from StripStream
|
|
unread: "badge-unread backdrop-blur-md",
|
|
"in-progress": "badge-in-progress backdrop-blur-md",
|
|
completed: "badge-completed backdrop-blur-md",
|
|
};
|
|
|
|
export function Badge({ children, variant = "default", className = "" }: BadgeProps) {
|
|
return (
|
|
<span className={`
|
|
inline-flex items-center
|
|
px-2.5 py-0.5
|
|
rounded-full
|
|
text-xs font-semibold
|
|
border
|
|
transition-colors duration-200
|
|
${variantStyles[variant]}
|
|
${className}
|
|
`}>
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
// Status badge for jobs/tasks
|
|
const statusVariants: Record<string, BadgeVariant> = {
|
|
running: "in-progress",
|
|
generating_thumbnails: "in-progress",
|
|
success: "completed",
|
|
completed: "completed",
|
|
failed: "error",
|
|
cancelled: "muted",
|
|
pending: "warning",
|
|
unread: "unread",
|
|
};
|
|
|
|
const statusLabels: Record<string, string> = {
|
|
generating_thumbnails: "Thumbnails",
|
|
};
|
|
|
|
interface StatusBadgeProps {
|
|
status: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function StatusBadge({ status, className = "" }: StatusBadgeProps) {
|
|
const key = status.toLowerCase();
|
|
const variant = statusVariants[key] || "default";
|
|
const label = statusLabels[key] ?? status;
|
|
return <Badge variant={variant} className={className}>{label}</Badge>;
|
|
}
|
|
|
|
// Job type badge
|
|
const jobTypeVariants: Record<string, BadgeVariant> = {
|
|
rebuild: "primary",
|
|
full_rebuild: "warning",
|
|
thumbnail_rebuild: "secondary",
|
|
thumbnail_regenerate: "warning",
|
|
};
|
|
|
|
const jobTypeLabels: Record<string, string> = {
|
|
rebuild: "Index",
|
|
full_rebuild: "Full Index",
|
|
thumbnail_rebuild: "Thumbnails",
|
|
thumbnail_regenerate: "Regen. Thumbnails",
|
|
cbr_to_cbz: "CBR → CBZ",
|
|
};
|
|
|
|
interface JobTypeBadgeProps {
|
|
type: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function JobTypeBadge({ type, className = "" }: JobTypeBadgeProps) {
|
|
const key = type.toLowerCase();
|
|
const variant = jobTypeVariants[key] || "default";
|
|
const label = jobTypeLabels[key] ?? type;
|
|
return <Badge variant={variant} className={className}>{label}</Badge>;
|
|
}
|
|
|
|
// Progress badge (shows percentage)
|
|
interface ProgressBadgeProps {
|
|
progress: number;
|
|
className?: string;
|
|
}
|
|
|
|
export function ProgressBadge({ progress, className = "" }: ProgressBadgeProps) {
|
|
let variant: BadgeVariant = "unread";
|
|
if (progress === 100) variant = "completed";
|
|
else if (progress > 0) variant = "in-progress";
|
|
|
|
return (
|
|
<Badge variant={variant} className={className}>
|
|
{progress}%
|
|
</Badge>
|
|
);
|
|
}
|