/* ============================================================================= * B05_Profile_UI_RouteEdit_Apply.ts * 계획노선 편집 모달의 **[확인]** — 무거운 재계산과 대기 표시. * * 다시 계산은 **노선이 바뀐 뒤 [확인]에서만** 돈다(계획서 20 ④). 노선이 창을 연 그대로면 * 계산 없이 닫는다. [초기화](옛 「예상노선으로」)는 편집값만 되돌린다(`_Initial`). * * 누르면 서버가 배수유역부터 종·횡단·유토곡선까지 전 단계를 다시 돈다(약 90초). 중간 취소는 * 만들지 않기로 했으므로(계획서 0-2, 2026-09-09) **얼마나 지났는지**를 초로 보여 사람이 * 멈춘 것인지 도는 것인지 알 수 있게 한다. * ========================================================================== */ import { clearDrafts, clearResults } from "../A00_Common/b_page_state"; import { showToast } from "@ui/ui_template_elements"; import { replanRoute } from "./B05_Profile_Api_Replan"; type Vertex = [number, number]; export interface RouteApplyParams { overlay: HTMLElement; /** 화면 전체를 덮는 대기 막. 안에 `` 한 개가 글을 받는다. */ busy: HTMLElement; projectId: string; /** 지금 편집값 — 누른 순간에 읽는다. */ nodes: () => { planned: Vertex[]; curveOn: boolean[]; curveRadius: Array }; /** 창을 연 뒤 노선이 바뀌었나 — 안 바뀌었으면 다시 계산하지 않는다. */ changed: () => boolean; /** 초기노선 갈래 — [확인]에 지금 초기값을 싣고, 결과 캐시를 비운 뒤 다시 적는다. */ initial: { current: () => unknown; keep: () => void }; /** 성공하면 모달을 닫고 화면을 다시 읽는다. */ close: () => void; onApplied: () => void | Promise; } /** [확인]을 붙인다. 리스너는 모달과 수명이 같다. */ export function bindRouteApply(params: RouteApplyParams): void { const { overlay, busy, projectId } = params; async function runHeavy(label: string, task: () => Promise): Promise { busy.hidden = false; // ⚠ 「몇 분」은 옛 값이었다 — 0-11 로 **약 90초**가 됐다(2026-09-09 실측 네 번: // 87.3 · 90.0 · 93.9 · 95.4초). const message = busy.querySelector("span")!; const started = Date.now(); const tick = (): void => { const seconds = Math.round((Date.now() - started) / 1000); message.textContent = `${label} — 배수유역부터 다시 계산 중입니다. 1분 반쯤 걸립니다 (${seconds}초 지남).`; }; tick(); const timer = window.setInterval(tick, 1000); try { await task(); // 노선이 바뀌면 세션 초안·조회 캐시는 옛 노선 것이라 남기지 않는다(PLAN 0-7 확정 5). clearDrafts(projectId); clearResults(projectId); params.initial.keep(); // 초기값은 새 노선의 출발점 — 옛 노선 결과와 함께 버리지 않는다. showToast("노선을 다시 계산했습니다.", "success"); params.close(); await params.onApplied(); } catch (error) { busy.hidden = true; showToast(error instanceof Error ? error.message : "노선 재계산에 실패했습니다.", "error"); } finally { window.clearInterval(timer); // 성공·실패·닫힘 어느 쪽이든 멈춘다 } } overlay.querySelector('[data-act="apply"]')!.addEventListener("click", () => { const { planned, curveOn, curveRadius } = params.nodes(); if (planned.length < 2) { showToast("노선은 노드가 2개 이상이어야 합니다.", "error"); return; } if (!params.changed()) { showToast("노선이 그대로라 다시 계산하지 않았습니다.", "info"); params.close(); return; } void runHeavy("계획노선 반영", () => replanRoute( projectId, planned.map(([x, y], index) => ({ x, y, curve: curveOn[index] !== false, radius_m: curveRadius[index] ?? null, })), { initial: params.initial.current() }, ), ); }); }