136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import React from "react";
|
|
import { Check } from "lucide-react";
|
|
|
|
export type StageKey = "scan" | "route" | "section" | "design" | "quantity" | "document";
|
|
|
|
export interface WorkflowState {
|
|
current_stage: StageKey;
|
|
completed: StageKey[];
|
|
stale_from: StageKey | null;
|
|
stage1_confirmed: any | null;
|
|
}
|
|
|
|
interface StageInfo {
|
|
key: StageKey;
|
|
label: string;
|
|
}
|
|
|
|
const STAGES: StageInfo[] = [
|
|
{ key: "scan", label: "지표면 모델 분석" },
|
|
{ key: "route", label: "경로 설계 (BP/EP/CP)" },
|
|
{ key: "section", label: "종·횡단 생성" },
|
|
{ key: "design", label: "예정 설계" },
|
|
{ key: "quantity", label: "수량 산출" },
|
|
{ key: "document", label: "견적/문서" },
|
|
];
|
|
|
|
interface WorkflowBarProps {
|
|
state: WorkflowState;
|
|
onNavigate: (stage: StageKey) => void;
|
|
}
|
|
|
|
export function WorkflowBar({ state, onNavigate }: WorkflowBarProps) {
|
|
return (
|
|
<div className="workflow-bar" style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
background: "#1e293b",
|
|
padding: "12px 24px",
|
|
borderRadius: "8px",
|
|
marginBottom: "20px",
|
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
border: "1px solid #334155"
|
|
}}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "16px", flex: 1 }}>
|
|
{STAGES.map((stage, idx) => {
|
|
const isCurrent = state.current_stage === stage.key;
|
|
const isCompleted = state.completed.includes(stage.key);
|
|
const isStale = state.stale_from !== null && STAGES.findIndex(s => s.key === state.stale_from) <= idx;
|
|
const canClick = stage.key === "scan"
|
|
|| (stage.key === "route" && state.completed.includes("scan"))
|
|
|| (stage.key === "section" && state.completed.includes("route"))
|
|
|| isCompleted;
|
|
|
|
// 현재는 1~3단계까지 활성화한다.
|
|
const isSupported = stage.key === "scan" || stage.key === "route" || stage.key === "section";
|
|
const activeClick = canClick && isSupported;
|
|
|
|
return (
|
|
<React.Fragment key={stage.key}>
|
|
<div
|
|
onClick={() => activeClick && onNavigate(stage.key)}
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
cursor: activeClick ? "pointer" : "not-allowed",
|
|
opacity: isSupported ? 1 : 0.4,
|
|
position: "relative"
|
|
}}
|
|
>
|
|
<div style={{
|
|
width: "28px",
|
|
height: "28px",
|
|
borderRadius: "50%",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontSize: "12px",
|
|
fontWeight: "bold",
|
|
background: isCurrent
|
|
? "#38bdf8"
|
|
: isCompleted
|
|
? "#10b981"
|
|
: "#475569",
|
|
color: isCurrent || isCompleted ? "#0f172a" : "#cbd5e1",
|
|
border: isCurrent ? "2px solid #ffffff" : "none",
|
|
transition: "all 0.2s ease"
|
|
}}>
|
|
{isCompleted ? <Check size={14} /> : idx + 1}
|
|
</div>
|
|
<span style={{
|
|
fontSize: "14px",
|
|
fontWeight: isCurrent ? "bold" : "normal",
|
|
color: isCurrent
|
|
? "#38bdf8"
|
|
: isCompleted
|
|
? "#f8fafc"
|
|
: "#94a3b8"
|
|
}}>
|
|
{stage.label}
|
|
</span>
|
|
|
|
{isStale && (
|
|
<span style={{
|
|
position: "absolute",
|
|
top: "-10px",
|
|
right: "-15px",
|
|
background: "#f59e0b",
|
|
color: "#0f172a",
|
|
fontSize: "9px",
|
|
fontWeight: "bold",
|
|
padding: "1px 4px",
|
|
borderRadius: "4px",
|
|
whiteSpace: "nowrap"
|
|
}}>
|
|
stale
|
|
</span>
|
|
)}
|
|
</div>
|
|
{idx < STAGES.length - 1 && (
|
|
<div style={{
|
|
flex: 1,
|
|
height: "2px",
|
|
background: isCompleted ? "#10b981" : "#475569",
|
|
margin: "0 12px"
|
|
}} />
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|