Files
Aislo/B08_Quantity/B08_Quantity_UI_Drawing.ts
T
eomsangdonandClaude Opus 5.5 9e53b866e2 feat(b08): 구조물도 모달 공용 큰 창 · 카드 셋 한 줄 세 칸(156-4)
- 모달 = 공용 wide(96vw × 94vh) · 자기 폭 규칙 지움
- 도면 · 일위대가 · 면적산출 카드 = 한 줄 세 칸(3:2:2) · 카드 높이 큰 창에 맞춤 · 칸 사이 넓힘
- 조합끼리 가름줄
- 시험 test_156_1 덧붙임 · test_61_2 옛 폭 판정을 wide 로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-09 20:40:17 +09:00

160 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B08_Quantity_UI_Drawing.ts
* 구조물 집계표 행 → 구조물도 모달(PLAN 95-3) — 그 측점에 놓인 조합 전부를 세로로 쌓음.
* 조합 한 개 = M02 항목 카드(`M02_MasterTemplete_ItemCards`) 그대로 — 도면(도각 포함 · 보기 전용 CAD ·
* 여러 장이면 장 탭) · 일위대가 · 면적산출 = 공용 카드 셋을 한 줄 세 칸(156-4 · 창 = 공용 wide).
*
* 원천 = 그 구조물의 프로젝트 사본만(연결된 사본 · PLAN 143-1) — 없으면 「사본 없음」 · 읽기만
* 조합 목록 = 집계표와 같음(`B08_Quantity_Summary_Data` `combosAt`)
* 시설이 있으면 맨 위 「조합 연결」 칸(`B08_Quantity_UI_Link` · PLAN 154-2 · 156-1) — 저장 · 끊기면 닫고 다시 셈
* 카드 · 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,
UnlinkedFacility,
} from "./B08_Quantity_Summary_Data";
import { linkSection } from "./B08_Quantity_UI_Link";
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;
/** 그 측점 시설 칸 전부(154 · 156-1) */
facilities: UnlinkedFacility[];
/** 바로 연결 · 끊기를 저장한 뒤 — 집계표 다시 셈 */
onLinked: () => void;
}
/** 사본 폴더 읽기 — 그 부분 파일이 없으면 던짐(카드가 「없음」). */
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"),
wide: true, // 공용 큰 창(96vw × 94vh · 156-4)
dialogClass: "b08-sheets",
beforeClose: () => {
closed = true; // 아직 불러오는 중이면 뒤늦게 카드를 만들지 않음
handles.forEach((cards) => cards.destroy());
return true;
},
mount: (body, close) => {
const linking = request.facilities.length
? [
linkSection(request.projectId, request.facilities, () => {
closed = true;
handles.forEach((cards) => cards.destroy());
close();
request.onLinked();
}),
]
: [];
if (!request.combos.length)
return void body.append(
...linking,
...(linking.length ? [] : [note(L("B08_Quantity_Summary_NoCombo"))]),
);
body.append(...linking, note(L("B08_Quantity_Drawing_Loading")));
void (async () => {
try {
const { createItemCards } =
await import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards");
if (closed) return;
body.replaceChildren(
...linking,
...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(...linking, note(`${L("B08_Quantity_Drawing_Failed")} — ${reason}`));
}
})();
},
});
}