import "./ui_template_workflow_layout.css"; import { t } from "./ui_template_locale"; import { createWorkflowOverlays } from "./ui_template_overlay"; import { fetchDashboardMe } from "../B01_Dashboard/B01_Dashboard_Api_Fetch"; let dashboardRolePromise: Promise | undefined; function getDashboardRole(): Promise { dashboardRolePromise ??= fetchDashboardMe().then((user) => user.role); return dashboardRolePromise; } 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; if (index === 1) { void getDashboardRole() .then((role) => { if (role !== "SYSTEM_ADMIN") { button.classList.remove("is-enabled"); button.disabled = true; button.title = "μ‹œμŠ€ν…œ κ΄€λ¦¬μž μ „μš© λ‹¨κ³„μž…λ‹ˆλ‹€."; } }) .catch(() => undefined); } // 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, }; }