139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import "./ui_template_workflow_layout.css";
|
|
import { t } from "./ui_template_locale";
|
|
import { createWorkflowOverlays } from "./ui_template_overlay";
|
|
|
|
export interface WorkflowStage {
|
|
stage_no: number;
|
|
stage_key: string;
|
|
state: string;
|
|
}
|
|
|
|
export interface WorkflowLayoutOptions {
|
|
title: string;
|
|
steps: string[];
|
|
activeStep: number;
|
|
leftPanel?: HTMLElement;
|
|
mainContent: HTMLElement;
|
|
stages?: WorkflowStage[];
|
|
currentStage?: number;
|
|
routes?: readonly string[];
|
|
onStepClick?: (stepIndex: number, route: string) => void;
|
|
}
|
|
|
|
export interface WorkflowLayoutHandle {
|
|
root: HTMLElement;
|
|
setOptionsOpen: (isOpen: boolean) => void;
|
|
setProgressOpen: (isOpen: boolean) => void;
|
|
}
|
|
|
|
export interface StepBarOptions {
|
|
stages?: WorkflowStage[];
|
|
currentStage?: number;
|
|
routes?: readonly string[];
|
|
compact?: boolean;
|
|
orientation?: "horizontal" | "vertical";
|
|
icons?: readonly string[];
|
|
onStepClick?: (stepIndex: number, route: string) => void;
|
|
}
|
|
|
|
export const WORKFLOW_STEP_ICONS = ["📁", "🗺️", "🛣️", "📐", "🏗️", "∑", "📄"] as const;
|
|
|
|
export function createStepBar(
|
|
steps: readonly string[],
|
|
activeStep: number,
|
|
options?: StepBarOptions,
|
|
): HTMLElement {
|
|
const bar = document.createElement("div");
|
|
bar.className = "ui-workflow-layout__steps";
|
|
if (options?.compact) bar.classList.add("is-compact");
|
|
if (options?.orientation === "vertical") bar.classList.add("is-vertical");
|
|
steps.forEach((step, index) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "ui-workflow-layout__step";
|
|
if (index === activeStep) button.classList.add("is-active");
|
|
const iconText = options?.icons?.[index];
|
|
if (iconText) {
|
|
const icon = document.createElement("span");
|
|
icon.className = "ui-workflow-layout__step-icon";
|
|
icon.setAttribute("aria-hidden", "true");
|
|
icon.textContent = iconText;
|
|
const label = document.createElement("span");
|
|
label.className = "ui-workflow-layout__step-label";
|
|
label.textContent = step;
|
|
button.append(icon, label);
|
|
} else {
|
|
button.textContent = step;
|
|
}
|
|
|
|
const stage =
|
|
options?.stages?.find((item) => item.stage_no === index) ?? options?.stages?.[index];
|
|
const hasStages = Boolean(options?.stages?.length);
|
|
const isBlockedState = stage?.state === "NOT_STARTED" || stage?.state === "STALE";
|
|
const isEnabled = !hasStages || !isBlockedState || stage?.stage_no === options?.currentStage;
|
|
button.classList.toggle("is-enabled", isEnabled);
|
|
button.disabled = !isEnabled;
|
|
|
|
// state 기반 스타일링 (표시 전용)
|
|
if (stage) {
|
|
const state = stage.state;
|
|
button.classList.add(`state-${state.toLowerCase()}`);
|
|
if (state === "STALE") {
|
|
button.title = t("WF_State_Stale");
|
|
} else if (state === "FAILED") {
|
|
button.title = t("WF_State_Failed");
|
|
} else if (state === "COMPLETE") {
|
|
button.title = t("WF_State_Complete");
|
|
} else if (state === "IN_PROGRESS") {
|
|
button.title = t("WF_State_InProgress");
|
|
} else {
|
|
button.title = t("WF_State_NotStarted");
|
|
}
|
|
}
|
|
|
|
if (isEnabled && options?.routes?.[index]) {
|
|
button.addEventListener("click", () => {
|
|
options.onStepClick?.(index, options.routes![index]);
|
|
});
|
|
}
|
|
|
|
bar.append(button);
|
|
});
|
|
return bar;
|
|
}
|
|
|
|
export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLayoutHandle {
|
|
const root = document.createElement("div");
|
|
root.className = "ui-workflow-layout";
|
|
|
|
const body = document.createElement("div");
|
|
body.className = "ui-workflow-layout__body";
|
|
|
|
const main = document.createElement("main");
|
|
main.className = "ui-workflow-layout__main";
|
|
main.append(options.mainContent);
|
|
body.append(main);
|
|
|
|
const progressContent = createStepBar(options.steps, options.activeStep, {
|
|
stages: options.stages,
|
|
currentStage: options.currentStage,
|
|
routes: options.routes,
|
|
orientation: "vertical",
|
|
icons: WORKFLOW_STEP_ICONS,
|
|
onStepClick: options.onStepClick,
|
|
});
|
|
const overlays = createWorkflowOverlays({
|
|
title: options.title,
|
|
optionsContent: options.leftPanel,
|
|
progressContent,
|
|
onOptionsOpenChange: (isOpen) => root.classList.toggle("is-options-open", isOpen),
|
|
});
|
|
|
|
root.append(body, overlays.root);
|
|
return {
|
|
root,
|
|
setOptionsOpen: overlays.setTitleOpen,
|
|
setProgressOpen: overlays.setProgressOpen,
|
|
};
|
|
}
|