Files
Aislo/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts
T
eomsangdonandClaude Opus 5.5 7f04bf961c feat(B06): 관 · 옹벽 칸 한 곳 — 조정 창은 손잡이 · 읽기만(PLAN 24-4a)
- 옹벽 · 세월교 · BOX 조정 창의 값 칸(셀렉트 · −/+) 걷음 — 읽기 · 십자 · 그림 끌기 손잡이만(input 0)
- 그림 끌기 손잡이: 고른 구조물을 끌면 십자와 같은 걸음 · 같은 동작 · 끄는 동안 미리 보기
- 좌측 [횡단 조정] 사본 상자 없앰 — 폼에 없던 줄(읽기 · 단 수 · 옵션 · 집수정 내공 · 세월교 측벽 높이 · 다단 벽)만 폼 칸에 옮겨 붙음
- 집수정 기준측점 전 · 후를 폼 칸으로(옵션 보이기 따름) · 옮겨 붙은 줄도 옵션 보이기 규칙
- 폼 값 = 칸 전부: 비운 칸은 그림 자동 · 가게(pipes)에서도 지움 · 조정 예약(culvertopt)에 따로 담지 않음
- 시험 test_b06_adjust_single_form 6건 · 거울 시험(집수정 전 · 후 폼 칸) 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
2026-09-29 23:54:12 +09:00

104 lines
4.6 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Drainage_Facility_Merge.ts
* 계곡 통과 시설 [저장] — **폼이 아는 칸만 갈아 끼우고, 폼이 모르는 칸은 건드리지 않음**
* (2026-09-14 브레인 판정).
*
* ⚠ 앞서 저장이 옵션을 **통째로** 갈아 끼웠음 — 구조물 집계표·구조물도로 적은 칸(관 기슭막이
* 기초 `revet_foundation` · 독립 기슭막이 `foundation`·뒷길이·돌 종류 …)이 B05 에서 그 시설을
* 한 번 더 저장하면 **조용히 지워졌음**. 「작업본 쓰기는 [저장]에서만」인데 그 [저장]이 옆 칸까지
* 지우던 자리.
* ⚠ 통째로 갈아 끼우는 것은 **시설 종류 자체를 바꿀 때만** — 그때는 옛 칸이 뜻을 잃음.
* ⚠ 폼이 아는 칸을 폼이 비워 보내면(값을 지움·유입구를 집수정 → 기슭막이로 바꿔 집수정 칸이 빠짐)
* 그 칸은 지워짐 — 사용자가 폼에서 한 일이므로 맞음.
* ⚠ 폼에 칸을 늘리면 아래 목록에도 넣을 것. 빠뜨리면 그 칸을 **폼에서 지워도 옛 값이 남는**
* 쪽으로 틀림(지워지는 쪽보다 덜 위험). 거울 시험 `test_b05_facility_options_merge`.
* ⚠ import 없음 — 시험이 이 파일만 옮겨 돌림.
* ========================================================================== */
const revetKeys = (side: "inlet" | "outlet"): string[] =>
["form", "length_m", "height_m", "before_m", "after_m"].map((key) => `${side}_revet_${key}`);
const wingKeys = (side: "in" | "out"): string[] => [
`wing_${side}`,
`wing_${side}_height_m`,
`wing_${side}_length_m`,
`wing_${side}_angle_deg`,
];
/** 시설 종류별로 **폼이 적는 칸** — `createFacilityOptionsForm().readOptions()` 가 쓰는 키 전부. */
export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
pipe: [
"pipe_diameter_mm",
"pipe_kind",
"wing_wall_type",
"inlet_type",
"inlet_structure",
"inlet_basin_form",
"inlet_basin_material",
"inlet_basin_length_m",
"inlet_basin_before_m",
"inlet_basin_after_m",
...revetKeys("inlet"),
"outlet_type",
...revetKeys("outlet"),
],
box_culvert: ["body_width_m", "body_height_m", ...wingKeys("in"), ...wingKeys("out")],
ford_pavement: ["ford_width_m", "ford_height_m", "ford_slope_pct", "thickness_cm", "length_m"],
ford_bridge: [
"pipe_kind",
"pipe_diameter_mm",
"pipe_count",
"ford_width_m",
"ford_height_m",
...wingKeys("in"),
...wingKeys("out"),
],
revetment: ["side", ...revetKeys("inlet"), ...revetKeys("outlet")],
};
/** 독립 기슭막이 레지스트리 키 → 폼은 유입·유출 두 채널 키로 적는다. */
const REVETMENT_FIELDS = ["form", "length_m", "height_m", "before_m", "after_m"];
/** 「구조물 옵션 보이기」에서 끈 레지스트리 키 → 시설 폼이 가릴 칸 키(21-10 흠 8).
* · 독립 기슭막이는 채널 키(`inlet_revet_*` · `outlet_revet_*`)로 넓힘
* · 유입구 「구조」한 칸이 `inlet_type` 과 `inlet_structure` 를 함께 적음 */
export function hiddenFormKeys(
facility: string | null,
registryHidden: Iterable<string>,
): Set<string> {
const hidden = new Set<string>(registryHidden);
if (facility === "revetment") {
for (const field of REVETMENT_FIELDS) {
if (!hidden.has(field)) continue;
hidden.add(`inlet_revet_${field}`);
hidden.add(`outlet_revet_${field}`);
}
}
if (hidden.has("inlet_type")) hidden.add("inlet_structure");
return hidden;
}
interface MergeableAttributes {
facility: string;
start_m?: number;
end_m?: number;
options?: Record<string, string | number>;
/** 집계 항목(options 밖) — 다음 값에 칸이 없으면(undefined) 옛 지정을 그대로 잇는다. */
summary_item?: unknown;
}
/** 저장할 시설 정보 — 같은 종류면 폼이 모르는 옛 칸을 이어 붙임. */
export function mergeFacilityOptions<T extends MergeableAttributes>(
previous: T | null,
next: T,
): T {
if (!previous || previous.facility !== next.facility) return next;
const managed = new Set(FORM_OPTION_KEYS[next.facility] ?? []);
const kept = Object.entries(previous.options ?? {}).filter(([key]) => !managed.has(key));
const options = { ...Object.fromEntries(kept), ...(next.options ?? {}) };
const summary =
next.summary_item === undefined && previous.summary_item !== undefined
? { summary_item: previous.summary_item }
: {};
return { ...next, ...summary, options: Object.keys(options).length ? options : undefined };
}