/* ============================================================================= * group_workflow.ts * D 예정노선 · E 안정성평가 단계 묶음(요구 345 · PLAN 529-1) — B05~B09 틀(`createWorkflowLayout`)을 * 단계 목록 · 아이콘 · 주소 인자로 그대로 씀. 빈 단계 = 틀 · 좌측 패널([?] [<] [>]) · 단계 막대만. * 페이지 길 = 각 폴더 `draft/<폴더>_Route.ts` · 본문이 생기면 그 폴더 페이지로 갈아 끼움. * 떠남(560-4) = `beforeLeave` 가 true 일 때만 — 단계 아이콘 · [<] [>] 는 기다린 뒤 이동 · * 머리 메뉴 · 뒤로 가기는 같은 함수를 `setLeaveGuard` 로(false = 머묾). * ========================================================================== */ import { API_BASE_URL, PLAN_STEP_ROUTES, STAB_STEP_ROUTES, type RoutePath, } from "@config/config_frontend"; import { el, hideLoadingOverlay, showLoadingOverlay, showToast } from "@ui/ui_template_elements"; import { t, type LocaleKey } from "@ui/ui_template_locale"; import { createWorkflowLayout } from "@ui/ui_template_workflow_layout"; import { guardProjectLeave, setCurrentProject, type ProjectLeaveGuard } from "./b_project_switch"; import { navigateTo, setLeaveGuard, type PageRenderer } from "./router"; interface GroupFlow { routes: readonly RoutePath[]; labels: readonly LocaleKey[]; icons: readonly string[]; } const FLOWS: readonly GroupFlow[] = [ { routes: PLAN_STEP_ROUTES, labels: ["DE_Step_PlanBase", "DE_Step_PlanAnalysis", "DE_Step_PlanConfirm"], icons: ["📍", "🧭", "✅"], }, { routes: STAB_STEP_ROUTES, labels: [ "DE_Step_StabGround", "DE_Step_StabSection", "DE_Step_Stab3D", "DE_Step_StabRock", "DE_Step_StabArea", "DE_Step_StabReport", ], icons: ["🪨", "📐", "🧊", "⛰️", "🗺️", "📄"], }, ]; /** 종류별 단계 묶음(대시보드 표 단계 막대 · 528-2) — ROAD · 모르는 종류 = 없음(B 막대 그대로) */ export function groupFlowOf( kind: string | null | undefined, ): { routes: readonly RoutePath[]; steps: string[]; icons: readonly string[] } | null { const flow = { PLAN: FLOWS[0], STABILITY: FLOWS[1] }[kind ?? ""]; return flow ? { ...flow, steps: flow.labels.map((key) => t(key)) } : null; } /** 단계 틀을 세움 — mainContent · leftPanel 이 없으면 빈 단계 안내 · [?] 줄만 */ export function mountGroupStep( root: HTMLElement, route: RoutePath, parts: { mainContent?: HTMLElement; leftPanel?: HTMLElement; /** 옛 떠남(저장 등 · false = 머묾) — 페이지가 dirty · save 로 옮기면 지움 */ beforeLeave?: () => Promise; } & Partial = {}, ): void { const flow = FLOWS.find((f) => f.routes.includes(route)); if (!flow) throw new Error(`단계 묶음 밖 주소: ${route}`); const active = flow.routes.indexOf(route); const leave = parts.beforeLeave; if (leave) setLeaveGuard(async () => !root.isConnected || (await leave())); // 떠남 셋(401) — 단계 아이콘 · [<] [>] · 머리 메뉴 · 뒤로 모두 라우터 물음 한 자리([취소 · 폐기 · 저장]) const { dirty, save, discard, message } = parts; if (dirty && save) guardProjectLeave(root, { dirty, save, discard, message }); const layout = createWorkflowLayout({ title: t(flow.labels[active]), steps: flow.labels.map((key) => t(key)), icons: flow.icons, activeStep: active, routes: flow.routes, leftPanel: parts.leftPanel, mainContent: parts.mainContent ?? el("p", { className: "app-placeholder", text: t("DE_Step_Empty") }), onStepClick: (_index, next) => { if (!leave) return navigateTo(next as RoutePath); void leave().then((ok) => ok && navigateTo(next as RoutePath)); }, }); root.append(layout.root); } /** 빈 단계 페이지(길 선언의 load 가 부름) */ export const groupStepPage = (route: RoutePath): PageRenderer => (root) => mountGroupStep(root, route); /** 등록 화면 [등록](D01 · E01) — POST → 현재 프로젝트로 기억 → 첫 단계로 · 실패 = 서버 까닭 알림 */ export async function createGroupProject( path: string, body: object, firstStep: RoutePath, ): Promise { showLoadingOverlay(); try { const response = await fetch(`${API_BASE_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(body), }); const data = (await response.json().catch(() => ({}))) as { project_id?: string; detail?: unknown; }; if (!response.ok || !data.project_id) { const detail = typeof data.detail === "string" ? data.detail : `HTTP ${response.status}`; throw new Error(detail); } setCurrentProject(data.project_id); showToast(t("DE_Reg_Success"), "success"); navigateTo(firstStep); } catch (error) { showToast(error instanceof Error ? error.message : t("Common_Status_Error"), "error"); } finally { hideLoadingOverlay(); } }