import "./ui_template_workflow_layout.css"; import { t } from "./ui_template_locale"; 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; onMenuToggle?: (isOpen: boolean) => void; onHeaderToggle?: (isVisible: boolean) => void; } export interface WorkflowLayoutHandle { root: HTMLElement; setMenuOpen: (isOpen: boolean) => void; setHeaderVisible: (isVisible: boolean) => void; } export interface StepBarOptions { stages?: WorkflowStage[]; currentStage?: number; routes?: readonly string[]; compact?: boolean; onStepClick?: (stepIndex: number, route: string) => void; } 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"); 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"); button.textContent = step; const stage = options?.stages?.find((item) => item.stage_no === index) ?? options?.stages?.[index]; const hasStages = Boolean(options?.stages?.length); const isEnabled = !hasStages || stage?.state !== "NOT_STARTED" || 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 header = document.createElement("header"); header.className = "ui-workflow-layout__header"; const menuButton = document.createElement("button"); menuButton.type = "button"; menuButton.className = "ui-workflow-layout__menu-button"; menuButton.setAttribute("aria-label", "Toggle settings panel"); menuButton.innerHTML = "🛠️ Option ▾"; const titleWrap = document.createElement("div"); titleWrap.className = "ui-workflow-layout__title-wrap"; const title = document.createElement("h2"); title.className = "ui-workflow-layout__title"; title.textContent = options.title; titleWrap.append( title, createStepBar(options.steps, options.activeStep, { stages: options.stages, currentStage: options.currentStage, routes: options.routes, onStepClick: options.onStepClick, }), ); const headerHideButton = document.createElement("button"); headerHideButton.type = "button"; headerHideButton.className = "ui-workflow-layout__header-hide"; headerHideButton.setAttribute("aria-label", "Hide header"); headerHideButton.textContent = "▲"; header.append(titleWrap, headerHideButton); // 헤더 숨김 시 표시되는 복원 화살표 const headerRevealButton = document.createElement("button"); headerRevealButton.type = "button"; headerRevealButton.className = "ui-workflow-layout__header-reveal"; headerRevealButton.setAttribute("aria-label", "Show header"); headerRevealButton.textContent = "▼"; const body = document.createElement("div"); body.className = "ui-workflow-layout__body"; const dropdownPanel = document.createElement("div"); dropdownPanel.className = "ui-workflow-layout__dropdown-panel"; dropdownPanel.append(options.leftPanel); const main = document.createElement("main"); main.className = "ui-workflow-layout__main"; main.style.position = "relative"; main.append(menuButton, options.mainContent); body.append(dropdownPanel, main); root.append(header, headerRevealButton, body); function setMenuOpen(isOpen: boolean): void { root.classList.toggle("is-menu-open", isOpen); menuButton.setAttribute("aria-expanded", String(isOpen)); const icon = menuButton.querySelector("span"); if (icon) { icon.textContent = isOpen ? "Option ▴" : "Option ▾"; } options.onMenuToggle?.(isOpen); } function setHeaderVisible(isVisible: boolean): void { root.classList.toggle("is-header-hidden", !isVisible); headerHideButton.setAttribute("aria-expanded", String(isVisible)); options.onHeaderToggle?.(isVisible); } menuButton.addEventListener("click", (e) => { e.stopPropagation(); setMenuOpen(!root.classList.contains("is-menu-open")); }); document.addEventListener("click", (e) => { if (root.classList.contains("is-menu-open")) { const target = e.target as HTMLElement; if (!dropdownPanel.contains(target) && !menuButton.contains(target)) { setMenuOpen(false); } } }); headerHideButton.addEventListener("click", () => setHeaderVisible(false)); headerRevealButton.addEventListener("click", () => setHeaderVisible(true)); setMenuOpen(false); setHeaderVisible(true); return { root, setMenuOpen, setHeaderVisible }; }