fix(B06): 폼의 높이·길이·전/후가 횡단도를 따라오게 하고 기본값을 조정창과 맞춘다

2026-08-29 사용자 지적 2건:
① 폼에서 높이·길이·기준측점 전/후를 고쳐도 도면이 그대로였다 — 조정창 조작값
   (revet_adjust.h·m)이 폼 값보다 우선이라 그림이 옛 값을 유지했다. 폼이 높이·
   형태를 정하면 그 벽의 조정값을 비워 폼 값이 이기게 했다.
② 폼 기본값은 예전 조정창이 보여주던 값이 기준이다 — 관 옵션이 비어 있으면
   횡단 스펙(culvert 스펙: 형태·높이·길이·전/후·집수정 구간값·관종·관경)에서
   채워 폼과 조정창이 같은 원천을 보게 했다. 저장된 옵션이 있으면 그쪽이 이긴다.
This commit is contained in:
2026-08-29 16:56:37 +09:00
parent 6027e76eaf
commit 510731dd17
2 changed files with 71 additions and 1 deletions
+15
View File
@@ -137,6 +137,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
// 하단 고정 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<void> {
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"));
@@ -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<string, string | number> | undefined,
detail: SectionDetailResponse | null,
chainageM: number,
): Record<string, string | number> | 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<string, string | number> = { ...(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;
}