- M02_Item_Snapshot.linked_snapshots — 지정 ∩ 같은 항목 사본 {구조물 id: 항목 · 폴더 · 부분 · 경로} · B08 · B09 공용
- 집계표 서버 — 잇기 목록을 사본으로 거름 · 조합 모두 없는 칸은 안 셈 · 세월교 날 잇기 · 관 몫 본체 잇기 · 「조합 사본 없는 시설 n곳」 알림 · 응답 linked
- 화면 — 조합 이름 = 사본 경로(층 목록 GET 없음) · 측점 · 조합 열 = 사본 있는 것만 · 구조물도 모달 = 사본만(없으면 사본 없음)
- 시험 — test_98 · 120 · 131 픽스처 잇기 · test_61_2 사본만 · 새 test_143_1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
133 lines
5.3 KiB
TypeScript
133 lines
5.3 KiB
TypeScript
/* =============================================================================
|
|
* B08_Quantity_UI_Drawing.ts
|
|
* 구조물 집계표 행 → 구조물도 모달(PLAN 95-3) — 그 측점에 놓인 조합 전부를 세로로 쌓음.
|
|
* 조합 한 개 = M02 항목 카드(`M02_MasterTemplete_ItemCards`) 그대로 — 도면(도각 포함 · 보기 전용 CAD ·
|
|
* 여러 장이면 장 탭) + 면적산출.
|
|
*
|
|
* 원천 = 그 구조물의 프로젝트 사본만(연결된 사본 · PLAN 143-1) — 없으면 「사본 없음」 · 읽기만
|
|
* 조합 목록 = 집계표와 같음(`B08_Quantity_Summary_Data` `combosAt`)
|
|
* 카드 · CAD · 스프레드시트는 모달을 열 때만 `import()` · 닫을 때 카드를 모두 정리
|
|
* ========================================================================== */
|
|
|
|
import "./B08_Quantity_UI_Drawing.css";
|
|
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import { PART_OF, projectItems } from "@ui/ui_template_item_picker_project";
|
|
import type { ItemCardsHandle } from "../M02_MasterTemplete/M02_MasterTemplete_ItemCards";
|
|
import type { ItemSource } from "../M02_MasterTemplete/M02_MasterTemplete_ItemSource";
|
|
import { summaryTooltip, type SummaryItem } from "../common_util/common_util_summary_item";
|
|
import type { ComboLabel, LinkedSnapshot, StationCombo } 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;
|
|
}
|
|
|
|
export interface SheetsRequest {
|
|
projectId: string;
|
|
/** 모달 제목에 붙는 측점 글 */
|
|
station: string;
|
|
combos: StationCombo[];
|
|
/** 연결된 사본 `{구조물 id: 사본}`(집계 응답 · 143-1) */
|
|
linked: Record<string, LinkedSnapshot>;
|
|
describe: (item: SummaryItem) => ComboLabel | null;
|
|
}
|
|
|
|
/** 사본 폴더 읽기 — 그 부분 파일이 없으면 던짐(카드가 「없음」). */
|
|
function sourceOf(projectId: string, folder: string): ItemSource {
|
|
const items = projectItems(projectId);
|
|
return {
|
|
layer: "project",
|
|
projectId,
|
|
read: async (part) => {
|
|
const doc = await items.read(folder, PART_OF[part]);
|
|
if (doc == null) throw new Error(L("B08_Quantity_Sheets_NoCopy"));
|
|
return { 문서: doc };
|
|
},
|
|
};
|
|
}
|
|
|
|
/** 조합 한 칸 — 제목줄(이름 · 원천) + 안내 + 카드. */
|
|
function comboSection(
|
|
request: SheetsRequest,
|
|
combo: StationCombo,
|
|
createItemCards: typeof import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards").createItemCards,
|
|
handles: ItemCardsHandle[],
|
|
): HTMLElement {
|
|
const label = request.describe(combo.item);
|
|
const name = label
|
|
? [label.group, label.name].filter(Boolean).join(" ")
|
|
: combo.item.name || combo.item.key;
|
|
const copy = combo.copyId ? request.linked[combo.copyId] : undefined;
|
|
const section = document.createElement("section");
|
|
section.className = "b08-sheets__combo";
|
|
section.dataset.b08Combo = combo.id;
|
|
const title = document.createElement("h4");
|
|
title.className = "b08-sheets__title";
|
|
title.textContent = name;
|
|
title.title = summaryTooltip(combo.item);
|
|
section.append(title);
|
|
if (!copy) {
|
|
section.append(note(L("B08_Quantity_Drawing_Missing")));
|
|
return section;
|
|
}
|
|
const source = sourceOf(request.projectId, copy.폴더);
|
|
const from = document.createElement("span");
|
|
from.className = "b08-sheets__from";
|
|
from.textContent = L("B08_Quantity_Sheets_FromCopy");
|
|
title.append(from);
|
|
if (!copy.부분.includes("도면")) section.append(note(L("B08_Quantity_Drawing_NoDrawing")));
|
|
const cards = createItemCards({
|
|
source: () => source,
|
|
editable: () => false,
|
|
onEdit: () => undefined,
|
|
warnOnFail: () => false, // 못 읽으면 카드에 「없음」
|
|
});
|
|
handles.push(cards);
|
|
section.append(cards.root);
|
|
void cards.load({ id: copy.폴더, label: name });
|
|
return section;
|
|
}
|
|
|
|
/** 모달을 엶 — 조합이 없으면 안내 한 줄 · 불러오다 실패하면 까닭 한 줄. */
|
|
export function openStructureSheets(request: SheetsRequest): void {
|
|
const handles: ItemCardsHandle[] = [];
|
|
let closed = false;
|
|
openModal({
|
|
title: `${request.station} ${L("B08_Quantity_Sheets_Title")}`,
|
|
closeLabel: L("Common_Btn_Close"),
|
|
dialogClass: "b08-sheets",
|
|
beforeClose: () => {
|
|
closed = true; // 아직 불러오는 중이면 뒤늦게 카드를 만들지 않음
|
|
handles.forEach((cards) => cards.destroy());
|
|
return true;
|
|
},
|
|
mount: (body) => {
|
|
if (!request.combos.length) return void body.append(note(L("B08_Quantity_Summary_NoCombo")));
|
|
body.append(note(L("B08_Quantity_Drawing_Loading")));
|
|
void (async () => {
|
|
try {
|
|
const { createItemCards } =
|
|
await import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards");
|
|
if (closed) return;
|
|
body.replaceChildren(
|
|
...request.combos.map((combo) =>
|
|
comboSection(request, combo, createItemCards, handles),
|
|
),
|
|
);
|
|
} catch (error) {
|
|
if (closed) return;
|
|
const reason = error instanceof Error ? error.message : String(error);
|
|
body.replaceChildren(note(`${L("B08_Quantity_Drawing_Failed")} — ${reason}`));
|
|
}
|
|
})();
|
|
},
|
|
});
|
|
}
|