사용자 결정 두 가지를 반영함(2026-09-07). ① 측구(옆도랑) — 목록에 두되 표시만. 레지스트리에 `design_owner` 를 새로 두고 측구에 「횡단 설계」를 적음. 구조물 배치 폼에서 그 종류를 고르면 「횡단 설계에서 관리 — 여기서 넣어도 제원·수량은 그쪽 값을 씁니다.」가 뜸. `managed_by` 와 달리 저장은 그대로 되므로 배치는 계속 가능함. 까닭 — 횡단 설계가 측구 켬/끔·형식·터파기 단면적(`ditch_area_m2`)을 파이썬·TS 짝으로 이미 셈함. 구조물로 또 세면 같은 것을 두 번 계상함. 목록 항목에 안 붙이고 폼에 붙인 것은 「한 항목 = [측점][이름]만」이 2026-08-18 사용자 지시이기 때문임. ② B군 수량 1단계 — 구조물 정본에서 시설별 연장(m)을 냄. `common_util_structure_lengths.py` 하나. B군 수량 단위가 전부 m 이라 단면 기하가 필요 없음(맹암거 12-10 · L형 측구 12-9-1 · 산마루측구 12-9-2). 수량서 양식에 안 묶이므로 B08 을 실무 xlsx 양식으로 다시 짜도 그대로 씀. 빼는 것 셋 — 관 정본 소관(`managed_by`), 횡단 소관(`design_owner`), 그리고 소단측구 (놓일 소단이 아직 없음 — 계획서 3-9 뒤에 채움). 겹친 구간은 합쳐서 셈함. 같은 시설을 겹치게 두 번 넣으면 단순 합이 그 구간을 두 번 세기 때문임. 원래 합(`raw_length_m`)도 함께 내보내 겹침이 숨지 않게 함. 자체검증 - 새 시험 7건 `tmp/tests/test_structure_lengths.py` — 종류별 합산 / 겹침 제거(80m 인데 단순 합 110m) / 측구 제외 / 소단측구 제외 / 배관 제외 / 구간 없는 항목은 스키마가 막음 / 빈 프로젝트. - 화면 확인(8001·5174, `/api/health` `stale:false`) — 구조물군 B → 종류 측구 선택 시 안내가 254×44px 로 뜨고 문구가 맞음. 리셋으로 폼 되돌림. - 전체 521 passed · 18 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
174 lines
7.6 KiB
TypeScript
174 lines
7.6 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";
|
|
|
|
export interface StructuresFormElements {
|
|
root: HTMLElement;
|
|
body: HTMLElement;
|
|
groupSelect: HTMLSelectElement;
|
|
typeSelect: HTMLSelectElement;
|
|
/** 「제원·수량은 다른 화면이 냅니다」 안내 한 줄 — `design_owner` 가 있을 때만 보인다. */
|
|
ownerNote: HTMLElement;
|
|
startFields: StationFields;
|
|
anchorFields: StationFields;
|
|
endFields: StationFields;
|
|
memoField: HTMLInputElement;
|
|
/** 계산으로 채우는 측점 범위 표시(직접 입력이 아니다). */
|
|
rangeValue: HTMLElement;
|
|
rangeWrap: HTMLElement;
|
|
positionRow: HTMLElement;
|
|
/** 측점 그룹과 옵션 사이 구분선 — 타입이 없으면 함께 숨긴다. */
|
|
positionDivider: HTMLElement;
|
|
optionRow: HTMLElement;
|
|
/** 버튼 묶음 — 패널이 그 앞에 메모 칸을 끼워 넣는다. */
|
|
actions: HTMLElement;
|
|
facilityOptions: ReturnType<typeof createFacilityOptionsForm>;
|
|
primary: HTMLButtonElement;
|
|
removeButton: HTMLButtonElement;
|
|
resetButton: HTMLButtonElement;
|
|
/** 하단 고정 영역에 패널이 따로 붙이는 구조물 목록. */
|
|
list: HTMLElement;
|
|
}
|
|
|
|
/** 폼 뼈대를 세운다. 값 채우기·이벤트 배선은 호출한 쪽(패널)이 한다. */
|
|
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));
|
|
|
|
// 「제원·수량은 다른 화면이 냅니다」 안내 한 줄 — 목록 항목은 [측점][이름]만 적는 규칙이라
|
|
// (2026-08-18 사용자 지시) 표시는 이 폼에 둔다. 측구(옆도랑)가 그 경우다(2026-09-07 사용자:
|
|
// 「두되 표시만 해줘」). 문구는 레지스트리 `design_owner` 값으로 만든다.
|
|
const ownerNote = document.createElement("p");
|
|
ownerNote.className = "b05-structure__owner-note";
|
|
ownerNote.hidden = true;
|
|
|
|
// 옵션 칸은 타입마다 다르므로 선택할 때마다 새로 그린다.
|
|
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";
|
|
|
|
body.append(
|
|
typeRow,
|
|
ownerNote,
|
|
positionRow,
|
|
positionDivider,
|
|
optionRow,
|
|
facilityOptions.root,
|
|
actions,
|
|
);
|
|
|
|
return {
|
|
root,
|
|
body,
|
|
groupSelect,
|
|
typeSelect,
|
|
ownerNote,
|
|
startFields,
|
|
anchorFields,
|
|
endFields,
|
|
memoField,
|
|
rangeValue,
|
|
rangeWrap,
|
|
positionRow,
|
|
positionDivider,
|
|
optionRow,
|
|
actions,
|
|
facilityOptions,
|
|
primary,
|
|
removeButton,
|
|
resetButton,
|
|
list,
|
|
};
|
|
}
|