Files
hristudio/src/components/trials/wizard/TrialProgress.tsx

391 lines
13 KiB
TypeScript
Executable File

"use client";
import {
Activity,
Bot,
CheckCircle,
Circle,
Clock,
GitBranch,
Play,
Target,
Users,
} from "lucide-react";
import { Badge } from "~/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Progress } from "~/components/ui/progress";
import { Separator } from "~/components/ui/separator";
interface TrialProgressProps {
steps: Array<{
id: string;
name: string;
type:
| "wizard_action"
| "robot_action"
| "parallel_steps"
| "conditional_branch";
description?: string;
duration?: number;
parameters?: Record<string, unknown>;
}>;
currentStepIndex: number;
trialStatus: "scheduled" | "in_progress" | "completed" | "aborted" | "failed";
}
const stepTypeConfig = {
wizard_action: {
label: "Wizard",
icon: Play,
color: "blue",
bgColor: "bg-blue-100",
textColor: "text-blue-600",
borderColor: "border-blue-300",
},
robot_action: {
label: "Robot",
icon: Bot,
color: "green",
bgColor: "bg-green-100",
textColor: "text-green-600",
borderColor: "border-green-300",
},
parallel_steps: {
label: "Parallel",
icon: Users,
color: "purple",
bgColor: "bg-purple-100",
textColor: "text-purple-600",
borderColor: "border-purple-300",
},
conditional_branch: {
label: "Branch",
icon: GitBranch,
color: "orange",
bgColor: "bg-orange-100",
textColor: "text-orange-600",
borderColor: "border-orange-300",
},
};
export function TrialProgress({
steps,
currentStepIndex,
trialStatus,
}: TrialProgressProps) {
if (!steps || steps.length === 0) {
return (
<Card>
<CardContent className="p-6 text-center">
<div className="text-slate-500">
<Target className="mx-auto mb-2 h-8 w-8 opacity-50" />
<p className="text-sm">No experiment steps defined</p>
</div>
</CardContent>
</Card>
);
}
const progress =
trialStatus === "completed"
? 100
: trialStatus === "aborted"
? 0
: ((currentStepIndex + 1) / steps.length) * 100;
const completedSteps =
trialStatus === "completed"
? steps.length
: trialStatus === "aborted" || trialStatus === "failed"
? 0
: currentStepIndex;
const getStepStatus = (index: number) => {
if (trialStatus === "aborted" || trialStatus === "failed") return "aborted";
if (trialStatus === "completed" || index < currentStepIndex)
return "completed";
if (index === currentStepIndex && trialStatus === "in_progress")
return "active";
if (index === currentStepIndex && trialStatus === "scheduled")
return "pending";
return "upcoming";
};
const getStepStatusConfig = (status: string) => {
switch (status) {
case "completed":
return {
icon: CheckCircle,
iconColor: "text-green-600",
bgColor: "bg-green-100",
borderColor: "border-green-300",
textColor: "text-green-800",
};
case "active":
return {
icon: Activity,
iconColor: "text-blue-600",
bgColor: "bg-blue-100",
borderColor: "border-blue-300",
textColor: "text-blue-800",
};
case "pending":
return {
icon: Clock,
iconColor: "text-amber-600",
bgColor: "bg-amber-100",
borderColor: "border-amber-300",
textColor: "text-amber-800",
};
case "aborted":
return {
icon: Circle,
iconColor: "text-red-600",
bgColor: "bg-red-100",
borderColor: "border-red-300",
textColor: "text-red-800",
};
default: // upcoming
return {
icon: Circle,
iconColor: "text-slate-400",
bgColor: "bg-slate-100",
borderColor: "border-slate-300",
textColor: "text-slate-600",
};
}
};
const totalDuration = steps.reduce(
(sum, step) => sum + (step.duration ?? 0),
0,
);
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center space-x-2">
<Target className="h-5 w-5" />
<span>Trial Progress</span>
</CardTitle>
<div className="flex items-center space-x-2">
<Badge variant="outline" className="text-xs">
{completedSteps}/{steps.length} steps
</Badge>
{totalDuration > 0 && (
<Badge variant="outline" className="text-xs">
~{Math.round(totalDuration / 60)}min
</Badge>
)}
</div>
</div>
</CardHeader>
<CardContent className="space-y-6">
{/* Overall Progress Bar */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-slate-600">Overall Progress</span>
<span className="font-medium">{Math.round(progress)}%</span>
</div>
<Progress
value={progress}
className={`h-2 ${
trialStatus === "completed"
? "bg-green-100"
: trialStatus === "aborted" || trialStatus === "failed"
? "bg-red-100"
: "bg-blue-100"
}`}
/>
<div className="flex justify-between text-xs text-slate-500">
<span>Start</span>
<span>
{trialStatus === "completed"
? "Completed"
: trialStatus === "aborted"
? "Aborted"
: trialStatus === "failed"
? "Failed"
: trialStatus === "in_progress"
? "In Progress"
: "Not Started"}
</span>
</div>
</div>
<Separator />
{/* Steps Timeline */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-slate-900">
Experiment Steps
</h4>
<div className="space-y-3">
{steps.map((step, index) => {
const stepConfig = stepTypeConfig[step.type];
const StepIcon = stepConfig.icon;
const status = getStepStatus(index);
const statusConfig = getStepStatusConfig(status);
const StatusIcon = statusConfig.icon;
return (
<div key={step.id} className="relative">
{/* Connection Line */}
{index < steps.length - 1 && (
<div
className={`absolute top-12 left-6 h-6 w-0.5 ${
getStepStatus(index + 1) === "completed" ||
(getStepStatus(index + 1) === "active" &&
status === "completed")
? "bg-green-300"
: "bg-slate-300"
}`}
/>
)}
{/* Step Card */}
<div
className={`flex items-start space-x-3 rounded-lg border p-3 transition-all ${
status === "active"
? `${statusConfig.bgColor} ${statusConfig.borderColor} shadow-md ring-2 ring-blue-200`
: status === "completed"
? `${statusConfig.bgColor} ${statusConfig.borderColor}`
: status === "aborted"
? `${statusConfig.bgColor} ${statusConfig.borderColor}`
: "border-slate-200 bg-slate-50"
}`}
>
{/* Step Number & Status */}
<div className="flex-shrink-0 space-y-1">
<div
className={`flex h-8 w-12 items-center justify-center rounded-lg ${
status === "active"
? statusConfig.bgColor
: status === "completed"
? "bg-green-100"
: status === "aborted"
? "bg-red-100"
: "bg-slate-100"
}`}
>
<span
className={`text-sm font-medium ${
status === "active"
? statusConfig.textColor
: status === "completed"
? "text-green-700"
: status === "aborted"
? "text-red-700"
: "text-slate-600"
}`}
>
{index + 1}
</span>
</div>
<div className="flex justify-center">
<StatusIcon
className={`h-4 w-4 ${statusConfig.iconColor}`}
/>
</div>
</div>
{/* Step Content */}
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<h5
className={`truncate font-medium ${
status === "active"
? "text-slate-900"
: status === "completed"
? "text-green-900"
: status === "aborted"
? "text-red-900"
: "text-slate-700"
}`}
>
{step.name}
</h5>
{step.description && (
<p className="mt-1 line-clamp-2 text-sm text-slate-600">
{step.description}
</p>
)}
</div>
<div className="ml-3 flex-shrink-0 space-y-1">
<Badge
variant="outline"
className={`text-xs ${stepConfig.textColor} ${stepConfig.borderColor}`}
>
<StepIcon className="mr-1 h-3 w-3" />
{stepConfig.label}
</Badge>
{step.duration && (
<div className="flex items-center space-x-1 text-xs text-slate-500">
<Clock className="h-3 w-3" />
<span>{step.duration}s</span>
</div>
)}
</div>
</div>
{/* Step Status Message */}
{status === "active" && trialStatus === "in_progress" && (
<div className="mt-2 flex items-center space-x-1 text-sm text-blue-600">
<Activity className="h-3 w-3 animate-pulse" />
<span>Currently executing...</span>
</div>
)}
{status === "active" && trialStatus === "scheduled" && (
<div className="mt-2 flex items-center space-x-1 text-sm text-amber-600">
<Clock className="h-3 w-3" />
<span>Ready to start</span>
</div>
)}
{status === "completed" && (
<div className="mt-2 flex items-center space-x-1 text-sm text-green-600">
<CheckCircle className="h-3 w-3" />
<span>Completed</span>
</div>
)}
</div>
</div>
</div>
);
})}
</div>
</div>
{/* Summary Stats */}
<Separator />
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-green-600">
{completedSteps}
</div>
<div className="text-xs text-slate-600">Completed</div>
</div>
<div>
<div className="text-2xl font-bold text-blue-600">
{trialStatus === "in_progress" ? 1 : 0}
</div>
<div className="text-xs text-slate-600">Active</div>
</div>
<div>
<div className="text-2xl font-bold text-slate-600">
{steps.length -
completedSteps -
(trialStatus === "in_progress" ? 1 : 0)}
</div>
<div className="text-xs text-slate-600">Remaining</div>
</div>
</div>
</CardContent>
</Card>
);
}