Files
Aislo/B08_Quantity/B08_Quantity_UI_Drawing.ts
T
eomsangdonandClaude Opus 5.5 0cc7ff67eb feat(B08): 2탭 구조물도 — 집계표 조합마다 보조 탭 · M02 도면(도각) + 면적산출 카드 보기 전용
- 새 B08_Quantity_UI_Drawing.ts · .css — 조합 목록 · 차례 = 집계표와 같음(B08_Quantity_Summary_Data.summaryColumns)
- 한 장 = M02 항목 카드(createItemCards) 그대로 · 조합 원본 층 readTemplate 읽기만 · editable false · [저장] · [수정] 없음
- 안내 글 — 조합 없음 · M02 에 없는 조합 · 도번 없는 조합
- B08_Quantity_UI_Page.ts 2탭에 붙이는 두 줄 · locale_b4 글 넷
- 시험 test_61_2_b08_structure_sheets(탭 붙음 · 읽기만 · 같은 열 · 글 키)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
2026-10-06 18:17:41 +09:00

130 lines
5.4 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Drawing.ts
* 2탭 구조물도(PLAN 61-2) — 집계표 열(저장된 구조물 조합)마다 보조 탭 · 한 장 = 그 조합의
* M02 도면(도각 포함 · 보기 전용 CAD) + 면적산출 — M02 항목 카드(`M02_MasterTemplete_ItemCards`) 그대로.
*
* 조합 목록 · 차례 = 집계표와 같음(`B08_Quantity_Summary_Data`)
* 읽기만 — 조합 원본 층(`readTemplate`)을 읽고 카드는 [저장] · [수정] 없음 · B05/B06 자료도 읽기만
* 카드 · CAD · 스프레드시트는 이 탭을 열 때만 `import()`
* ========================================================================== */
import "./B08_Quantity_UI_Drawing.css";
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton } from "@ui/ui_template_elements";
import { loadRows as loadPickerRows } from "@ui/ui_template_item_picker";
import { layerOf, pathOf, rowId, type PickerRow } from "@ui/ui_template_item_picker_data";
import { readTemplate } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
import type { ItemSource } from "../M02_MasterTemplete/M02_MasterTemplete_ItemSource";
import { fetchStructures } from "../B05_Profile/B05_Profile_Api_Structures";
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
import { summaryTooltip } from "../common_util/common_util_summary_item";
import {
comboId,
summaryColumns,
type ComboColumn,
type ComboLabel,
} from "./B08_Quantity_Summary_Data";
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;
}
/** 집계표와 같은 열 + 열마다 M02 줄(없으면 M02 에 없는 조합). */
async function loadColumns(
projectId: string,
): Promise<{ columns: ComboColumn[]; rows: Map<string, PickerRow> }> {
const [structures, pickerRows, pipes] = await Promise.all([
fetchStructures(projectId),
loadPickerRows(),
// 배수 분석 전이면 관이 없음 — 구조물만으로
fetchDetailPipePoints(projectId).then(
(response) => response.pipe_points,
() => [],
),
]);
const rows = new Map(pickerRows.map((row) => [rowId(row), row]));
const describe = (item: Parameters<typeof comboId>[0]): ComboLabel | null => {
const row = rows.get(comboId(item));
if (!row) return null;
const [group = "", ...rest] = pathOf(row);
return { group, name: rest.join(" ") || group || row.key };
};
return { columns: summaryColumns([...structures.structures, ...pipes], describe), rows };
}
/** 탭 몸통을 그림 — 불러오는 동안 한 줄 · 실패하면 까닭 한 줄. */
export async function renderStructureSheets(host: HTMLElement, projectId: string): Promise<void> {
host.replaceChildren(note(L("B08_Quantity_Drawing_Loading")));
try {
const [{ columns, rows }, { createItemCards }] = await Promise.all([
loadColumns(projectId),
import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards"),
]);
if (!host.isConnected) return; // 그새 다른 탭으로 옮김
if (!columns.length) return void host.replaceChildren(note(L("B08_Quantity_Summary_NoCombo")));
let row: PickerRow | null = null;
const source = (): ItemSource => {
const layer = row ? layerOf(row) : "system";
return {
layer,
projectId: null,
read: (part, column) => readTemplate(layer, part, column, null),
};
};
const cards = createItemCards({
source,
editable: () => false,
onEdit: () => undefined,
warnOnFail: () => false, // 못 읽으면 카드에 「없음」
});
const tabs = document.createElement("div");
tabs.className = "b08-quantity__tabs b08-drawing__tabs";
tabs.setAttribute("role", "tablist");
const notice = document.createElement("div");
const sheet = document.createElement("div");
sheet.className = "b08-drawing__cards";
sheet.append(cards.root);
const select = (picked: ComboColumn): void => {
tabs.replaceChildren(
...columns.map((column) => {
const button = createButton({
label: column.group ? `${column.group} ${column.name}` : column.name,
variant: column === picked ? "filled" : "ghost",
size: "sm",
onClick: () => column !== picked && select(column),
});
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", String(column === picked));
button.dataset.b08Combo = column.id;
button.title = summaryTooltip(column.item);
if (column.missing) button.classList.add("is-missing");
return button;
}),
);
row = rows.get(picked.id) ?? null;
if (!row?.열) {
notice.replaceChildren(note(L("B08_Quantity_Drawing_Missing")));
return void cards.load(null);
}
notice.replaceChildren(...(row.도번 ? [] : [note(L("B08_Quantity_Drawing_NoDrawing"))]));
void cards.load({ id: row.열, label: picked.name });
};
host.replaceChildren(tabs, notice, sheet);
select(columns[0]);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
host.replaceChildren(note(`${L("B08_Quantity_Drawing_Failed")} — ${reason}`));
}
}