diff --git a/B06_Section/B06_Section_UI_Page.ts b/B06_Section/B06_Section_UI_Page.ts index fcc2ab0a..0b5b51c9 100644 --- a/B06_Section/B06_Section_UI_Page.ts +++ b/B06_Section/B06_Section_UI_Page.ts @@ -137,6 +137,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { // 하단 고정 dock 도 B05와 같은 구조: [구조물 목록][구분선][액션 버튼 행]. const structuresPanel = createB06StructuresPanel({ projectId, + detail: () => sectionDetail, stationInterval: () => stationInterval ?? 20, focusChainage: focusStationAt, queuePipeOptions: (chainageM, patch) => stationControls.queueCulvertOptions(chainageM, patch), @@ -182,6 +183,20 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { put(side, "revet_after_m", num(`${prefix}_revet_after_m`)); put(side, "structure", patch[`${prefix}_type`]); } + // 폼이 높이·형태를 정하면 그 벽의 **조정값**(revet_adjust.h·m)은 비운다 — + // 조정값이 남아 있으면 그림이 폼 값을 따라오지 않는다(2026-08-29 사용자 보고). + for (const [role, prefix] of [ + ["inlet", "inlet"], + ["outlet", "outlet"], + ] as const) { + const patchHeight = patch[`${prefix}_revet_height_m`] !== undefined; + const patchForm = patch[`${prefix}_revet_form`] !== undefined; + if (!patchHeight && !patchForm) continue; + stationControls.revetOffset.update(owner.chainage_m, role, { + ...(patchHeight ? { h: null } : {}), + ...(patchForm ? { m: null } : {}), + }); + } 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")); diff --git a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts index 5c686685..1d22679f 100644 --- a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts +++ b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts @@ -35,6 +35,9 @@ const PIPE_MOVE_NOTICE = export interface B06StructuresPanelDeps { projectId: string | null; + /** 횡단 캐시 — 폼 기본값을 조정창과 **같은 원천**(culvert 스펙)에서 읽는다 + * (2026-08-29 사용자: 예전 조정창이 보여주던 값이 기본값이다). */ + detail?: () => SectionDetailResponse | null; /** 측점 간격(m) — 측점번호+잔여거리 환산용. */ stationInterval: () => number; /** 목록·폼에서 고른 시설의 측점 카드를 선택·스크롤한다. */ @@ -82,6 +85,44 @@ function coversChainage(structure: StructureInstance, chainageM: number): boolea return Math.abs(structureAnchorM(structure) - chainageM) < PIPE_MATCH_M; } +/** + * 관 지점 옵션에 **횡단 스펙 값**을 채워 넣는다 — 조정창이 보여주던 값과 폼이 + * 어긋나지 않게 한다(2026-08-29 사용자). 옵션에 이미 값이 있으면 그대로 두고, + * 비어 있을 때만 스펙(서버 계산·저장분)에서 가져온다. + */ +function withSpecDefaults( + options: Record | undefined, + detail: SectionDetailResponse | null, + chainageM: number, +): Record | undefined { + const owner = detail?.cross_sections.find( + (section) => Math.abs(section.chainage_m - chainageM) < PIPE_MATCH_M, + ); + const culvert = owner?.culvert; + if (!culvert) return options; + const merged: Record = { ...(options ?? {}) }; + const put = (key: string, value: string | number | null | undefined): void => { + if (merged[key] === undefined && value !== null && value !== undefined) merged[key] = value; + }; + put("pipe_kind", culvert.pipe_kind); + put("pipe_diameter_mm", Math.round(culvert.diameter_m * 1000)); + for (const [side, prefix] of [ + [culvert.inlet, "inlet"], + [culvert.outlet, "outlet"], + ] as const) { + put(`${prefix}_type`, side.structure); + put(`${prefix}_revet_form`, side.revet_form); + put(`${prefix}_revet_height_m`, side.revet_height_m); + put(`${prefix}_revet_length_m`, side.revet_length_m); + put(`${prefix}_revet_before_m`, side.revet_before_m); + put(`${prefix}_revet_after_m`, side.revet_after_m); + } + put("inlet_basin_length_m", culvert.inlet.basin_length_m); + put("inlet_basin_before_m", culvert.inlet.basin_before_m); + put("inlet_basin_after_m", culvert.inlet.basin_after_m); + return merged; +} + export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06StructuresPanel { let structures: StructureInstance[] = []; let pipeFacilities: PipeFacilityItem[] = []; @@ -169,13 +210,15 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc // 저장하지 않고 나갔던 조작분이 있으면 그것으로 화면을 세운다(B05와 같은 규칙). structures = readPendingStructures(projectId) ?? stored.structures; section.setStructures(structures); + const detail = deps.detail?.() ?? null; pipeFacilities = pipeResponse.pipe_points.map((pipe) => ({ chainage_m: pipe.chainage_m, facility: pipe.facility ?? "pipe", start_m: pipe.start_m, end_m: pipe.end_m, source: pipe.source, - options: pipe.options, + // 폼 기본값 = 조정창이 쓰던 값(횡단 스펙). 저장된 옵션이 있으면 그쪽이 이긴다. + options: withSpecDefaults(pipe.options, detail, pipe.chainage_m), design_flow_m3s: null, })); section.setPipeFacilities(pipeFacilities); @@ -187,6 +230,16 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc } } + /** 폼에 올리기 직전에 그 관의 옵션을 최신 스펙으로 보강한다(빈 칸만 채운다). */ + function refreshSpecDefaults(chainageM: number): void { + const hit = pipeFacilities.find( + (entry) => Math.abs(entry.chainage_m - chainageM) < PIPE_MATCH_M, + ); + if (!hit) return; + hit.options = withSpecDefaults(hit.options, deps.detail?.() ?? null, hit.chainage_m); + section.setPipeFacilities(pipeFacilities); + } + return { root: section.root, listRoot: section.listRoot, @@ -198,6 +251,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc }, showPipeAt: (chainageM) => { currentChainageM = chainageM; + if (chainageM !== null) refreshSpecDefaults(chainageM); section.selectPipeByChainage(chainageM); }, showAtChainage: (chainageM) => { @@ -213,6 +267,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc ); if (pipe) { currentChainageM = pipe.chainage_m; + refreshSpecDefaults(pipe.chainage_m); section.selectPipeByChainage(pipe.chainage_m); return; }