feat(B06): 세월교 좌측 「구조물 배치」 폼을 조정창 로직에 연결한다
좌측 폼의 세월교 상세 항목(관종·관경·수량·월류 폭·날개벽 유입/유출)이 도면에 전혀 반영되지 않았다. `applyPipeOptionsToCache`가 `section.culvert`만 보고 세월교 측점(`section.ford`)에서는 즉시 return했기 때문이다. 프론트는 구조물 배치의 상세 UI를 쓰고 로직은 조정창 제어기 것을 쓴다 (2026-08-30 사용자 확정). - `applyFordFormOptions()` 신설 — 폼 옵션 키를 `FordControl.setPipe`/`setWing` 인자로 옮긴다. 캐시 반영·바닥판 연장 재계산·정본 예약·카드 갱신은 제어기에 이미 있어 새로 만들지 않았다. - `FordControl.setPipe` 확장 — `pipe_kind`(관종)·`ford_width_m`(월류 폭 = `span_m`) 추가. 두 값은 조정창에 칸이 없고 폼만 보낸다. - `withFordSpec()` — 폼 기본값을 `section.ford` 스펙에서 읽는다. 두 창이 같은 스펙을 보므로 어느 쪽에서 만졌든 값이 갈리지 않는다(수량(련) 빈 칸 해소). 월류 높이(`ford_height_m`)는 백엔드 `_ford_set`이 읽지 않는다 — 세월교는 구체 위 노면이라 파임이 없고, 물넘이포장 전용 값이다. 폼에는 남되 도면 반영처는 없다. 자체검증(공용 브라우저 5174, 측점 7+9.7 세월교): - tsc --noEmit 통과. - 폼 날개 길이(유입) 2→4 → 조정창 「날개 길이 4.0m」, 바닥판 7.63m→9.04m (= +2×cos45°). 수정 전에는 둘 다 그대로였다. - 폼 수량 3 → 조정창 「3련」, 폼 관경 Ø1200 → 조정창 select 1200. - 재선택 왕복: 월류폭 12·수량 3·관경 1200·날개길이 4 그대로 되읽힘(스펙 정본). - 진입 시 수량(련) 칸 = 1(수정 전 빈 칸), 관종 파형강관. - 조작값은 원래 값(10/1/1000/2)으로 되돌려 놓았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -277,8 +277,17 @@ export interface FordControl {
|
||||
adjustFor: (chainageM: number) => FordAdjust;
|
||||
update: (chainageM: number, role: FordWallRole, patch: Partial<FordWallAdjust>) => void;
|
||||
reset: (chainageM: number, role: FordWallRole) => void;
|
||||
/** 관경(mm)·수량(련) 저장 — B05 정본(`pipe_points`)으로 간다. */
|
||||
setPipe: (chainageM: number, patch: { pipe_diameter_mm?: number; pipe_count?: number }) => void;
|
||||
/** 관경(mm)·수량(련)·관종·월류 폭 저장 — B05 정본(`pipe_points`)으로 간다.
|
||||
* 관종·월류 폭은 조정창에 칸이 없고 좌측 「구조물 배치」 폼만 보낸다(2026-08-30 사용자). */
|
||||
setPipe: (
|
||||
chainageM: number,
|
||||
patch: {
|
||||
pipe_diameter_mm?: number;
|
||||
pipe_count?: number;
|
||||
pipe_kind?: string;
|
||||
ford_width_m?: number;
|
||||
},
|
||||
) => void;
|
||||
/** 날개벽 제원 저장(2026-08-25 사용자 — 조정창에서 직접 제어). 같은 정본으로 간다. */
|
||||
setWing: (
|
||||
chainageM: number,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import { flushPendingStructures } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { createStationControls } from "./B06_Section_UI_Page_Station_Controls";
|
||||
import { applyFordFormOptions } from "./B06_Section_UI_Page_Ford_Controls";
|
||||
import { buildCrossPatches } from "./B06_Section_UI_Page_Patches";
|
||||
import { maxToeFitHalfWidth } from "./B06_Section_UI_Cross_Fit";
|
||||
import { readAlignmentDraft } from "../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
@@ -194,8 +195,15 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
const owner = sectionDetail?.cross_sections.find(
|
||||
(section) => Math.abs(section.chainage_m - chainageM) < 0.51,
|
||||
);
|
||||
const culvert = owner?.culvert;
|
||||
if (!owner || !culvert) return;
|
||||
if (!owner) return;
|
||||
// 세월교는 스펙 자리(`section.ford`)가 배수관과 달라 조정창 제어기로 보낸다 —
|
||||
// 로직은 그쪽 것을 쓰고 프론트만 폼 UI다(2026-08-30 사용자 확정).
|
||||
if (owner.ford) {
|
||||
applyFordFormOptions(stationControls.ford, 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;
|
||||
|
||||
@@ -114,6 +114,9 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
// 캐시를 먼저 고쳐 즉시 반영한다 — 저장은 늦게 묶어서 간다.
|
||||
if (patch.pipe_diameter_mm) spec.diameter_m = patch.pipe_diameter_mm / 1000;
|
||||
if (patch.pipe_count) spec.pipe_count = patch.pipe_count;
|
||||
if (patch.pipe_kind) spec.pipe_kind = patch.pipe_kind;
|
||||
// 월류 폭 = 구체의 도로 진행 방향 길이(`span_m`) — 백엔드 `_ford_set`과 같은 자리.
|
||||
if (patch.ford_width_m) spec.span_m = patch.ford_width_m;
|
||||
}
|
||||
deps.queuePipeOptions(chainageM, patch);
|
||||
deps.refreshCard(chainageM);
|
||||
@@ -160,6 +163,51 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 좌측 「구조물 배치」 폼이 낸 세월교 옵션을 **조정창 제어기**로 흘려보낸다
|
||||
* (2026-08-30 사용자 확정: 프론트는 구조물 배치의 상세 UI, 로직은 조정창 것을 쓴다).
|
||||
*
|
||||
* 폼 옵션 키(`pipe_*`·`ford_width_m`·`wing_in*`·`wing_out*`)를 제어기 인자로 옮기기만
|
||||
* 한다 — 캐시 반영·바닥판 연장 재계산·정본 예약·카드 갱신은 제어기 안에 이미 있다.
|
||||
* 값이 온 항목만 보낸다(빈 patch는 부르지 않는다).
|
||||
*/
|
||||
export function applyFordFormOptions(
|
||||
control: FordControl,
|
||||
chainageM: number,
|
||||
patch: Record<string, number | string>,
|
||||
): void {
|
||||
const num = (key: string): number | undefined => {
|
||||
if (patch[key] === undefined) return undefined;
|
||||
const value = Number(patch[key]);
|
||||
return Number.isFinite(value) ? value : undefined;
|
||||
};
|
||||
const pipe: Parameters<FordControl["setPipe"]>[1] = {};
|
||||
const diameterMm = num("pipe_diameter_mm");
|
||||
if (diameterMm !== undefined) pipe.pipe_diameter_mm = diameterMm;
|
||||
const count = num("pipe_count");
|
||||
if (count !== undefined) pipe.pipe_count = count;
|
||||
if (typeof patch.pipe_kind === "string") pipe.pipe_kind = patch.pipe_kind;
|
||||
const widthM = num("ford_width_m");
|
||||
if (widthM !== undefined) pipe.ford_width_m = widthM;
|
||||
if (Object.keys(pipe).length) control.setPipe(chainageM, pipe);
|
||||
|
||||
for (const [role, prefix] of [
|
||||
["inlet", "wing_in"],
|
||||
["outlet", "wing_out"],
|
||||
] as const) {
|
||||
const wing: Parameters<FordControl["setWing"]>[2] = {};
|
||||
// 설치 값은 폼이 "있음"/"없음" 문자열로 낸다(백엔드 `_wing_spec`과 같은 규약).
|
||||
if (patch[prefix] !== undefined) wing.installed = patch[prefix] !== "없음";
|
||||
const heightM = num(`${prefix}_height_m`);
|
||||
if (heightM !== undefined) wing.height_m = heightM;
|
||||
const lengthM = num(`${prefix}_length_m`);
|
||||
if (lengthM !== undefined) wing.length_m = lengthM;
|
||||
const angleDeg = num(`${prefix}_angle_deg`);
|
||||
if (angleDeg !== undefined) wing.angle_deg = angleDeg;
|
||||
if (Object.keys(wing).length) control.setWing(chainageM, role, wing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BOX암거 구체 조작값 제어 — 세월교와 같은 흐름(세션 사본 + 캐시 `design.box_adjust`).
|
||||
* 좌·우 끝을 따로 잡으며 길이는 바깥으로만, 표고는 양방향으로 움직인다.
|
||||
|
||||
@@ -102,6 +102,32 @@ function coversChainage(structure: StructureInstance, chainageM: number): boolea
|
||||
return Math.abs(structureAnchorM(structure) - chainageM) < PIPE_MATCH_M;
|
||||
}
|
||||
|
||||
/**
|
||||
* 세월교 폼 옵션을 **지금 그려진 스펙**(`section.ford`)으로 덮는다 — 조정창 조작이
|
||||
* 이 스펙을 제자리에서 고치므로, 폼이 저장된 옵션을 붙들면 두 창이 갈린다
|
||||
* (2026-08-30 사용자: 수량(련) 칸이 비어 있고 조정창은 1련이었다).
|
||||
*/
|
||||
function withFordSpec(
|
||||
options: Record<string, string | number> | undefined,
|
||||
ford: NonNullable<SectionDetailResponse["cross_sections"][number]["ford"]>,
|
||||
): Record<string, string | number> {
|
||||
const merged: Record<string, string | number> = { ...(options ?? {}) };
|
||||
if (ford.pipe_kind) merged.pipe_kind = ford.pipe_kind;
|
||||
merged.pipe_diameter_mm = Math.round(ford.diameter_m * 1000);
|
||||
merged.pipe_count = ford.pipe_count;
|
||||
merged.ford_width_m = ford.span_m;
|
||||
for (const [wing, prefix] of [
|
||||
[ford.wing_in, "wing_in"],
|
||||
[ford.wing_out, "wing_out"],
|
||||
] as const) {
|
||||
merged[prefix] = wing.installed ? "있음" : "없음";
|
||||
if (wing.height_m !== null) merged[`${prefix}_height_m`] = wing.height_m;
|
||||
if (wing.length_m !== null) merged[`${prefix}_length_m`] = wing.length_m;
|
||||
if (wing.angle_deg !== null) merged[`${prefix}_angle_deg`] = wing.angle_deg;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 관 지점 옵션에 **횡단 스펙 값**을 채워 넣는다 — 조정창이 보여주던 값과 폼이
|
||||
* 어긋나지 않게 한다(2026-08-29 사용자). 옵션에 이미 값이 있으면 그대로 두고,
|
||||
@@ -117,6 +143,10 @@ function withSpecDefaults(
|
||||
const owner = detail?.cross_sections.find(
|
||||
(section) => Math.abs(section.chainage_m - chainageM) < PIPE_MATCH_M,
|
||||
);
|
||||
// 세월교는 폼과 조정창이 **같은 캐시**(`section.ford`)를 본다 — 어느 쪽에서 만졌든
|
||||
// 그 스펙이 지금 그려진 값이라 저장된 옵션보다 앞선다(2026-08-30 사용자: 프론트는
|
||||
// 구조물 배치 상세 UI, 로직·값은 조정창 것).
|
||||
if (owner?.ford) return withFordSpec(options, owner.ford);
|
||||
const culvert = owner?.culvert;
|
||||
if (!culvert) return options;
|
||||
const merged: Record<string, string | number> = { ...(options ?? {}) };
|
||||
|
||||
Reference in New Issue
Block a user