File size: 2,892 Bytes
5a0e691
 
2f67ecf
f42f5a6
2f67ecf
5a0e691
 
 
 
 
 
 
9a28cd0
 
 
 
 
 
5a0e691
 
 
 
 
 
 
973dd4c
5a0e691
 
 
88aa370
5a0e691
9a28cd0
 
5a0e691
2f67ecf
88aa370
 
 
 
 
 
f42f5a6
88aa370
 
 
5a0e691
9a28cd0
5a0e691
9a28cd0
5a0e691
2f67ecf
5a0e691
f42f5a6
5a0e691
 
 
f42f5a6
5a0e691
 
 
f42f5a6
5a0e691
 
2f67ecf
5a0e691
 
 
 
f42f5a6
5a0e691
 
 
 
2f67ecf
5a0e691
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
'use client';

import { motion } from 'framer-motion';
import { CheckCircle2, XCircle } from 'lucide-react';
import { fadeUp, fadeIn } from '@/lib/animations';

interface Step {
  name: string;
  label: string;
}

const STEPS: Step[] = [
  { name: 'submitted_to_ncbi', label: 'Submitted to NCBI BLAST' },
  { name: 'polling_ncbi',      label: 'NCBI is searching' },
  { name: 'parsing',           label: 'Reading results' },
  { name: 'interpreting',      label: 'Writing your explanation' },
  { name: 'fetching_alphafold', label: 'Fetching AlphaFold structure' },
  { name: 'complete',          label: 'Complete' },
];

interface JobProgressProps {
  stepsCompleted: string[];
  status: string;
}

export function JobProgress({ stepsCompleted, status }: JobProgressProps) {
  const isComplete = status === 'complete';
  const isFailed = status === 'failed';
  const stepSet = new Set(stepsCompleted);
  const pct = stepsCompleted.length === 0 ? 0 : Math.round((stepsCompleted.length / STEPS.length) * 100);

  const nextPendingIndex = stepsCompleted.length;

  return (
    <motion.div variants={fadeUp} className="bg-white rounded-2xl border border-gray-200 p-6">
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-sm font-semibold text-gray-900">Pipeline progress</h3>
        <span className="text-xs text-gray-500">{pct}%</span>
      </div>
      <div className="w-full bg-gray-100 rounded-full h-1.5 mb-5">
        <div
          className={`h-1.5 rounded-full transition-all duration-500 ${isFailed ? 'bg-error' : 'bg-accent'}`}
          style={{ width: `${pct}%` }}
        />
      </div>
      <div className="space-y-3">
        {STEPS.map((step, i) => {
          const done = stepSet.has(step.name);
          const active = status === 'running' && !done && i === nextPendingIndex;
          return (
            <motion.div key={step.name} variants={fadeIn} className="flex items-center gap-3">
              {done ? (
                <CheckCircle2 className="w-5 h-5 text-teal-600 shrink-0" />
              ) : isFailed ? (
                <XCircle className="w-5 h-5 text-red-500 shrink-0" />
              ) : active ? (
                <div className="w-5 h-5 rounded-full bg-accent animate-pulse shrink-0" />
              ) : (
                <div className="w-5 h-5 rounded-full border-2 border-gray-300 shrink-0" />
              )}
              <span className={`text-sm ${done ? 'text-gray-900 font-medium' : active ? 'text-teal-700' : 'text-gray-400'}`}>
                {step.label}
              </span>
            </motion.div>
          );
        })}
      </div>
      {isComplete && (
        <p className="text-xs text-accent mt-3 font-medium">All steps complete</p>
      )}
      {isFailed && (
        <p className="text-xs text-red-500 mt-3 font-medium">Pipeline failed</p>
      )}
    </motion.div>
  );
}