/* ============================================================================= * B05_Profile_UI_Drainage_Facility_Fields.ts * 계곡 통과 시설 부속 옵션 서브폼의 입력 조각 — 라벨·숫자칸·격자·소그룹 상자와 * 기슭막이 한 벌(형태·길이·높이·보호공), BOX암거 본체 규격·날개벽 조각. * * 서브폼 본체(B05_Profile_UI_Drainage_Facility)가 700줄 한계에 닿아 분리했다. * 조각은 전부 순수 DOM 생성·값 읽기라 본체의 시설 전환 흐름과 독립적이다 — * 유입·유출·독립 기슭막이(D4)가 같은 조각을 키만 바꿔 쓰는 구조도 그대로다. * ========================================================================== */ import { FORD_MANNING_N, FORD_SLOPE } from "@config/config_frontend"; /* ── 유입구 구조 ──────────────────────────────────────────────────────────── */ /** B06 유입측 구조물 형식 — 조정창 드롭다운과 같은 값 집합(2026-08-29 병합). * 문자열 리터럴로 두어 B05가 B06 모듈에 기대지 않게 한다. */ export type InletStructureKind = "auto" | "revet" | "I" | "L" | "U"; /** 유입구 "구조" 목록 — 조정창의 구조물 형식(자동/기슭막이/집수정 I·ㄴ·ㄷ)을 이 한 * 드롭다운으로 합쳤다(2026-08-29 사용자 지시 5: 일단 리스트를 합치고 중복은 뒤에 뺀다). * 고른 값 하나가 정본 둘을 정한다 — 관 옵션 `inlet_type`(기슭막이/집수정)과 B06 * 유입측 형식(`inlet_structure`). */ export const INLET_KINDS: ReadonlyArray<{ label: string; type: "기슭막이" | "집수정"; structure: InletStructureKind; }> = [ { label: "기슭막이", type: "기슭막이", structure: "revet" }, { label: "집수정", type: "집수정", structure: "auto" }, { label: "자동(규칙)", type: "기슭막이", structure: "auto" }, { label: "집수정 I형", type: "집수정", structure: "I" }, { label: "집수정 ㄴ형", type: "집수정", structure: "L" }, { label: "집수정 ㄷ형", type: "집수정", structure: "U" }, ]; export const inletKindOf = (label: string): (typeof INLET_KINDS)[number] => INLET_KINDS.find((kind) => kind.label === label) ?? INLET_KINDS[0]; /* ── 물넘이·세월교 개략 단면 ────────────────────────────────────────────── */ /** 월류 단면 한 벌 — 설계유량을 흘리는 데 **필요한** 수심·단면적과 그때의 유속. */ export interface FordSection { depthM: number; areaM2: number; velocityMs: number; } /** 물넘이(노면 개수로)의 개략 단면 — 설계유량과 월류 폭으로 필요 수심을 되짚는다. * * 광폭 직사각형(B ≫ h)으로 보면 Manning을 h에 대해 바로 풀 수 있다: * Qd = B·h·(1/n)·h^(2/3)·√S → h = (Qd·n / (B·√S))^(3/5) * 다만 실제 동수반경은 R = A/P (P = B + 2h)로 h보다 작아, 광폭 근사값을 그대로 쓰면 * 통수능이 설계유량에 조금 못 미친다. 그래서 그 값을 출발점 삼아 실제 R로 몇 번 * 되짚어 **B·h·V(h) = Qd**가 되는 h로 수렴시킨다. * * n·S는 실무 수리계산서 역산값이다(`config_frontend.FORD_MANNING_N`·`FORD_SLOPE`, * 2026-08-17 확인). 폭 B는 지식DB에 수치 근거가 없어 사용자가 넣는다 — 프로그램이 * 지어내지 않는다. 유량이나 폭이 없으면 null(표시할 값 없음)이다. * * ⚠ 여기서 내는 수심은 **필요 최소 수심**이다. 실무 수리계산서가 적는 수심은 설계자가 * 가정한 값이고(울진1: B=20m·h=0.27m → 통수능 41.2㎥/s ≫ Qd 2.54㎥/s), 그 시트는 * 가정 단면의 통수능이 설계유량을 넘는지 검증하는 방식이다. 둘은 의미가 다르다. */ export function fordSection(designFlowM3s: number | null, widthM: number): FordSection | null { if (!designFlowM3s || designFlowM3s <= 0 || !Number.isFinite(widthM) || widthM <= 0) return null; const slopeRoot = Math.sqrt(FORD_SLOPE); const velocityAt = (depth: number): number => (1 / FORD_MANNING_N) * Math.pow((widthM * depth) / (widthM + 2 * depth), 2 / 3) * slopeRoot; // 광폭 근사 출발값 — 실제 R보다 크게 잡히므로 아래 반복이 조금씩 키워 수렴한다. let depth = Math.pow((designFlowM3s * FORD_MANNING_N) / (widthM * slopeRoot), 3 / 5); for (let index = 0; index < 20; index += 1) { const next = designFlowM3s / (widthM * velocityAt(depth)); if (Math.abs(next - depth) < 1e-6) { depth = next; break; } depth = next; } return { depthM: depth, areaM2: widthM * depth, velocityMs: velocityAt(depth) }; } /* ── 폼 조각 ────────────────────────────────────────────────────────────── */ export function labeled(text: string, input: HTMLElement): HTMLLabelElement { const wrapper = document.createElement("label"); wrapper.className = "b05-route__field"; const caption = document.createElement("span"); caption.textContent = text; wrapper.append(caption, input); // 묶음(스텝 등)을 담으면 라벨의 암묵 대상이 첫 버튼이 된다 — 안쪽 입력을 명시로 // 가리켜 라벨 hover·클릭이 버튼으로 새지 않게 한다(2026-08-29 사용자 보고). const inner = input.querySelector?.("input, select, textarea") as HTMLElement | null; if (inner?.id) wrapper.htmlFor = inner.id; return wrapper; } export function numberInput(step: string, min = "0", placeholder = ""): HTMLInputElement { const input = document.createElement("input"); input.type = "number"; input.step = step; input.min = min; if (placeholder) input.placeholder = placeholder; return input; } /** * 숫자 칸 + [-][+] 한 묶음(2026-08-29 사용자 지시 2) — 조정창처럼 눈금으로도 * 고칠 수 있게 한다. 래퍼 여백은 0이라 기존 격자 레이아웃을 흔들지 않는다. * 버튼은 값을 고친 뒤 input·change를 모두 울려 기존 리스너(연동·저장)가 그대로 탄다. */ /** 스텝 묶음 입력에 붙일 일련번호 — 라벨이 가리킬 대상을 명시하는 데 쓴다. */ let stepperSeq = 0; /** `decimals` — 표시 자릿수. 련·각도처럼 정수로 세는 칸은 0을 준다(2026-08-30 사용자: * 세월교 상세도 같은 양식으로 맞추되 "1.0련"·"45.0°"로 보이면 안 된다). */ export function stepper(input: HTMLInputElement, stepM: number, decimals = 1): HTMLElement { const wrap = document.createElement("div"); wrap.className = "b05-structure__stepper"; // 라벨이 이 입력을 가리키게 id를 붙인다. 없으면