- 집계표 = createDataTable — 머리 두 줄(조합 구분 · 이름) · 측점 열 고정 · 칸은 빈칸 · 빠진 조합 빨강 · 풍선 글 그대로 - 공용 표 하위 호환 옵션 — empty(빈 값 글) · 열 title · className · 빠진 항목 머리 색 · 틀 폭은 표만큼 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
107 lines
4.6 KiB
TypeScript
107 lines
4.6 KiB
TypeScript
/* =============================================================================
|
|
* 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<number> {
|
|
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<void> {
|
|
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<typeof comboId>[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}`));
|
|
}
|
|
}
|