feat(구조물): C군 벽에 「설치 측」 칸 추가 + 칸을 잠글 수 있는 구조

PLAN 3-6 「설치 측을 사용자가 고르게 할지(폼에 칸 추가) 뒤에 정할 것」 — 2026-09-07
사용자 확정 「폼 선택은 가능하게 반영하고 나중에 선택 비활성화로 하자」.

- 레지스트리 C군 5종(옹벽·돌쌓기(찰)·돌쌓기(메)·흙막이·큰돌쌓기)에 `side` 선택 칸.
  선택지 「자동(성토 쪽)·좌·우」, 기본은 자동 — 종전 동작(성토 나는 쪽 자동)과 같음.
- 기하는 이미 그 값을 봄 — `B06_Section_UI_Cross_Revetment.ts:168` 이 "좌"/"우" 면 그쪽,
  아니면 `fillSide()`. 배관(`common_util_structure_walls.ts:80`)·파이썬 짝도 `options.side`
  를 이미 나름. 즉 배선은 있고 **칸만 없었음**.
- 옵션 칸에 `enabled` 를 둠(기본 참). 거짓이면 폼에 **회색으로 남고** 못 고르며 필수 검사
  에서도 빠짐 — 칸을 지우지 않고 잠그는 자리(사용자 「나중에 비활성화」).

자체검증 — 레지스트리 5종 모두 `(['자동(성토 쪽)','좌','우'], 기본 '자동(성토 쪽)', enabled True)`
로 읽힘. 실화면 B06 C군 폼에 「설치 측」 칸이 그 세 선택지로 뜸(값 자동). tsc 통과.
시험 499 통과 — 실패 1·오류 8 은 모두 `require is not defined in ES module scope` 인
JS 도우미 건으로 내 변경과 무관(도우미 파일 날짜 8/27·9/2, 다른 환경에서 만든 것).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 08:06:12 +09:00
co-authored by Claude Opus 5
parent a811286e52
commit 6b76a3f273
4 changed files with 49 additions and 1 deletions
@@ -26,6 +26,8 @@ export interface StructureOptionField {
default: string | number | null;
/** 미확정 항목(기본값 없음) — 사용자가 값을 넣어야 저장된다. */
required?: boolean;
/** 거짓이면 폼에 **회색으로** 그려지고 못 고른다 — 칸은 남기되 잠그는 자리. */
enabled?: boolean;
/** 입력 시점 — B05는 유무·종류·위치만 받고 상세 치수(detail)는 B06/B07에서 받는다
* (2026-08-17 사용자 확정). detail이면 required여도 B05 폼에 그리지 않는다. */
phase?: "b05" | "detail";
@@ -639,6 +639,13 @@
"default": null,
"required": true,
"phase": "detail"
},
{
"key": "side",
"label": "설치 측",
"input": "select",
"choices": ["자동(성토 쪽)", "좌", "우"],
"default": "자동(성토 쪽)"
}
]
},
@@ -697,6 +704,13 @@
"default": null,
"required": true,
"phase": "detail"
},
{
"key": "side",
"label": "설치 측",
"input": "select",
"choices": ["자동(성토 쪽)", "좌", "우"],
"default": "자동(성토 쪽)"
}
]
},
@@ -755,6 +769,13 @@
"default": null,
"required": true,
"phase": "detail"
},
{
"key": "side",
"label": "설치 측",
"input": "select",
"choices": ["자동(성토 쪽)", "좌", "우"],
"default": "자동(성토 쪽)"
}
]
},
@@ -813,6 +834,13 @@
"default": null,
"required": true,
"phase": "detail"
},
{
"key": "side",
"label": "설치 측",
"input": "select",
"choices": ["자동(성토 쪽)", "좌", "우"],
"default": "자동(성토 쪽)"
}
]
},
@@ -871,6 +899,13 @@
"default": null,
"required": true,
"phase": "detail"
},
{
"key": "side",
"label": "설치 측",
"input": "select",
"choices": ["자동(성토 쪽)", "좌", "우"],
"default": "자동(성토 쪽)"
}
]
},
@@ -44,6 +44,10 @@ class StructureOptionField(BaseModel):
# (2026-08-17 사용자 확정). `detail`이면 required여도 B05 저장에서 강제하지 않는다
# — 필수 원칙은 유지되고 강제 시점만 B06/B07로 미뤄진다.
phase: Literal["b05", "detail"] = "b05"
# 폼에 칸은 두되 **지금은 못 고르게** 할 때 거짓으로 둔다 — 회색으로 그려지고 값은
# 기본값이 그대로 저장된다(2026-09-07 사용자: 「폼 선택은 가능하게 반영하고 나중에
# 선택 비활성화로 하자」). 칸 자체를 없애면 나중에 켤 자리를 다시 찾아야 한다.
enabled: bool = True
class StructureType(BaseModel):
@@ -322,11 +322,18 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
input.value = String(preset ?? "");
}
const label = option.unit ? `${option.label} (${option.unit})` : option.label;
// `enabled:false` 는 칸을 남기고 잠그기만 한다 — 값은 기본값이 그대로 저장된다.
const locked = option.enabled === false;
if (locked) {
input.disabled = true;
input.classList.add("is-locked");
input.title = "지금은 고를 수 없는 항목입니다.";
}
optionRow.append(field(label, input));
input.addEventListener("change", () => liveCommit());
optionInputs.push({
key: option.key,
required: !!option.required,
required: !locked && !!option.required,
input,
read: () => (option.input === "number" ? Number(input.value) || 0 : input.value),
isEmpty: () => input.value.trim() === "",