Files
Aislo/B08_Quantity/B08_Quantity_UI_Hydro.ts
T
eomsangdonandClaude Opus 5.5 5f056ec661 feat(b08): 4탭 홍수량 계산서 — 배수지점 측점 보조 탭 · 견본 모양 한 장 · 서버 계산(PLAN 62-2 · 62-3)
- 측점 보조 탭 = 저장된 pipe_points 측점만 읽음 · 입력은 이 탭 자기 기본값(B05/B06 · 배수유역 값과 안 이음)
- 1-1 합리식(Qd = Q × 할증) · 1-2 Kirpich 도달시간(최소 5분) · 1-3 rt = (R24/24)·(24/T)^0.557
- 2 단면검토 — 관 단면 그림 · 단면적 · 윤변 · 경심 · 경사 · n · Manning 유속 · Q = A·V · OK/NG 점선 상자 · 유출율
- 선택지(할증 2.0/1.5/1.2 · 만관/240°/유효 70 % · 관 종류별 n) · 기본값 한 파일(B08_Quantity_Hydro_Defaults.py)
- 계산 = 서버 단독(B08_Quantity_Engine_Hydro · /quantity/hydro · /quantity/hydro/calc · 최신 응답만) · 입력은 캐시만 · 어떤 파일에도 안 씀
- 흰 종이 계산서(입력 빨강 · 계산 파랑 · 주요 입력 노랑) · 쪽 표시 · 시험 test_62_2_b08_hydro_engine.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
2026-10-06 18:15:29 +09:00

136 lines
4.9 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Hydro.ts
* 4탭 수리집수면적표 — 배수지점 측점마다 보조 탭 · 한 장 = 홍수량 계산서(PLAN 62-2 · 62-3).
*
* 측점 목록만 저장된 관 지점(pipe_points)에서 · 입력은 이 탭 자기 기본값(B05/B06 값과 안 이음)
* 계산 = 서버 단독(POST …/quantity/hydro/calc · 최신 응답만 채택) · 입력은 캐시(sessionStorage)만
* 어디에도 쓰지 않음 — 배수유역 · B05/B06 파일 그대로.
* ========================================================================== */
import "./B08_Quantity_UI_Hydro.css";
import { API_BASE_URL } from "@config/config_frontend";
import { createButton } from "@ui/ui_template_elements";
import { readState, writeState } from "../A00_Common/b_page_state";
import { stationLabel } from "../common_util/common_util_station";
import { stationInterval } from "./B08_Quantity_UI_Summary";
import {
buildHydroSheet,
type HydroInputs,
type HydroOptions,
type HydroResult,
} from "./B08_Quantity_UI_Hydro_Sheet";
interface HydroStation {
chainage_m: number;
facility: string;
}
type Drafts = Record<string, Partial<HydroInputs>>;
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
credentials: "include",
headers: { "Content-Type": "application/json" },
...init,
});
const body = (await response.json().catch(() => null)) as { detail?: unknown } | null;
if (!response.ok) {
const detail = body?.detail;
throw new Error(typeof detail === "string" ? detail : `HTTP ${response.status}`);
}
return body as T;
}
function note(text: string): HTMLElement {
const p = document.createElement("p");
p.className = "ui-general-block__empty";
p.textContent = text;
return p;
}
const keyOf = (station: HydroStation): string => station.chainage_m.toFixed(3);
export async function renderHydro(host: HTMLElement, projectId: string): Promise<void> {
host.replaceChildren(note("홍수량 계산서를 불러오는 중…"));
let sheet: { stations: HydroStation[] } & HydroOptions;
let interval = 20;
try {
[sheet, interval] = await Promise.all([
request<{ stations: HydroStation[] } & HydroOptions>(`/projects/${projectId}/quantity/hydro`),
stationInterval(projectId).catch(() => 20),
]);
} catch (error) {
host.replaceChildren(note(`홍수량 계산서를 불러오지 못했습니다 — ${String(error)}`));
return;
}
if (!sheet.stations.length) {
host.replaceChildren(
note("저장된 배수지점(관 지점)이 없습니다 — 배수유역 · 종단에서 관을 저장하세요."),
);
return;
}
const drafts: Drafts = readState<Drafts>("b08-hydro", projectId) ?? {};
const tabs = document.createElement("div");
tabs.className = "b08-hydro__tabs";
tabs.setAttribute("role", "tablist");
const page = document.createElement("div");
host.replaceChildren(tabs, page);
let seq = 0;
const open = (index: number): void => {
const station = sheet.stations[index];
tabs.replaceChildren(
...sheet.stations.map((s, i) => {
const button = createButton({
label: stationLabel(s.chainage_m, interval),
variant: i === index ? "filled" : "ghost",
size: "sm",
onClick: () => i !== index && open(i),
});
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", String(i === index));
button.dataset.b08HydroStation = String(s.chainage_m);
button.title = s.facility;
return button;
}),
);
const draw = (inputs: HydroInputs): void => {
const view = buildHydroSheet(
stationLabel(station.chainage_m, interval),
{ index: index + 1, total: sheet.stations.length },
inputs,
sheet,
(next) => {
drafts[keyOf(station)] = next;
writeState("b08-hydro", drafts, projectId);
// 단면 가정 · 관 종류는 그림 · 식이 바뀜 — 장을 다시 그림 · 그 밖은 값만
if (next.section !== inputs.section || next.pipe_kind !== inputs.pipe_kind) draw(next);
else void calc(next, view.show);
},
);
page.replaceChildren(view.root);
void calc(inputs, view.show);
};
draw({ ...sheet.defaults, ...drafts[keyOf(station)] } as HydroInputs);
};
const calc = async (
inputs: HydroInputs,
show: (r: HydroResult | null, message?: string) => void,
): Promise<void> => {
const mine = ++seq;
try {
const result = await request<HydroResult>(`/projects/${projectId}/quantity/hydro/calc`, {
method: "POST",
body: JSON.stringify(inputs),
});
if (mine === seq) show(result);
} catch (error) {
if (mine === seq) show(null, `계산하지 못했습니다 — ${String(error)}`);
}
};
open(0);
}