feat(B05): 옵션 활성화 시점을 위치 입력 확정 시점으로 통일

측점번호만 넣어도 옵션이 풀리던 것을, 임시 배치와 같은 시점 — 측점번호+잔여거리를
다 받고 입력군을 빠져나온 순간(commitPosition) — 으로 맞춘다(2026-08-18 사용자
지시). 값을 지우면 확정이 풀려 즉시 다시 잠긴다. 기존 항목·우클릭 추가는 위치가
실려 열리므로 처음부터 활성.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 18:47:42 +09:00
co-authored by Claude Opus 5
parent 2e6f657f39
commit 33fe2fa660
+22 -4
View File
@@ -192,6 +192,10 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
* 미리 넣어 세부유역·설계유량 미리보기를 얻고, [추가]로만 확정한다. 취소(삭제·
* 리셋·다른 항목 선택·종류 변경)하면 관을 물려 추가 직전 상태로 되돌린다. */
let tempPipeChainage: number | null = null;
/** 위치 입력이 "확정"됐는지 — 측점번호+잔여거리를 다 받고 입력군을 빠져나간
* 순간(또는 폼에 위치를 실어 연 순간)에만 true. 옵션 활성화 시점도 임시 배치와
* 같은 이 순간이다(2026-08-18 사용자 지시). */
let positionConfirmed = false;
let optionInputs: Array<{
key: string;
required: boolean;
@@ -255,7 +259,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
* 입력 순서 가이드(2026-08-18 사용자 지시). 자동 배치 시설은 측점을 갖고 폼에
* 올라오므로 항상 열려 있다. */
function syncOptionLock(): void {
const locked = !hasPosition();
const locked = !(positionConfirmed && hasPosition());
optionInputs.forEach((entry) => {
entry.input.disabled = locked;
});
@@ -286,8 +290,18 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
syncButtons();
}
/** 측점 입력 변경 — 옵션 잠금만 다시 판정한다(임시 배치는 commitTempPipe 몫). */
/** 측점 입력 변경 — 값을 지우면 확정이 풀려 옵션이 즉시 다시 잠긴다.
* 확정(활성화)은 입력군 이탈 시 commitPosition이 한다. */
function handleStationInput(): void {
if (!hasPosition()) positionConfirmed = false;
syncOptionLock();
}
/** 위치 입력 확정 — 측점번호+잔여거리를 다 받고 입력군을 빠져나온 순간.
* 옵션 활성화와 A군 임시 배치가 이 한 지점에서 같이 나간다(2026-08-18). */
function commitPosition(): void {
positionConfirmed = hasPosition();
commitTempPipe();
syncOptionLock();
}
@@ -419,6 +433,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
cancelTempPipe();
editingId = target?.structure_id ?? null;
selectedPipeChainage = null;
// 위치가 실려 열리는 폼(기존 항목)은 확정 상태, 빈 폼은 미확정으로 시작한다.
positionConfirmed = target !== null;
const step = interval();
if (target) {
const type = typeMap().get(target.type_id);
@@ -469,6 +485,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
const hadStructure = editingId !== null;
editingId = null;
selectedPipeChainage = pipe.chainage_m;
positionConfirmed = true; // 기존 관 — 위치가 실려 열린다.
const step = interval();
const type = typeMap().get(pipe.facility);
if (type) {
@@ -657,7 +674,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
// 따라 비어 온다).
window.requestAnimationFrame(() => {
if (positionRow.contains(document.activeElement)) return;
commitTempPipe();
commitPosition();
});
});
positionRow.addEventListener("keydown", (event) => {
@@ -761,6 +778,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
typeSelect.value = typeId;
editingId = null;
selectedPipeChainage = null;
positionConfirmed = true; // 우클릭은 측점이 확정돼 들어온다 — 옵션도 바로 연다.
const placement = placementOf(typeId);
const step = interval();
const isInterval = !type.managed_by && placement === "interval";
@@ -773,7 +791,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
// BOX암거는 레지스트리 기본값(본체 2.0×2.0·날개벽 있음 1m/2m/45°)을 실어 폼을
// 연다 — [추가]만 눌러도 하류가 제원을 받는다(2026-08-17 사용자 확정 유지).
syncFacilityForm(typeId === "box_culvert" ? defaultOptions(type) : {});
// 우클릭은 측점이 이미 확정돼 들어온다 — 임시 배치 즉시 진행.
// 임시 배치 즉시 진행(A군).
if (type.managed_by) commitTempPipe();
syncButtons();
renderList();