/* ============================================================================= * B06_Section_UI_Page_Pipe_Options.ts * 「구조물 배치」 폼에서 바꾼 **관 옵션을 횡단 캐시에 얹는** 경로. * * `B06_Section_UI_Page` 에서 떼어낸 몫이다(700줄 제한, 2026-09-02). 여기서는 화면 * 캐시(`section.culvert`·조정창 제어기)만 고친다 — 영구저장은 [저장]·[확정] 몫이다. * ========================================================================== */ import { pipeDiameterM } from "@util/common_util_culvert_sets"; import { showToast } from "@ui/ui_template_elements"; import type { SectionDetailResponse } from "./B06_Section_Api_Fetch"; import { applyBoxFormOptions, applyFordFormOptions } from "./B06_Section_UI_Page_Ford_Controls"; import { revetWallSpec } from "./B06_Section_UI_Cross_Culvert_Const"; import type { createStationControls } from "./B06_Section_UI_Page_Station_Controls"; type StationControls = ReturnType; /** 폼 → 캐시 반영에 필요한 페이지 창구만 모은 것. */ export interface PipeOptionsContext { detail: () => SectionDetailResponse | null; ford: StationControls["ford"]; box: StationControls["box"]; revetOffset: StationControls["revetOffset"]; /** 기하가 잘라 낸 실제 값을 폼에 되돌린다. */ overrideOptions: (chainageM: number, values: Record) => void; refreshCard: (chainageM: number) => void; } /** * 폼에서 바꾼 관 옵션을 **횡단 캐시**(`section.culvert` 스펙)에 얹고, 그 구조물이 * 덮는 측점 카드를 다시 그린다 — 조정창 조작과 같은 흐름이라 도면이 바로 따라온다 * (2026-08-29 사용자 보고: 값만 바뀌고 횡단도가 그대로였다). * 여기서는 화면 캐시만 고친다 — 영구저장은 [저장]·[확정] 몫이다. */ export function applyPipeOptionsToCache( ctx: PipeOptionsContext, chainageM: number, patch: Record, ): void { const owner = ctx .detail() ?.cross_sections.find((section) => Math.abs(section.chainage_m - chainageM) < 0.51); if (!owner) return; // 세월교는 스펙 자리(`section.ford`)가 배수관과 달라 조정창 제어기로 보낸다 — // 로직은 그쪽 것을 쓰고 프론트만 폼 UI다(2026-08-30 사용자 확정). if (owner.ford) { applyFordFormOptions(ctx.ford, owner.chainage_m, patch); return; } // BOX암거도 스펙 자리가 따로다(`section.box`) — 같은 규칙으로 제어기에 보낸다 // (2026-08-30 사용자: 세월교와 같은 문제). if (owner.box) { applyBoxFormOptions(ctx.box, owner.chainage_m, patch); return; } const culvert = owner.culvert; if (!culvert) return; const num = (key: string): number | undefined => { const value = Number(patch[key]); return Number.isFinite(value) ? value : undefined; }; const put = (target: T, field: keyof T, value: unknown): void => { if (value !== undefined) (target as Record)[field as string] = value; }; put(culvert, "pipe_kind", patch.pipe_kind); // 기슭막이 기초(기초유/기초버림) — **터파기 파선을 그리는 값**이다(`foundationChoice`). // 옮겨 적지 않아 폼에서 고쳐도 도면이 다음 조회까지 안 따라왔다(2026-09-09 실측). // ⚠ 폼 키는 `revet_foundation`, 스펙 자리는 `foundation` 이라 이름이 다르다. put(culvert, "foundation", patch.revet_foundation); const diameterMm = num("pipe_diameter_mm"); if (diameterMm !== undefined) culvert.diameter_m = pipeDiameterM(diameterMm); // 유입·유출 기슭막이 제원과 집수정 구간값 — 폼 옵션 키를 스펙 자리로 옮긴다. for (const [side, prefix] of [ [culvert.inlet, "inlet"], [culvert.outlet, "outlet"], ] as const) { put(side, "revet_form", patch[`${prefix}_revet_form`]); put(side, "revet_height_m", num(`${prefix}_revet_height_m`)); put(side, "revet_length_m", num(`${prefix}_revet_length_m`)); put(side, "revet_before_m", num(`${prefix}_revet_before_m`)); put(side, "revet_after_m", num(`${prefix}_revet_after_m`)); put(side, "structure", patch[`${prefix}_type`]); } // 배관 벽의 **높이·형태는 스펙이 아니라 조정값**(revet_adjust.h·m)이 정한다 // — `revetWallSpec`이 배관에서는 spec.revet_height_m을 쓰지 않기 때문이다. // 폼에서 고친 값을 조정창과 같은 채널로 보내야 도면이 따라온다(2026-08-29 사용자). for (const [role, prefix] of [ ["inlet", "inlet"], ["outlet", "outlet"], ] as const) { const height = num(`${prefix}_revet_height_m`); const form = patch[`${prefix}_revet_form`]; if (height === undefined && typeof form !== "string") continue; ctx.revetOffset.update(owner.chainage_m, role, { ...(height !== undefined ? { h: height } : {}), ...(typeof form === "string" ? { m: form } : {}), }); // 형태마다 높이 한계가 있다(돌쌓기(메) 2.0m 등). 기하가 잘라 낸 실제 높이를 // 폼에 되돌린다 — 숫자만 커지고 그림은 그대로인 상태를 남기지 않는다. if (height === undefined) continue; const applied = revetWallSpec( role === "outlet" ? culvert.outlet : culvert.inlet, ctx.revetOffset.adjustFor(owner, role), culvert.hidden_pipe === true, culvert.diameter_m, ).pureHeight; if (Math.abs(applied - height) > 0.05) { ctx.overrideOptions(owner.chainage_m, { [`${prefix}_revet_height_m`]: Number(applied.toFixed(1)), }); showToast( `${prefix === "outlet" ? "유출" : "유입"} 기슭막이 높이는 형태 한계로 ` + `${applied.toFixed(1)}m까지만 적용됩니다.`, "error", ); } } put(culvert.inlet, "basin_length_m", num("inlet_basin_length_m")); put(culvert.inlet, "basin_before_m", num("inlet_basin_before_m")); put(culvert.inlet, "basin_after_m", num("inlet_basin_after_m")); // 연장이 바뀌면 옆 측점 링크도 달라진다 — 그 구조물이 덮는 범위만 다시 그린다. const reach = Math.max( culvert.inlet.revet_before_m ?? 0, culvert.inlet.revet_after_m ?? 0, culvert.outlet.revet_before_m ?? 0, culvert.outlet.revet_after_m ?? 0, num("inlet_revet_length_m") ?? 0, num("outlet_revet_length_m") ?? 0, ); for (const other of ctx.detail()?.cross_sections ?? []) { if (Math.abs(other.chainage_m - owner.chainage_m) <= reach + 1e-9) { ctx.refreshCard(other.chainage_m); } } }