/* ============================================================================= * B06_Section_Cross_Refresh.ts * 「현재 계획선에 맞춘 횡단 재계산」 **단일 창구** — B05·B06이 같은 입력으로 같은 결과를 본다. * * ── 왜 필요한가 (2026-09-03 사용자 지시) ───────────────────────────── * 유토곡선은 두 화면이 같은 데이터를 보여 주는 기능인데, 재계산 호출이 두 벌이라 값이 * 갈렸다. 실측 — 같은 프로젝트·같은 시점에 B06 `절토(자연) 3,704.6㎥` ↔ B05 `4,526.8㎥`. * 원인은 인자였다: * · B06: `previewCrossDesigns(..., standardPanel.getValues(), { fullDesigns, rockBoundaryOffsets })` * · B05: `previewCrossDesigns(..., undefined, { fullDesigns })` * 표준 단면값과 암 경계 오프셋이 빠지면 서버가 다른 설계를 그려 단면적이 달라진다. * 보존하는 사용자 부속값 목록도 서로 달라(B05는 2개, B06은 7개) B05를 거치면 기슭막이 * 조정 같은 값이 사라졌다. * * 그래서 **입력 수집·서버 호출·제자리 반영**을 여기 한 곳으로 모은다. 세션 편집값은 * 패널이 아니라 세션 저장소에서 직접 읽으므로, 패널이 없는 B05도 B06과 같은 값을 보낸다. * ========================================================================== */ import { previewCrossDesigns } from "./B06_Section_Api_Fetch"; import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch"; import { readRockBoundarySession } from "./B06_Section_UI_Page_Persist"; import { readStandardCrossSession } from "./B06_Section_UI_Standard_Panel"; /** 계획선 편집 델타 — B05 `AlignmentEdits`와 저장분 `profile_alignment.edits`가 같은 모양이다. */ export interface CrossRefreshEdits { station_offsets: Record; curve_radii: Record; } export interface CrossRefreshInput { projectId: string; routeId: number; /** 제자리 갱신 대상 — 공유 캐시가 들고 있는 그 객체여야 두 화면이 같이 따라온다. */ detail: SectionDetailResponse; edits: CrossRefreshEdits; /** * 응답이 도착한 시점에 **아직 이 결과를 써도 되는지** 묻는다(false면 반영하지 않는다). * 끌기 중에는 요청이 겹치므로, 늦게 온 옛 응답이 새 설계를 덮지 않게 하는 문지기다. */ shouldApply?: () => boolean; } /** 서버 설계로 갈아 끼워도 **살려 두는 사용자 부속값** — 화면 조작으로만 생기는 값이다. */ function preserveUserFields( next: NonNullable, previous: CrossSection["design"], ): NonNullable { if (!previous) return next; return { ...next, inlet_structure: previous.inlet_structure, basin_adjust: previous.basin_adjust, revet_adjust: previous.revet_adjust, extra_wall_counts: previous.extra_wall_counts, extra_spans: previous.extra_spans, revet_link_detached: previous.revet_link_detached, revet_follow_grade: previous.revet_follow_grade, }; } /** * 전 측점 횡단을 현재 계획선으로 다시 계산해 `detail.cross_sections[].design`을 제자리 교체한다. * 돌려주는 값은 실제로 바뀐 측점의 누가거리 목록 — 호출한 쪽이 그 카드만 다시 그리면 된다. */ export async function refreshCrossDesigns(input: CrossRefreshInput): Promise { const { projectId, routeId, detail, edits, shouldApply } = input; // full_designs — 설계선 좌표까지 받아야 3D 코리도·횡단도가 편집 즉시 같은 형상이 된다. const response = await previewCrossDesigns( projectId, routeId, edits, readStandardCrossSession(projectId) ?? undefined, { fullDesigns: true, rockBoundaryOffsets: readRockBoundarySession(projectId, routeId), }, ); if (shouldApply && !shouldApply()) return []; const designByChainage = new Map( response.designs.map((entry) => [entry.chainage_m.toFixed(3), entry.design]), ); const updated: number[] = []; for (const section of detail.cross_sections) { const next = designByChainage.get(section.chainage_m.toFixed(3)); if (!next) continue; section.design = preserveUserFields( next as NonNullable, section.design, ); updated.push(section.chainage_m); } return updated; }