/* ============================================================================= * B05_Profile_UI_Profile_Preview.ts * 계획선 편집 → 횡단 설계 프리뷰 반영 (패널 본체에서 분리, 2026-09-02 · 700줄 한계). * * 계획고가 바뀌면 측점별 횡단 단면적도 바뀐다 — 서버에 한 번 물어 전 측점을 다시 계산해 * **공유 캐시가 들고 있는 같은 객체**를 제자리 갱신한다. 그래야 횡단 기준 유토곡선과 * 3D 예상형상이 같은 값으로 따라온다(2026-08-03·08-23 사용자 보고). * * 끌기 중에는 계속 호출되므로 디바운스하고, 늦게 온 응답은 seq 비교로 버린다. * ========================================================================== */ import type { AlignmentEdits } from "./B05_Profile_UI_Profile_Alignment"; import { previewCrossDesigns } from "../B06_Section/B06_Section_Api_Fetch"; import type { SectionDetailResponse } from "../B06_Section/B06_Section_Api_Fetch"; export interface CrossPreviewContext { projectId: string; detail: () => SectionDetailResponse | null; routeId: () => number | null; edits: () => AlignmentEdits; debounceMs: number; /** 반영이 끝난 뒤 다시 그린다. */ onApplied: () => void; } export interface CrossPreview { /** 편집이 있을 때마다 부른다 — 마지막 값만 서버로 나간다. */ schedule: () => void; /** 패널을 걷을 때 대기 중인 요청 타이머를 끈다. */ dispose: () => void; } export function createCrossPreview(ctx: CrossPreviewContext): CrossPreview { let timer = 0; let seq = 0; return { schedule() { if (!ctx.detail() || ctx.routeId() === null) return; window.clearTimeout(timer); timer = window.setTimeout(() => { const detail = ctx.detail(); const routeId = ctx.routeId(); if (!detail || routeId === null) return; const current = (seq += 1); // full_designs — 설계선 좌표까지 통째로 받아야 3D 코리도가 편집 즉시 정확한 // 형상으로 재빌드된다(2026-08-23 사용자 지시). 암 경계는 백엔드가 세션값이 없으면 // DB 저장 echo를 폴백으로 쓰므로 그대로 유지된다. void previewCrossDesigns(ctx.projectId, routeId, ctx.edits(), undefined, { fullDesigns: true, }) .then((next) => { const live = ctx.detail(); if (current !== seq || !live || ctx.routeId() !== routeId) return; const designByChainage = new Map( next.designs.map((entry) => [entry.chainage_m.toFixed(3), entry.design]), ); for (const section of live.cross_sections) { const full = designByChainage.get(section.chainage_m.toFixed(3)); if (!full || !section.design) continue; // 전체 교체(설계선 포함) — B06 reconcile과 같은 패턴으로 사용자 부속값은 보존. section.design = { ...(full as NonNullable), inlet_structure: section.design.inlet_structure, basin_adjust: section.design.basin_adjust, }; } ctx.onApplied(); }) .catch(() => { /* 프리뷰 실패는 무시 — 화면의 계획선은 그대로 두고 다음 편집에서 다시 시도한다. */ }); }, ctx.debounceMs); }, dispose() { window.clearTimeout(timer); }, }; }