- `B06_Section_UI_Page_Persist.ts`(231줄) 신설 — 암 경계선 세션 저장소 (`createRockBoundaryStore`), 편집분 수집, [저장]·[확정]. - `B06_Section_UI_Page_Pipe_Options.ts`(130줄) 신설 — 폼에서 바꾼 관 옵션을 횡단 캐시·조정창 제어기에 얹는 경로. - 페이지는 상태 창구(`SectionPersistContext`·`PipeOptionsContext`)만 넘김. - 화면 검증: B06 진입 카드 280장·암 버튼 69개·콘솔 오류 0. 암 경계선 ▲ 조작으로 표시 -0.5m → -0.4m + 세션키 기록, ↺ 로 -0.5m 복귀. [저장] 클릭 시 `POST /sections/111/save` 200. - `tsc --noEmit` 0, prettier 적용, pytest 359 passed.
131 lines
6.1 KiB
TypeScript
131 lines
6.1 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Page_Pipe_Options.ts
|
|
* 「구조물 배치」 폼에서 바꾼 **관 옵션을 횡단 캐시에 얹는** 경로.
|
|
*
|
|
* `B06_Section_UI_Page` 에서 떼어낸 몫이다(700줄 제한, 2026-09-02). 여기서는 화면
|
|
* 캐시(`section.culvert`·조정창 제어기)만 고친다 — 영구저장은 [저장]·[확정] 몫이다.
|
|
* ========================================================================== */
|
|
|
|
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<typeof createStationControls>;
|
|
|
|
/** 폼 → 캐시 반영에 필요한 페이지 창구만 모은 것. */
|
|
export interface PipeOptionsContext {
|
|
detail: () => SectionDetailResponse | null;
|
|
ford: StationControls["ford"];
|
|
box: StationControls["box"];
|
|
revetOffset: StationControls["revetOffset"];
|
|
/** 기하가 잘라 낸 실제 값을 폼에 되돌린다. */
|
|
overrideOptions: (chainageM: number, values: Record<string, number | string>) => void;
|
|
refreshCard: (chainageM: number) => void;
|
|
}
|
|
|
|
/**
|
|
* 폼에서 바꾼 관 옵션을 **횡단 캐시**(`section.culvert` 스펙)에 얹고, 그 구조물이
|
|
* 덮는 측점 카드를 다시 그린다 — 조정창 조작과 같은 흐름이라 도면이 바로 따라온다
|
|
* (2026-08-29 사용자 보고: 값만 바뀌고 횡단도가 그대로였다).
|
|
* 여기서는 화면 캐시만 고친다 — 영구저장은 [저장]·[확정] 몫이다.
|
|
*/
|
|
export function applyPipeOptionsToCache(
|
|
ctx: PipeOptionsContext,
|
|
chainageM: number,
|
|
patch: Record<string, number | string>,
|
|
): 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 = <T extends object>(target: T, field: keyof T, value: unknown): void => {
|
|
if (value !== undefined) (target as Record<string, unknown>)[field as string] = value;
|
|
};
|
|
put(culvert, "pipe_kind", patch.pipe_kind);
|
|
const diameterMm = num("pipe_diameter_mm");
|
|
if (diameterMm !== undefined) culvert.diameter_m = diameterMm / 1000;
|
|
// 유입·유출 기슭막이 제원과 집수정 구간값 — 폼 옵션 키를 스펙 자리로 옮긴다.
|
|
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);
|
|
}
|
|
}
|
|
}
|