Files
Aislo/B08_Quantity/B08_Quantity_UI_Summary.ts
T
eomsangdonandClaude Sonnet 5.5 361ce51659 refactor(B08·B09): 자체 변종 → 부품 상자 — 탭 줄 · 분할 고르기 · 배지 칩 · 도구줄 · 떠 있는 판 · 보조 글 (PLAN 553-3)
- 위 · 보조 탭 줄 = 부품 탭(fillTabs · 밑줄 · 알약) · B09 좌측 스위치 = 분할 고르기
- 연결 칩 · 검사 탭 이름 · 끌기 태그 = 배지 · 이정표 도구 · 머리 줄 · 저장 줄 = 도구줄 · 구간 고침 · 행 고름 창 = 떠 있는 판
- 안내 · 빈 줄 · 상태 글 = 보조 글 · 운반 칸 = 카드 · 인쇄 미리보기 좌측 줄 = B09 dock 한 벌 · 쪽 체크 = 라디오 · 체크 부품
- 시험 흉내 틀 · 문구 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:33:35 +09:00

124 lines
5.9 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 { createText } from "@ui/ui_template_text";
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 {
return createText(text, "muted", 14);
}
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}`));
}
}