feat(B05·B06): 「구조물 옵션 보이기」에서 끈 옵션을 배수관 · BOX암거 · 세월교 · 기슭막이 하위 폼에서도 가림(21-10 흠 8)
- 시설 폼 칸에 옵션 키 표식(data-option-keys) · 끈 키의 칸은 가림 · 칸이 다 가려진 줄은 줄째 - 키가 여럿인 칸(본체 규격 · 유입구 구조)은 키가 다 꺼졌을 때만 가림 · 독립 기슭막이는 유입 · 유출 채널 키로 넓힘 - 숨긴 칸은 칸 값(제안값 채우기 포함)이 아니라 폼이 받은 옛 값을 저장에 그대로 실음 · 이 폼에는 필수 검사가 없음 - 가리기 코드는 새 파일(_Facility_Hidden)로 분리해 Facility.ts 700줄 아래 유지 - 시험 3개 추가 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TULoa94ZFL26KU6ZqVpjkF
This commit is contained in:
@@ -60,6 +60,7 @@ import {
|
||||
import { pipeWallMinHeightM } from "../common_util/common_util_culvert_sets";
|
||||
import type { SummaryItem } from "../common_util/common_util_summary_item";
|
||||
import { createFordFields } from "./B05_Profile_UI_Drainage_Facility_Ford";
|
||||
import { createHiddenOptions } from "./B05_Profile_UI_Drainage_Facility_Hidden";
|
||||
|
||||
export type { InletStructureKind }; // 목록은 700줄 한계로 `_Fields` 로 옮김 — 부르던 곳은 그대로
|
||||
|
||||
@@ -204,8 +205,11 @@ export interface FacilityOptionsForm {
|
||||
/** 담당 유역의 설계유량(㎥/s) — 물넘이·세월교 개략 단면의 입력. 없으면 안내만. */
|
||||
designFlow?: number | null,
|
||||
) => void;
|
||||
/** 현재 필드 값을 시설 옵션으로 읽는다(빈 값 생략). */
|
||||
/** 현재 필드 값을 시설 옵션으로 읽는다(빈 값 생략). 숨긴 칸은 폼이 받은 옛 값 그대로. */
|
||||
readOptions: () => Record<string, string | number>;
|
||||
/** 「구조물 옵션 보이기」에서 끈 레지스트리 옵션 키 — 그 칸을 가린다(값은 옛 값 보존).
|
||||
* `setFacility` 뒤에 부른다. */
|
||||
setHiddenOptions: (facility: PipeFacility | null, registryKeys: Iterable<string>) => void;
|
||||
/** 설계유량만 갱신한다 — 필드 값은 건드리지 않는다. 임시 배치(2026-08-18)의
|
||||
* 유역 재계산 결과를 입력 중인 폼에 살짝 반영하는 용도. */
|
||||
setDesignFlow: (designFlow: number | null) => void;
|
||||
@@ -249,8 +253,11 @@ export function createFacilityOptionsForm(
|
||||
// 관보호공 날개벽 — **개소당 원단위**가 형식마다 붙박이라 고르는 칸이 있어야 물량이 선다
|
||||
// (2026-09-09, 계획서 4-13 · 소광리 원본 탭 다섯). 비우면 날개벽을 안 센다.
|
||||
const wingWall = optionalSelect("안 놓음", ["A-TYPE", "C-TYPE", "A-TYPE+집수정"]);
|
||||
const pipeRow = grid(labeled("관종", pipeMaterial), labeled("관경", pipeDiameter));
|
||||
const wingRow = grid(labeled("관보호공 날개벽", wingWall));
|
||||
const pipeRow = grid(
|
||||
labeled("관종", pipeMaterial, "pipe_kind"),
|
||||
labeled("관경", pipeDiameter, "pipe_diameter_mm"),
|
||||
);
|
||||
const wingRow = grid(labeled("관보호공 날개벽", wingWall, "wing_wall_type"));
|
||||
|
||||
// ── 유입구 — 집수정 또는 기슭막이 택일(사용자 지시 5~9) ─────────────────
|
||||
const inletGroup = group("유입구");
|
||||
@@ -273,11 +280,14 @@ export function createFacilityOptionsForm(
|
||||
"돌쌓기(찰)",
|
||||
"돌쌓기(메)",
|
||||
]);
|
||||
const basinMaterialField = labeled("형태", basinMaterial);
|
||||
const basinMaterialField = labeled("형태", basinMaterial, "inlet_basin_material");
|
||||
// 집수정 종방향 길이 — 3D 예상형상용, 전후 동일 배분·기본 2m(2026-08-23 사용자).
|
||||
const basinLength = numberInput("0.1", "0", "제안 2");
|
||||
const basinRows = [
|
||||
grid(labeled("형식", basinForm), labeled("길이 (m)", stepper(basinLength, 1))),
|
||||
grid(
|
||||
labeled("형식", basinForm, "inlet_basin_form"),
|
||||
labeled("길이 (m)", stepper(basinLength, 1), "inlet_basin_length_m"),
|
||||
),
|
||||
];
|
||||
const height = (): string =>
|
||||
String(pipeWallMinHeightM(Number(pipeDiameter.value || "1000") / 1000));
|
||||
@@ -294,7 +304,11 @@ export function createFacilityOptionsForm(
|
||||
inletSlot.className = "b05-structure__adjust-slot";
|
||||
const outletSlot = inletSlot.cloneNode() as HTMLDivElement;
|
||||
inletGroup.body.append(
|
||||
grid(labeled("구조", inletType), basinMaterialField, inletRevet.formField),
|
||||
grid(
|
||||
labeled("구조", inletType, ["inlet_type", "inlet_structure"]),
|
||||
basinMaterialField,
|
||||
inletRevet.formField,
|
||||
),
|
||||
...basinRows,
|
||||
...inletRevet.rows,
|
||||
inletSlot,
|
||||
@@ -309,7 +323,7 @@ export function createFacilityOptionsForm(
|
||||
height,
|
||||
});
|
||||
outletGroup.body.append(
|
||||
grid(labeled("구조", outletType), outletRevet.formField),
|
||||
grid(labeled("구조", outletType, "outlet_type"), outletRevet.formField),
|
||||
...outletRevet.rows,
|
||||
outletSlot,
|
||||
);
|
||||
@@ -329,7 +343,7 @@ export function createFacilityOptionsForm(
|
||||
// 좌·우가 저장 채널(유입/유출) 중 어느 쪽인지는 측점 지형이 정하므로 이름표만
|
||||
// 바꿔 단다(`setRevetSideLabels`) — 값은 언제나 그 채널 키로 오간다.
|
||||
const revetSide = optionalSelect("— 안 정함 (제안 양쪽) —", ["양쪽", "좌", "우"]);
|
||||
const revetTopRow = grid(labeled("설치 측", revetSide));
|
||||
const revetTopRow = grid(labeled("설치 측", revetSide, "side"));
|
||||
const revetInlet = createRevetSideGroup(INLET_REVET_KEYS, "돌쌓기(메)");
|
||||
const revetOutlet = createRevetSideGroup(OUTLET_REVET_KEYS, "돌쌓기(메)");
|
||||
/** 좌·우 이름표 — B06이 그 측점의 벽 방향을 알려 주면 바뀐다. 기본은 유입=좌. */
|
||||
@@ -372,12 +386,20 @@ export function createFacilityOptionsForm(
|
||||
]);
|
||||
const boxWidth = numberInput("0.1", "0", `제안 ${BOX_SIZE_PRESETS[0][1]}`);
|
||||
const boxHeight = numberInput("0.1", "0", `제안 ${BOX_SIZE_PRESETS[0][2]}`);
|
||||
const boxCustomRow = grid(labeled("본체 폭 (m)", boxWidth), labeled("본체 높이 (m)", boxHeight));
|
||||
const boxCustomRow = grid(
|
||||
labeled("본체 폭 (m)", boxWidth, "body_width_m"),
|
||||
labeled("본체 높이 (m)", boxHeight, "body_height_m"),
|
||||
);
|
||||
const boxWrap = document.createElement("div");
|
||||
boxWrap.append(grid(labeled("본체 규격 (m)", boxSize)), boxCustomRow);
|
||||
boxWrap.append(
|
||||
grid(labeled("본체 규격 (m)", boxSize, ["body_width_m", "body_height_m"])),
|
||||
boxCustomRow,
|
||||
);
|
||||
const wingInFields = createWingFields("날개벽(유입)", WING_IN_KEYS);
|
||||
const wingOutFields = createWingFields("날개벽(유출)", WING_OUT_KEYS);
|
||||
|
||||
const hiddenOptions = createHiddenOptions(root);
|
||||
|
||||
/** 고른 프리셋을 폭·높이 칸에 실어 둔다 — 저장도, "사용자 지정"으로 바꿨을 때의
|
||||
* 출발값도 이 두 칸이 정본이다. 사용자 지정 상태에서는 손대지 않는다. */
|
||||
function syncBoxSize(): void {
|
||||
@@ -551,8 +573,12 @@ export function createFacilityOptionsForm(
|
||||
onInletStructureChange(handler) {
|
||||
inletStructureHandler = handler;
|
||||
},
|
||||
setHiddenOptions(facility, registryKeys) {
|
||||
hiddenOptions.set(facility, registryKeys);
|
||||
},
|
||||
setFacility(facility, options = {}, designFlow = null) {
|
||||
current = facility;
|
||||
hiddenOptions.remember(options);
|
||||
ford.setDesignFlow(designFlow ?? null);
|
||||
if (facility === null) {
|
||||
syncVisibility();
|
||||
@@ -658,6 +684,7 @@ export function createFacilityOptionsForm(
|
||||
revetInlet.fields.read(options);
|
||||
revetOutlet.fields.read(options);
|
||||
}
|
||||
hiddenOptions.restore(options); // 숨긴 칸은 폼이 받은 옛 값 그대로
|
||||
return options;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -81,9 +81,15 @@ export function fordSection(designFlowM3s: number | null, widthM: number): FordS
|
||||
|
||||
/* ── 폼 조각 ────────────────────────────────────────────────────────────── */
|
||||
|
||||
export function labeled(text: string, input: HTMLElement): HTMLLabelElement {
|
||||
export function labeled(
|
||||
text: string,
|
||||
input: HTMLElement,
|
||||
/** 이 칸이 적는 옵션 키 — 「구조물 옵션 보이기」에서 끈 키의 칸을 가리는 표식(21-10 흠 8). */
|
||||
optionKeys: string | readonly string[] = [],
|
||||
): HTMLLabelElement {
|
||||
const wrapper = document.createElement("label");
|
||||
wrapper.className = "b05-route__field";
|
||||
if (optionKeys.length) wrapper.dataset.optionKeys = [optionKeys].flat().join(" ");
|
||||
const caption = document.createElement("span");
|
||||
caption.textContent = text;
|
||||
wrapper.append(caption, input);
|
||||
@@ -355,11 +361,11 @@ export function createRevetmentFields(
|
||||
};
|
||||
|
||||
// 눈금은 조정창과 같은 값이다 — 길이 1m, 전/후 0.5m, 높이 0.1m(2026-08-29 일원화).
|
||||
const formField = labeled("형태", form);
|
||||
const lengthField = length ? labeled("길이 (m)", stepper(length, 1)) : null;
|
||||
const heightField = labeled("높이 (m)", stepper(height, 0.1));
|
||||
const beforeField = before ? labeled("기준측점 전 (m)", stepper(before, 0.5)) : null;
|
||||
const afterField = after ? labeled("기준측점 후 (m)", stepper(after, 0.5)) : null;
|
||||
const formField = labeled("형태", form, keys.form);
|
||||
const lengthField = length ? labeled("길이 (m)", stepper(length, 1), keys.length) : null;
|
||||
const heightField = labeled("높이 (m)", stepper(height, 0.1), keys.height);
|
||||
const beforeField = before ? labeled("기준측점 전 (m)", stepper(before, 0.5), keys.before) : null;
|
||||
const afterField = after ? labeled("기준측점 후 (m)", stepper(after, 0.5), keys.after) : null;
|
||||
|
||||
// [제안값 넣기] — **누른 때만** 빈 칸에 제안값을 넣음(적은 칸은 안 건드림) · 층따기 단추와 같은 모양.
|
||||
const fill = document.createElement("button");
|
||||
@@ -494,15 +500,15 @@ export function createWingFields(title: string, keys: WingKeys): WingFields {
|
||||
const angle = numberInput("1", "0", `제안 ${WING_DEFAULTS.angle}`);
|
||||
|
||||
// 숫자 칸은 기슭막이·집수정과 같은 [-][값][+] 묶음으로 맞춘다(2026-08-30 사용자).
|
||||
const heightField = labeled("짧은쪽 높이 (m)", stepper(height, 0.1));
|
||||
const heightField = labeled("짧은쪽 높이 (m)", stepper(height, 0.1), keys.height);
|
||||
const dimsRow = grid(
|
||||
labeled("길이 (m)", stepper(length, 0.1)),
|
||||
labeled("각도 (°)", stepper(angle, 5, 0)),
|
||||
labeled("길이 (m)", stepper(length, 0.1), keys.length),
|
||||
labeled("각도 (°)", stepper(angle, 5, 0), keys.angle),
|
||||
);
|
||||
const box = group(title);
|
||||
const slot = document.createElement("div");
|
||||
slot.className = "b05-structure__adjust-slot";
|
||||
box.body.append(grid(labeled("설치", install), heightField), dimsRow, slot);
|
||||
box.body.append(grid(labeled("설치", install, keys.install), heightField), dimsRow, slot);
|
||||
|
||||
function syncVisibility(): void {
|
||||
const off = install.value === "없음";
|
||||
|
||||
@@ -41,12 +41,12 @@ export interface FordFields {
|
||||
export function createFordFields(emit: () => void): FordFields {
|
||||
// 세월교 — 구체 내 배관 수량(련). 련은 정수라 소수 자릿수를 두지 않는다.
|
||||
const count = numberInput("1", "1", "련");
|
||||
const countRow = grid(labeled("수량 (련)", stepper(count, 1, 0)));
|
||||
const countRow = grid(labeled("수량 (련)", stepper(count, 1, 0), "pipe_count"));
|
||||
const width = numberInput("0.1");
|
||||
const height = numberInput("0.01");
|
||||
const widthRow = grid(
|
||||
labeled("월류 폭 (m)", stepper(width, 0.1)),
|
||||
labeled("월류 높이 (m)", stepper(height, 0.1)),
|
||||
labeled("월류 폭 (m)", stepper(width, 0.1), "ford_width_m"),
|
||||
labeled("월류 높이 (m)", stepper(height, 0.1), "ford_height_m"),
|
||||
);
|
||||
// 물넘이 바닥은 유입(상류)이 높고 유출이 낮게 기운다(2026-08-28 사용자 확정).
|
||||
// 비우면 그 측점의 **노면 횡단경사**를 그대로 쓴다 — 횡단도가 판단한다.
|
||||
@@ -56,9 +56,9 @@ export function createFordFields(emit: () => void): FordFields {
|
||||
const thickness = numberInput("1");
|
||||
const length = numberInput("0.1");
|
||||
const slopeRow = grid(
|
||||
labeled("바닥 경사 유입→유출 (%)", stepper(slope, 0.1)),
|
||||
labeled("포장 두께 (㎝)", stepper(thickness, 1, 0)),
|
||||
labeled("포장 길이 노폭 방향 (m)", stepper(length, 0.1)),
|
||||
labeled("바닥 경사 유입→유출 (%)", stepper(slope, 0.1), "ford_slope_pct"),
|
||||
labeled("포장 두께 (㎝)", stepper(thickness, 1, 0), "thickness_cm"),
|
||||
labeled("포장 길이 노폭 방향 (m)", stepper(length, 0.1), "length_m"),
|
||||
);
|
||||
const summary = document.createElement("p");
|
||||
summary.className = "b05-drainage__facility-note";
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_Drainage_Facility_Hidden.ts
|
||||
* 계곡 통과 시설 폼의 **끈 옵션 가리기** — M02 「구조물 옵션 보이기」에서 끈 옵션
|
||||
* (`GET /api/projects/structure-types` 의 `visible: false`)의 칸을 시설 폼에서도 안 그린다
|
||||
* (PLAN 21-10 흠 8).
|
||||
*
|
||||
* · 칸은 `labeled(..., 옵션키)` 가 붙인 `data-option-keys` 표식으로 찾는다 — 키가 여럿인 칸(본체 규격 ·
|
||||
* 유입구 구조)은 **키가 다 꺼졌을 때** 가린다(하나만 꺼지면 남은 키를 적는 칸이라 둠).
|
||||
* · 숨긴 칸의 값은 칸이 아니라 **폼이 받은 옛 값**(`remember`)을 저장에 그대로 싣는다 —
|
||||
* 칸에 제안값을 채워도 숨긴 값이 몰래 바뀌지 않는다.
|
||||
* · 필수 검사는 이 폼에 없다(레지스트리 옵션 칸은 `renderOptionFields` 가 숨긴 칸을 뺌).
|
||||
* ========================================================================== */
|
||||
|
||||
import { hiddenFormKeys } from "./B05_Profile_UI_Drainage_Facility_Merge";
|
||||
|
||||
export interface HiddenOptions {
|
||||
/** 폼이 받은 옵션 — 숨긴 칸의 옛 값 보존용. `setFacility` 가 부른다. */
|
||||
remember: (options: Record<string, string | number>) => void;
|
||||
/** 끈 레지스트리 키를 받아 칸을 가린다. */
|
||||
set: (facility: string | null, registryKeys: Iterable<string>) => void;
|
||||
/** 읽은 옵션에서 숨긴 칸을 칸 값이 아닌 옛 값으로 바꾼다. */
|
||||
restore: (options: Record<string, string | number>) => void;
|
||||
}
|
||||
|
||||
export function createHiddenOptions(root: HTMLElement): HiddenOptions {
|
||||
let hidden: ReadonlySet<string> = new Set();
|
||||
let stored: Record<string, string | number> = {};
|
||||
const apply = (): void => {
|
||||
root.querySelectorAll<HTMLElement>("[data-option-keys]").forEach((node) => {
|
||||
const keys = (node.dataset.optionKeys ?? "").split(" ");
|
||||
node.classList.toggle(
|
||||
"is-option-hidden",
|
||||
keys.every((key) => hidden.has(key)),
|
||||
);
|
||||
});
|
||||
// 칸이 다 가려진 줄은 줄째(빈 줄 간격이 안 남게)
|
||||
root.querySelectorAll<HTMLElement>(".b05-structure__grid").forEach((row) => {
|
||||
const kids = [...row.children];
|
||||
const all =
|
||||
kids.length > 0 && kids.every((kid) => kid.classList.contains("is-option-hidden"));
|
||||
row.classList.toggle("is-option-hidden", all);
|
||||
});
|
||||
};
|
||||
return {
|
||||
remember(options) {
|
||||
stored = { ...options };
|
||||
},
|
||||
set(facility, registryKeys) {
|
||||
hidden = hiddenFormKeys(facility, registryKeys);
|
||||
apply();
|
||||
},
|
||||
restore(options) {
|
||||
for (const key of hidden) {
|
||||
delete options[key];
|
||||
if (stored[key] !== undefined) options[key] = stored[key];
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -53,6 +53,28 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
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;
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
loadPipeForm as loadPipeFormInto,
|
||||
} from "./B05_Profile_UI_Structures_Panel_Commit";
|
||||
import { renderStructureList } from "./B05_Profile_UI_Structures_List";
|
||||
import { isOptionVisible } from "./B05_Profile_UI_Structures_Summary";
|
||||
import { stationLabel as formatStation } from "../common_util/common_util_station";
|
||||
|
||||
/** 공개 타입·표시 상수는 700줄 제한으로 `_Structures_Panel_Types` 로 옮겼다 —
|
||||
@@ -228,7 +229,13 @@ export function createStructuresSection(
|
||||
options: Record<string, string | number> = {},
|
||||
designFlow: number | null = null,
|
||||
): void {
|
||||
facilityOptions.setFacility(facilityFormKind(currentType()), options, designFlow);
|
||||
const kind = facilityFormKind(currentType());
|
||||
facilityOptions.setFacility(kind, options, designFlow);
|
||||
// 「구조물 옵션 보이기」에서 끈 옵션은 시설 폼에서도 가림(값은 옛 값 그대로 저장)
|
||||
facilityOptions.setHiddenOptions(
|
||||
kind,
|
||||
(currentType()?.options ?? []).filter((o) => !isOptionVisible(o)).map((o) => o.key),
|
||||
);
|
||||
syncOptionLock();
|
||||
}
|
||||
|
||||
|
||||
@@ -484,3 +484,8 @@
|
||||
.b05-route__irregular-item.is-assigned span {
|
||||
color: color-mix(in srgb, var(--color-success) 65%, var(--color-text));
|
||||
}
|
||||
|
||||
/* 「구조물 옵션 보이기」에서 끈 옵션의 시설 폼 칸 */
|
||||
.b05-drainage__facility .is-option-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,20 @@ class FakeNode {
|
||||
(this.listeners.click || []).forEach((h) => h({ target: this }));
|
||||
}
|
||||
scrollIntoView() {}
|
||||
// 시험에 필요한 두 가지 선택자만 — `[data-option-keys]` · `.클래스`
|
||||
querySelectorAll(selector) {
|
||||
const match = selector.startsWith("[")
|
||||
? (n) => n.dataset && n.dataset.optionKeys !== undefined
|
||||
: (n) => n.classList.contains(selector.slice(1));
|
||||
const found = [];
|
||||
const walk = (node) =>
|
||||
node.children.forEach((kid) => {
|
||||
if (kid.classList && match(kid)) found.push(kid);
|
||||
walk(kid);
|
||||
});
|
||||
walk(this);
|
||||
return found;
|
||||
}
|
||||
}
|
||||
globalThis.document = { createElement: (tag) => new FakeNode(tag) };
|
||||
globalThis.Option = function Option(text, value) {
|
||||
@@ -101,6 +115,9 @@ function loadTs(file) {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
// 화면 공용 부품(@ui/…) — 이 시험이 안 쓰는 것이라 뭘 불러도 아무 일 안 하는 함수로 막아 둠
|
||||
if (request.startsWith("@ui/")) return new Proxy({}, { get: () => () => {} });
|
||||
if (request.startsWith(".") && request.endsWith(".json")) {
|
||||
return JSON.parse(fs.readFileSync(path.join(path.dirname(full), request), "utf8"));
|
||||
}
|
||||
return request.startsWith(".")
|
||||
? loadTs(path.join(path.dirname(full), `${request}.ts`))
|
||||
: require(request);
|
||||
@@ -118,6 +135,8 @@ const { buildSummaryRow } = B05("B05_Profile_UI_Structures_Summary_Row");
|
||||
const { renderStructureList } = B05("B05_Profile_UI_Structures_List");
|
||||
const { renderOptionFields } = B05("B05_Profile_UI_Structures_Panel_Options");
|
||||
const commitModule = B05("B05_Profile_UI_Structures_Panel_Commit");
|
||||
const { createHiddenOptions } = B05("B05_Profile_UI_Drainage_Facility_Hidden");
|
||||
const { labeled } = B05("B05_Profile_UI_Drainage_Facility_Fields");
|
||||
|
||||
const out = {};
|
||||
const A = {
|
||||
@@ -358,6 +377,58 @@ const prev = () => ({
|
||||
summary_item: A,
|
||||
});
|
||||
|
||||
// 시설 폼 끈 옵션 가리기(PLAN 21-10 흠 8)
|
||||
{
|
||||
const tagged = labeled("관종", new FakeNode("select"), "pipe_kind");
|
||||
const multi = labeled("구조", new FakeNode("select"), ["inlet_type", "inlet_structure"]);
|
||||
const plain = labeled("메모", new FakeNode("select"));
|
||||
out.labelTag = [tagged.dataset.optionKeys, multi.dataset.optionKeys, plain.dataset.optionKeys];
|
||||
const root = new FakeNode("div");
|
||||
const rowA = new FakeNode("div");
|
||||
rowA.className = "b05-structure__grid";
|
||||
const kind = labeled("관종", new FakeNode("select"), "pipe_kind");
|
||||
const dia = labeled("관경", new FakeNode("select"), "pipe_diameter_mm");
|
||||
rowA.append(kind, dia);
|
||||
const rowB = new FakeNode("div");
|
||||
rowB.className = "b05-structure__grid";
|
||||
const inlet = labeled("구조", new FakeNode("select"), ["inlet_type", "inlet_structure"]);
|
||||
const free = labeled("메모", new FakeNode("select")); // 표식 없는 칸은 안 가림
|
||||
rowB.append(inlet, free);
|
||||
root.append(rowA, rowB);
|
||||
const hiddenOf = () => ({
|
||||
kind: kind.classList.contains("is-option-hidden"),
|
||||
dia: dia.classList.contains("is-option-hidden"),
|
||||
rowA: rowA.classList.contains("is-option-hidden"),
|
||||
inlet: inlet.classList.contains("is-option-hidden"),
|
||||
free: free.classList.contains("is-option-hidden"),
|
||||
rowB: rowB.classList.contains("is-option-hidden"),
|
||||
});
|
||||
const hidden = createHiddenOptions(root);
|
||||
hidden.set("pipe", ["pipe_kind"]);
|
||||
out.hideOne = hiddenOf();
|
||||
hidden.set("pipe", ["pipe_kind", "pipe_diameter_mm"]);
|
||||
out.hideRow = hiddenOf(); // 줄의 칸이 다 가려지면 줄째
|
||||
hidden.set("pipe", ["inlet_structure"]);
|
||||
out.hideInletPartial = hiddenOf(); // 키가 여럿인 칸은 하나만 꺼지면 그대로
|
||||
hidden.set("pipe", ["inlet_type"]);
|
||||
out.hideInlet = hiddenOf(); // 유입구 「구조」는 inlet_type 을 끄면 함께 가려짐(구조 키 포함)
|
||||
hidden.set("pipe", []);
|
||||
out.showAll = hiddenOf(); // [모두 보임] 뒤 되돌아옴
|
||||
// 숨긴 칸은 폼이 받은 옛 값 그대로 — 칸에서 읽은 값(제안값 채우기 포함)이 아니라
|
||||
hidden.remember({ pipe_kind: "흄관", pipe_diameter_mm: 1000, other: 1 });
|
||||
hidden.set("pipe", ["pipe_kind", "inlet_type"]);
|
||||
const read = { pipe_kind: "VR관", pipe_diameter_mm: 800, inlet_type: "제안", other: 2 };
|
||||
hidden.restore(read);
|
||||
out.restored = read;
|
||||
hidden.remember({}); // 옛 값이 없던 숨긴 칸은 값이 안 실림
|
||||
const read2 = { pipe_kind: "VR관", pipe_diameter_mm: 800 };
|
||||
hidden.restore(read2);
|
||||
out.restoredEmpty = read2;
|
||||
out.revetKeys = [...merge.hiddenFormKeys("revetment", ["form", "height_m", "side"])].sort();
|
||||
out.pipeKeys = [...merge.hiddenFormKeys("pipe", ["inlet_type"])].sort();
|
||||
out.otherKeys = [...merge.hiddenFormKeys("box_culvert", ["form"])].sort();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const fresh = await saveWith({ previous: null, summary: null, formOptions: { H: 3 } });
|
||||
out.commitNew = { hasKey: "summary_item" in fresh, options: fresh.options };
|
||||
|
||||
@@ -136,3 +136,39 @@ def test_배수유역_화면은_들어올_때_세션_초안_관을_먼저_씀():
|
||||
body = source.split("const loadSaved", 1)[1].split("/**", 1)[0]
|
||||
assert "readPendingPipes(" in body and "computeDetailBasins(" in body
|
||||
assert body.index("readPendingPipes(") < body.index("fetchDetailPipePoints(")
|
||||
|
||||
|
||||
def test_시설_폼_끈_옵션_가리기(out):
|
||||
# 칸 표식 — 키 하나 · 여럿 · 없음
|
||||
assert out["labelTag"] == ["pipe_kind", "inlet_type inlet_structure", None]
|
||||
none = dict(kind=False, dia=False, rowA=False, inlet=False, free=False, rowB=False)
|
||||
assert out["hideOne"] == {**none, "kind": True}
|
||||
# 줄의 칸이 다 가려지면 줄째 · 표식 없는 칸은 그대로
|
||||
assert out["hideRow"] == {**none, "kind": True, "dia": True, "rowA": True}
|
||||
assert out["hideInletPartial"] == none
|
||||
assert out["hideInlet"] == {**none, "inlet": True}
|
||||
assert out["showAll"] == none # [모두 보임] 뒤 되돌아옴
|
||||
|
||||
|
||||
def test_숨긴_칸은_옛_값_그대로_저장에_실림(out):
|
||||
# 칸에서 읽은 값(VR관 · 제안)이 아니라 폼이 받은 옛 값 — 안 숨긴 칸은 칸 값
|
||||
assert out["restored"] == {"pipe_kind": "흄관", "pipe_diameter_mm": 800, "other": 2}
|
||||
# 옛 값이 없던 숨긴 칸은 값이 안 실림(칸의 제안값이 몰래 확정되지 않음)
|
||||
assert out["restoredEmpty"] == {"pipe_diameter_mm": 800}
|
||||
|
||||
|
||||
def test_끈_키를_폼_칸_키로_넓힘(out):
|
||||
# 독립 기슭막이는 유입 · 유출 채널 키로 · 유입구 구조는 inlet_structure 까지
|
||||
assert out["revetKeys"] == sorted(
|
||||
[
|
||||
"form",
|
||||
"height_m",
|
||||
"side",
|
||||
"inlet_revet_form",
|
||||
"outlet_revet_form",
|
||||
"inlet_revet_height_m",
|
||||
"outlet_revet_height_m",
|
||||
]
|
||||
)
|
||||
assert out["pipeKeys"] == ["inlet_structure", "inlet_type"]
|
||||
assert out["otherKeys"] == ["form"]
|
||||
|
||||
Reference in New Issue
Block a user