- 새 파일 = B08_Quantity_Summary_Table · Material_Tables · Earthwork_Tables · Hydro_Table · Milestone_Table - 화면 UI 파일 = 불러오기 · 그리기만 · 옛 B08 시험 = 옮긴 글 자리 따라 읽는 파일 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
126 lines
6.0 KiB
TypeScript
126 lines
6.0 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 (읽기만)
|
|
* 머리 두 줄 = 조합 구분(경로 첫 칸) · 조합 이름(나머지 경로) — 사본 manifest 경로(집계 응답 `linked`)
|
|
* 조합 · 측점 = 프로젝트 안 사본이 있는 것만(`linkedOnly` · PLAN 143-1) — 미연결 시설은 그 측점
|
|
* 줄만 측점 칸 왼쪽 노랑 램프(값 빈칸 · 서버 `unlinked` · PLAN 154-1 · 198-1)
|
|
* · 램프 호버(공용 `ui_template_hover_info`) = 그 측점 빈 조합 칸(160-2 · 화면만)
|
|
* · 시설이 모두 조합에 이은 측점 줄 = 같은 자리 초록 램프(호버 「모두 연결됨」 · 247-1)
|
|
* 횡단배수 열 · 칸 = GET …/quantity/summary(서버 단독 · PLAN 98 · 120) — 머리 구조물 · 형식 · 규격
|
|
* 3단 + 단위 줄 · 「관 공」 맨 윗 머리 = 관에 이은 조합 경로(있으면) · 서버가 펼친 종류(`types`)는
|
|
* 조합 열에서 뺌 · 다른 구조물 열은 조합 그대로
|
|
* 열 폭 = 맨 아래 단 글(+ 칸 값)만 잼 · 위 단 머리는 줄바꿈(`B08_Quantity_UI_Page.css` · 120-2)
|
|
* 줄을 누르면 그 측점의 구조물도 모달(PLAN 95-3 · `B08_Quantity_UI_Drawing` 은 누를 때 `import()`)
|
|
* 미연결 시설 = 모달 맨 위 「조합 연결」 → 바로 저장 → 탭 몸통을 새로 그림(154-2)
|
|
* 커플링 열 = 좌측 패널 1본 길이로 바로 다시 셈(관마다 길이 `pipes` · `recountCouplings` · PLAN 122)
|
|
* 자료 읽기 · 표 짜기 = `B08_Quantity_Summary_Table`(화면 없음 · 인쇄 미리보기도 씀 · 302-3)
|
|
* ========================================================================== */
|
|
|
|
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import { createCard, showToast } from "@ui/ui_template_elements";
|
|
import { createDataTable } from "@ui/ui_template_data_table";
|
|
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
|
import { combosAt, unlinkedAt } from "./B08_Quantity_Summary_Data";
|
|
import {
|
|
LAMP_CLASS,
|
|
loadSummary,
|
|
summaryTable,
|
|
type SummaryView,
|
|
} from "./B08_Quantity_Summary_Table";
|
|
import type { SheetsRequest } from "./B08_Quantity_UI_Drawing";
|
|
import { footNote } from "./B08_Quantity_UI_Foot";
|
|
import { onOptionsChange } from "./B08_Quantity_UI_Options";
|
|
|
|
function L(key: keyof typeof ui_locales): string {
|
|
return ui_locales[key][currentLanguageIndex];
|
|
}
|
|
|
|
function note(text: string): HTMLElement {
|
|
const node = document.createElement("p");
|
|
node.className = "ui-general-block__empty";
|
|
node.textContent = text;
|
|
return node;
|
|
}
|
|
|
|
function buildTable(
|
|
view: SummaryView,
|
|
onRowClick: (station: number, label: string) => void,
|
|
): HTMLElement {
|
|
const { options, labels, missing } = summaryTable(view);
|
|
const table = createDataTable({
|
|
...options,
|
|
onRowClick: (i) => onRowClick(view.stations[i], labels[i]),
|
|
fillViewport: true,
|
|
});
|
|
table.root.dataset.b08Summary = "table";
|
|
// 줄을 다시 그려도(보이는 줄만) 떠 있는 root 하나에 걸어 둠 — 줄 번호 = 줄 `data-i`
|
|
attachHoverInfo(
|
|
table.root,
|
|
(cell) => {
|
|
const why = missing[Number(cell.closest("tr")?.dataset.i)];
|
|
return why
|
|
? `${L("B08_Quantity_Summary_LampTip")} — ${why}`
|
|
: L("B08_Quantity_Summary_LampOk");
|
|
},
|
|
`td.${LAMP_CLASS}`,
|
|
);
|
|
return table.root;
|
|
}
|
|
|
|
/** 행 누름 — 카드 · CAD 묶음은 이때 처음 받음. */
|
|
async function openSheets(request: SheetsRequest): Promise<void> {
|
|
try {
|
|
const { openStructureSheets } = await import("./B08_Quantity_UI_Drawing");
|
|
openStructureSheets(request);
|
|
} catch (error) {
|
|
showToast(`${L("B08_Quantity_Drawing_Failed")} — ${String(error)}`, "error");
|
|
}
|
|
}
|
|
|
|
/** 탭 몸통을 그림 — 불러오는 동안 한 줄 · 실패하면 까닭 한 줄. */
|
|
export async function renderStructureSummary(host: HTMLElement, projectId: string): Promise<void> {
|
|
host.replaceChildren(note(L("B08_Quantity_Summary_Loading")));
|
|
try {
|
|
const view = await loadSummary(projectId);
|
|
const { stations, things, linked, describe, saved } = view;
|
|
const summary = view.live();
|
|
const parts: HTMLElement[] = [];
|
|
if (view.error) parts.push(note(`${L("B08_Quantity_Summary_Failed")} — ${view.error}`));
|
|
if (summary?.notes.length) parts.push(footNote(summary.notes.join(" · "))); // 236-1 한 줄
|
|
if (!view.columns.length && !summary?.columns.length)
|
|
parts.push(note(L("B08_Quantity_Summary_NoCombo")));
|
|
const draw = (): HTMLElement =>
|
|
stations.length
|
|
? buildTable(view, (station, label) =>
|
|
openSheets({
|
|
projectId,
|
|
station: label,
|
|
combos: combosAt(things, station),
|
|
linked,
|
|
describe,
|
|
facilities: unlinkedAt(saved?.facilities ?? [], station),
|
|
// 새 몸통에 다시 그림 — 옛 몸통은 떨어져 옵션 듣기가 스스로 빠짐(겹침 없음)
|
|
onLinked: () => {
|
|
const fresh = document.createElement("div");
|
|
host.replaceWith(fresh);
|
|
void renderStructureSummary(fresh, projectId);
|
|
},
|
|
}),
|
|
)
|
|
: note(L("B08_Quantity_Summary_NoStation"));
|
|
let table = draw();
|
|
host.replaceChildren(createCard({ body: [table, ...parts] })); // 공용 카드 안(토공 표와 같은 꼴 · 143-3) · 안내 · 경고 = 표 아래(202-1)
|
|
onOptionsChange(host, () => {
|
|
const next = draw();
|
|
table.replaceWith(next);
|
|
table = next;
|
|
});
|
|
} catch (error) {
|
|
const reason = error instanceof Error ? error.message : String(error);
|
|
host.replaceChildren(note(`${L("B08_Quantity_Summary_Failed")} — ${reason}`));
|
|
}
|
|
}
|