summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/contracts/PhaseProgressBar.tsx
blob: 9589db9465cf7171a6c8ab46163622d705bc6bd5 (plain) (tree)
1
2
3
4
5
6

                                                                 


                                 
                              











































                                                                                                     
               


                           

                                                                             


                                             
                                            










































                                                                                               
                                                  















                                                                                          
               

                              
                              
    

                                                                             


                                               
                                            

















                                                                                            
import type { ContractPhase, ContractType } from "../../lib/api";
import { getValidPhases } from "../../lib/api";

interface PhaseProgressBarProps {
  currentPhase: ContractPhase;
  contractType?: ContractType;
  onPhaseClick?: (phase: ContractPhase) => void;
  readonly?: boolean;
}

const phases: ContractPhase[] = ["research", "specify", "plan", "execute", "review"];

const phaseLabels: Record<ContractPhase, string> = {
  research: "Research",
  specify: "Specify",
  plan: "Plan",
  execute: "Execute",
  review: "Review",
};

const phaseColors: Record<ContractPhase, { active: string; inactive: string; completed: string }> = {
  research: {
    active: "bg-purple-400 border-purple-400",
    inactive: "bg-transparent border-purple-400/30",
    completed: "bg-purple-400/50 border-purple-400/50",
  },
  specify: {
    active: "bg-blue-400 border-blue-400",
    inactive: "bg-transparent border-blue-400/30",
    completed: "bg-blue-400/50 border-blue-400/50",
  },
  plan: {
    active: "bg-cyan-400 border-cyan-400",
    inactive: "bg-transparent border-cyan-400/30",
    completed: "bg-cyan-400/50 border-cyan-400/50",
  },
  execute: {
    active: "bg-yellow-400 border-yellow-400",
    inactive: "bg-transparent border-yellow-400/30",
    completed: "bg-yellow-400/50 border-yellow-400/50",
  },
  review: {
    active: "bg-green-400 border-green-400",
    inactive: "bg-transparent border-green-400/30",
    completed: "bg-green-400/50 border-green-400/50",
  },
};

export function PhaseProgressBar({
  currentPhase,
  contractType,
  onPhaseClick,
  readonly = false,
}: PhaseProgressBarProps) {
  const visiblePhases = contractType ? getValidPhases(contractType) : phases;
  const currentIndex = visiblePhases.indexOf(currentPhase);

  return (
    <div className="flex items-center gap-1">
      {visiblePhases.map((phase, index) => {
        const isActive = phase === currentPhase;
        const isCompleted = index < currentIndex;
        const colors = phaseColors[phase];
        const colorClass = isActive
          ? colors.active
          : isCompleted
          ? colors.completed
          : colors.inactive;

        const canClick = !readonly && onPhaseClick;

        return (
          <div key={phase} className="flex items-center">
            {/* Phase node */}
            <button
              onClick={() => canClick && onPhaseClick(phase)}
              disabled={readonly}
              className={`
                relative group flex flex-col items-center
                ${canClick ? "cursor-pointer" : "cursor-default"}
              `}
            >
              {/* Circle */}
              <div
                className={`
                  w-3 h-3 rounded-full border-2 transition-all
                  ${colorClass}
                  ${canClick && !isActive ? "hover:scale-110" : ""}
                `}
              />
              {/* Label */}
              <span
                className={`
                  absolute top-4 font-mono text-[9px] uppercase tracking-wide whitespace-nowrap
                  ${isActive ? "text-[#dbe7ff]" : "text-[#555]"}
                  ${canClick && !isActive ? "group-hover:text-[#75aafc]" : ""}
                `}
              >
                {phaseLabels[phase]}
              </span>
            </button>

            {/* Connector line */}
            {index < visiblePhases.length - 1 && (
              <div
                className={`
                  w-8 h-0.5 mx-1
                  ${index < currentIndex ? "bg-[#3f6fb3]" : "bg-[rgba(117,170,252,0.15)]"}
                `}
              />
            )}
          </div>
        );
      })}
    </div>
  );
}

export function PhaseProgressBarCompact({
  currentPhase,
  contractType,
}: {
  currentPhase: ContractPhase;
  contractType?: ContractType;
}) {
  const visiblePhases = contractType ? getValidPhases(contractType) : phases;
  const currentIndex = visiblePhases.indexOf(currentPhase);

  return (
    <div className="flex items-center gap-0.5">
      {visiblePhases.map((phase, index) => {
        const isActive = phase === currentPhase;
        const isCompleted = index < currentIndex;
        const colors = phaseColors[phase];

        return (
          <div
            key={phase}
            className={`
              w-2 h-2 rounded-full border
              ${isActive ? colors.active : isCompleted ? colors.completed : colors.inactive}
            `}
            title={phaseLabels[phase]}
          />
        );
      })}
    </div>
  );
}