/* ============================================================================= * B08_Quantity_UI_Summary.ts * 1탭 구조물 집계표(PLAN 61-1) — 줄 = 측점 · 열 = 저장된 구조물 조합 · 칸은 아직 비움. * 표는 공용 많은 자료 표(`ui_template_data_table` · PLAN 70-2) — 머리 · 측점 열 고정. * * 구조물 = GET …/route/structures · 배관 = GET …/drainage/pipe-points (읽기만) * 머리 두 줄 = 조합 구분(M02 경로 첫 칸) · 조합 이름(나머지 경로) — `GET /api/m02/items?all=1` * ========================================================================== */ import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; import { loadRows as loadPickerRows } from "@ui/ui_template_item_picker"; import { pathOf, rowId } from "@ui/ui_template_item_picker_data"; import { fetchStructures } from "../B05_Profile/B05_Profile_Api_Structures"; import { fetchLatestRoute, readRouteLatestCache } from "../B05_Profile/B05_Profile_Api_Fetch"; import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; import { createDataTable } from "@ui/ui_template_data_table"; import { stationLabel } from "../common_util/common_util_station"; import { summaryTooltip } from "../common_util/common_util_summary_item"; import { comboId, summaryColumns, summaryStations, type ComboColumn, type ComboLabel, } from "./B08_Quantity_Summary_Data"; function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } /** 측점 간격 — 노선 설정값 · 못 읽으면 20m. */ export async function stationInterval(projectId: string): Promise { const latest = readRouteLatestCache(projectId) ?? (await fetchLatestRoute(projectId)); return latest.route_params?.station_interval_m || 20; } function note(text: string): HTMLElement { const node = document.createElement("p"); node.className = "ui-general-block__empty"; node.textContent = text; return node; } /** 머리 글 길이에 맞춘 열 폭 — 칸은 비어 있어 글이 폭을 정함. */ const widthOf = (label: string): number => Math.min(200, Math.max(96, label.length * 13 + 24)); function buildTable(stations: number[], columns: ComboColumn[], interval: number): HTMLElement { const table = createDataTable({ columns: [ { key: "station", label: L("B08_Quantity_Summary_Station"), width: 96 }, ...columns.map((column) => ({ key: column.id, label: column.name, group: [column.group || L("B08_Quantity_Summary_MissingGroup")], title: summaryTooltip(column.item), className: column.missing ? "is-missing" : undefined, width: widthOf(column.name), })), ], // 칸은 아직 비움 — 사용자가 채울 자리라 「-」 대신 빈칸 rows: stations.map((station) => ({ station: stationLabel(station, interval) })), frozen: 1, empty: "", caption: L("B08_Quantity_Tab_StructureSummary"), }); table.root.dataset.b08Summary = "table"; return table.root; } /** 탭 몸통을 그림 — 불러오는 동안 한 줄 · 실패하면 까닭 한 줄. */ export async function renderStructureSummary(host: HTMLElement, projectId: string): Promise { host.replaceChildren(note(L("B08_Quantity_Summary_Loading"))); try { const [structures, pickerRows, pipes, interval] = await Promise.all([ fetchStructures(projectId), loadPickerRows(), // 배수 분석 전이면 관이 없음 — 구조물만으로 그림 fetchDetailPipePoints(projectId).then( (response) => response.pipe_points, () => [], ), stationInterval(projectId).catch(() => 20), ]); const m02 = new Map(pickerRows.map((row) => [rowId(row), row])); const describe = (item: Parameters[0]): ComboLabel | null => { const row = m02.get(comboId(item)); if (!row) return null; const [group = "", ...rest] = pathOf(row); return { group, name: rest.join(" ") || group || row.key }; }; const things = [...structures.structures, ...pipes]; const stations = summaryStations(things); const columns = summaryColumns(things, describe); const parts: HTMLElement[] = []; if (!columns.length) parts.push(note(L("B08_Quantity_Summary_NoCombo"))); parts.push( stations.length ? buildTable(stations, columns, interval) : note(L("B08_Quantity_Summary_NoStation")), ); host.replaceChildren(...parts); } catch (error) { const reason = error instanceof Error ? error.message : String(error); host.replaceChildren(note(`${L("B08_Quantity_Summary_Failed")} — ${reason}`)); } }