Files
Aislo/B05_Profile/B05_Profile_UI_Structures_Form.ts
T
eomsangdonandClaude Sonnet 5.5 33119bcc67 feat(B05): 노면공 확폭 구간 상자 · 물넘이 겹침 안내 문구 · 화면 시험 맞춤(87-3 · 87-4)
- 상세 내용 아래 「확폭 구간」 상자: 범위 안 확폭 > 0 측점을 이어진 구간으로 묶어 구간마다 켬/끔 단추(기본 켬) · 끈 구간 = options.widening_off "시작-끝,…"(측점 m) · 안 맞는 값 무시 · 횡단 자료 없으면 안내
- 횡단 측점 읽기 손잡이 pipeSections(design_structure_pipe_calc) · 구간 규칙 design_structure_widening(순수)
- 물넘이 겹침 안내 = 시작 · 종료 측점을 고치라는 문구
- 시험: test_87_widening_runs(새) · ui_checks 52-10 · 52-15 시작 · 종료 측점 꼴로 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
2026-10-08 12:08:15 +09:00

238 lines
10 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Structures_Form.ts
* 「구조물 배치」 폼의 **뼈대**만 세우는 조립기 (2026-09-03 · 700줄 제한).
*
* 패널 본체(`_UI_Structures_Panel`)는 값 읽기·검증·저장·목록 갱신을 맡고, 여기서는
* 화면 요소를 만들어 넘기기만 한다. 배치 규칙(행 순서·구분선·버튼 묶음)은 사용자 지시로
* 굳은 것이라 그대로 옮겼다.
*
* 화면 구성(2026-08-17~29 사용자 지시):
* [구조물군][종류] ← 2열 격자
* [측점 범위] [시작 측점] [기준 측점] [종료 측점] ← 타입에 따라 보이는 행이 갈린다
* ──────── 구분선
* [옵션 격자] / [계곡 통과 시설 서브폼]
* [추가·삭제·리셋]
* 목록(`list`)은 본문에 붙이지 않는다 — 폼이 길어지면 스크롤 밖으로 밀려 안 보였다.
* ========================================================================== */
import {
field,
select,
type StationFields,
stationFields,
} from "./B05_Profile_UI_Structures_Fields";
import { createFacilityOptionsForm } from "./B05_Profile_UI_Drainage_Facility";
import { buildSummaryRow, type SummaryRow } from "./B05_Profile_UI_Structures_Summary_Row";
import { createGroupBoxes, type GroupBoxes } from "./B05_Profile_UI_Structures_Groups";
import {
createFacilityDefinition,
type FacilityDefinition,
} from "./B05_Profile_UI_Structures_Define_Form";
import { createStructureSpecForm } from "./B05_Profile_UI_Structures_Spec";
import { createWideningBox } from "./B05_Profile_UI_Structures_Widening";
export interface StructuresFormElements {
root: HTMLElement;
body: HTMLElement;
groupSelect: HTMLSelectElement;
typeSelect: HTMLSelectElement;
startFields: StationFields;
anchorFields: StationFields;
endFields: StationFields;
memoField: HTMLInputElement;
/** 계산으로 채우는 측점 범위 표시(직접 입력이 아니다). */
rangeValue: HTMLElement;
rangeWrap: HTMLElement;
positionRow: HTMLElement;
/** 측점 그룹과 옵션 사이 구분선 — 타입이 없으면 함께 숨긴다. */
positionDivider: HTMLElement;
optionRow: HTMLElement;
/** 버튼 묶음 — 패널이 그 앞에 메모 칸을 끼워 넣는다. */
actions: HTMLElement;
facilityOptions: ReturnType<typeof createFacilityOptionsForm>;
/** 「지난 정의 값」 읽기만 상자(48-3) — 지금 정의에 없는 키의 값. */
staleBox: HTMLElement;
/** 시설 폼(손코딩)에 정의 붙이기(48-3 · 48-4) — 숨긴 선택지 · 규칙. */
facilityDefinition: FacilityDefinition;
primary: HTMLButtonElement;
removeButton: HTMLButtonElement;
resetButton: HTMLButtonElement;
/** 하단 고정 영역에 패널이 따로 붙이는 구조물 목록. */
list: HTMLElement;
/** 옵션 · 시설 서브폼 아래 「집계 항목」 줄(PLAN 21-4) — 이름 · [고르기] · [해제]. */
summary: SummaryRow;
/** 본체 집계 항목 아래 옵션 그룹 보조 컨테이너(PLAN 46-3) — 그룹마다 상자 · 집계 항목. */
groupBoxes: GroupBoxes;
}
/** 폼 뼈대를 세운다. 값 채우기·이벤트 배선은 호출한 쪽(패널)이 한다. */
export function buildStructuresForm(options: {
/** 측점 잔여거리 정규화에 쓰는 현재 측점 간격(m). */
getInterval: () => number;
/** 계곡 통과 시설 서브폼 값이 바뀔 때 — 패널이 즉시 반영을 건다. */
onFacilityChange: () => void;
}): StructuresFormElements {
const root = document.createElement("section");
// b05-structure-section: 이 섹션 안의 입력·버튼 높이를 한 값(--b05-control-h)으로
// 묶는 스코프(2026-08-29 사용자 지시 — 조작 컨트롤 높이 통일).
root.className =
"b05-route__panel-section b05-structure-section ui-collapsible ui-sidebar-section";
const heading = document.createElement("h3");
heading.className = "ui-collapsible__title";
heading.textContent = "구조물 배치";
const body = document.createElement("div");
body.className = "b05-route__panel-body";
root.append(heading, body);
const groupSelect = select([]);
const typeSelect = select([]);
// 위치 입력 순서 = 시작 측점 → 기준 측점 → 종료 측점 (2026-08-17 사용자 확정).
// 점형은 기준 측점만 보인다. 잔여거리는 [0, 간격) — 넘치면 측점번호로 올려 표시한다.
const startFields = stationFields("시작 측점", options.getInterval);
const anchorFields = stationFields("기준 측점", options.getInterval);
const endFields = stationFields("종료 측점", options.getInterval);
const memoField = document.createElement("input");
memoField.type = "text";
memoField.placeholder = "메모(선택)";
// 측점 범위(계산 표시) — C군 사면안정처럼 길이 옵션으로 구간이 정해지는 타입은
// 시작·종료를 직접 입력하지 않고 기준 측점 + 길이로 계산해 보여만 준다
// (2026-08-19 사용자 지시). 표기는 하단 구조물 목록과 같은 시작 ~ 종료 측점.
const rangeValue = document.createElement("span");
rangeValue.className = "b05-structure__range-value";
const rangeWrap = field("측점 범위", rangeValue);
// 측점은 3행 — 한 행이 [라벨][측점번호][잔여거리](2026-08-17 사용자 지시 2).
const positionRow = document.createElement("div");
positionRow.className = "b05-structure__position-row";
positionRow.append(rangeWrap, startFields.wrap, anchorFields.wrap, endFields.wrap);
// 기본 인터페이스는 2열 격자(2026-08-17 사용자 지시 1).
const typeRow = document.createElement("div");
typeRow.className = "b05-structure__grid";
typeRow.append(field("구조물군", groupSelect), field("종류", typeSelect));
// 옵션 칸은 타입마다 다르므로 선택할 때마다 새로 그린다.
const optionRow = document.createElement("div");
optionRow.className = "b05-structure__grid";
// 계곡 통과 시설(배관 등)의 부속 옵션 서브폼 — 일반 구조물의 옵션 칸과 같은 자리에서
// 같은 흐름(종류 선택 → 옵션 → 추가/수정)으로 편집한다(2026-08-17 사용자 지시 —
// 자동·수동 배관은 같은 구조물, 별도 UI 금지).
const facilityOptions = createFacilityOptionsForm({ onChange: options.onFacilityChange });
const primary = document.createElement("button");
primary.type = "button";
primary.className = "b05-route__irregular-btn is-primary";
const removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.className = "b05-route__irregular-btn is-danger";
removeButton.textContent = "삭제";
const resetButton = document.createElement("button");
resetButton.type = "button";
resetButton.className = "b05-route__irregular-btn";
resetButton.textContent = "리셋";
const actions = document.createElement("div");
actions.className = "b05-route__irregular-actions";
actions.append(primary, removeButton, resetButton);
// 목록은 이 섹션 본문에 붙이지 않는다 — 폼이 길어지면 스크롤 밖으로 밀려 안 보였다.
// 패널(B05_Profile_UI_Panel)이 하단 고정 영역에 배치한다(2026-08-18 사용자 지시).
const list = document.createElement("ul");
list.className = "b05-route__irregular-list";
// 측점 그룹과 옵션 나열 사이 구분선(2026-08-17 사용자 지시 2).
const positionDivider = document.createElement("hr");
positionDivider.className = "b05-structure__divider";
// 집계 항목 줄 — 종류를 고른 뒤에만 보인다(패널이 `syncPlacementFields` 에서 켠다).
const summary = buildSummaryRow();
summary.root.hidden = true;
const groupBoxes = createGroupBoxes();
// 배관 명세 컨테이너 「구조물 조합」 잇기 = 그룹 값(`group_items`)과 같은 자리 · 같은 [고르기] 길(PLAN 52-1)
facilityOptions.bindLinks({
get: groupBoxes.get,
pick: groupBoxes.pick,
clear: groupBoxes.clear,
set: groupBoxes.setOne,
});
groupBoxes.watch(facilityOptions.paintLinks);
// 노면공 시작~종료 읽기(87-1 길이 출력 · 87-3 확폭 구간 상자가 같이 씀) — 못 읽으면 null
const rangeNow = (): { start: number | null; end: number | null } => {
const step = options.getInterval();
return { start: startFields.read(step, false), end: endFields.read(step, false) };
};
const widening = createWideningBox({
range: rangeNow,
interval: options.getInterval,
onChange: options.onFacilityChange,
});
// 일반 구조물 명세 컨테이너(PLAN 52-10 · 노면공 등) — 잇기 · 반영 길은 배관 명세와 같음
const structureSpec = createStructureSpecForm(
optionRow,
{ get: groupBoxes.get, pick: groupBoxes.pick, clear: groupBoxes.clear, set: groupBoxes.setOne },
options.onFacilityChange,
// 노면공 길이 칸(87-1) = 시작~종료 출력 — 못 셈이면 빈 글(「—」)
(_c, item) => {
const { start, end } = rangeNow();
return item.id === "length" && start !== null && end !== null && end > start
? String(Number((end - start).toFixed(2)))
: "";
},
widening,
);
// 시작 · 종료를 고치면 길이 출력 · 확폭 구간을 다시 그림
[startFields, endFields].forEach((fields) =>
[fields.station, fields.remainder].forEach((input) =>
input.addEventListener("change", () => {
structureSpec.refresh();
widening.refresh();
}),
),
);
groupBoxes.watch(structureSpec.paintLinks);
const staleBox = document.createElement("fieldset");
staleBox.className = "b05-structure__group b05-structure__stale";
staleBox.hidden = true;
body.append(
typeRow,
positionRow,
summary.root, // 종류 단위 「구조물 조합」 잇기 — 기준 측점 바로 아래(PLAN 52-1 · 컨테이너 없는 종류)
positionDivider,
optionRow,
structureSpec.root,
widening.root, // 노면공 「확폭 구간」 — 상세 내용 아래(87-3)
facilityOptions.root,
staleBox,
groupBoxes.root,
actions,
);
return {
root,
body,
groupSelect,
typeSelect,
startFields,
anchorFields,
endFields,
memoField,
rangeValue,
rangeWrap,
positionRow,
positionDivider,
optionRow,
actions,
facilityOptions,
staleBox,
facilityDefinition: createFacilityDefinition(body),
primary,
removeButton,
resetButton,
list,
summary,
groupBoxes,
};
}