/* ============================================================================= * B08_Quantity_UI_Hydro.ts * 4탭 수리집수면적표 — 배수지점 측점마다 보조 탭 · 한 장 = 홍수량 계산서(PLAN 62-2 · 69-4~6). * * 측점 목록만 저장된 관 지점(pipe_points)에서 · 입력은 이 탭 자기 값(B05/B06 값과 안 이음) * 박스암거 = 사각 단면 장(73-11) · 세월교 · 물넘이포장 = 월류 높이 장(73-12) · 그 밖 = 관 장 * 처음 값 = 표준 값 · 측점 고유 값은 빈칸 · 지역 · R24 = 공사지 자료(서버) * 조작 = 캐시(sessionStorage) · [저장] = B08 자기 파일(PUT …/quantity/hydro) · 뒤 캐시 비움 * 계산 = 서버 단독(POST …/quantity/hydro/calc · 최신 응답만 채택) * ========================================================================== */ import "./B08_Quantity_UI_Hydro.css"; import { API_BASE_URL } from "@config/config_frontend"; import { createButton, hideLoadingOverlay, showLoadingOverlay, showToast, } from "@ui/ui_template_elements"; import { clearState, 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; /** 박스암거 · 세월교 · 물넘이포장(관이면 "") */ kind: string; /** 계산서 장 — 시설이 정함(서버 station_rows) */ sheet: HydroInputs["sheet"]; } type Drafts = Record>; type HydroPayload = { stations: HydroStation[]; saved: Drafts } & HydroOptions; async function request(path: string, init: RequestInit = {}): Promise { 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 { host.replaceChildren(note("홍수량 계산서를 불러오는 중…")); let sheet: HydroPayload; let interval = 20; try { [sheet, interval] = await Promise.all([ request(`/projects/${projectId}/quantity/hydro`), stationInterval(projectId).catch(() => 20), ]); } catch (error) { host.replaceChildren(note(`홍수량 계산서를 불러오지 못했습니다 — ${String(error)}`)); return; } if (!sheet.stations.length) { host.replaceChildren( note("저장된 배수지점(관 지점)이 없습니다 — 배수유역 · 종단에서 관을 저장하세요."), ); return; } let drafts: Drafts = readState("b08-hydro", projectId) ?? {}; const inputsOf = (station: HydroStation): HydroInputs => ({ ...sheet.defaults, ...(station.sheet === "pipe" ? {} : sheet.sheet_defaults[station.sheet]), ...sheet.saved[keyOf(station)], ...drafts[keyOf(station)], sheet: station.sheet, }) as HydroInputs; const tabs = document.createElement("div"); tabs.className = "b08-quantity__tabs b08-hydro__tabs"; tabs.setAttribute("role", "tablist"); const save = createButton({ label: "저장", variant: "filled", onClick: () => void saveAll(), }); save.dataset.b08HydroSave = ""; const toolbar = document.createElement("div"); toolbar.className = "b08-hydro__toolbar"; toolbar.append(tabs, save); const page = document.createElement("div"); host.replaceChildren(toolbar, page); let seq = 0; let openIndex = 0; const open = (index: number): void => { openIndex = index; const station = sheet.stations[index]; tabs.replaceChildren( ...sheet.stations.map((s, i) => { const label = stationLabel(s.chainage_m, interval); const button = createButton({ label: s.kind ? `${label} ${s.kind}` : label, 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.kind || s.facility; return button; }), ); const draw = (inputs: HydroInputs): void => { const view = buildHydroSheet( stationLabel(station.chainage_m, interval), 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); }, station.kind, ); page.replaceChildren(view.root); void calc(inputs, view.show); }; draw(inputsOf(station)); }; const calc = async ( inputs: HydroInputs, show: (r: HydroResult | null, message?: string) => void, ): Promise => { const mine = ++seq; try { const result = await request(`/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)}`); } }; /** [저장] — 측점 전부의 지금 입력을 B08 자기 파일로 · 성공하면 캐시를 비우고 저장본으로 다시 엶 */ const saveAll = async (): Promise => { const stations = Object.fromEntries(sheet.stations.map((s) => [keyOf(s), inputsOf(s)])); showLoadingOverlay(); try { const body = await request<{ saved: Drafts }>(`/projects/${projectId}/quantity/hydro`, { method: "PUT", body: JSON.stringify({ stations }), }); sheet.saved = body.saved; drafts = {}; clearState("b08-hydro", projectId); showToast("수리집수면적표를 저장했습니다.", "success"); open(openIndex); } catch (error) { showToast(`저장하지 못했습니다 — ${String(error)}`, "error"); } finally { hideLoadingOverlay(); } }; open(0); }