/* ============================================================================= * B08_Quantity_UI_Drawing.ts * 구조물 집계표 행 → 구조물도 모달(PLAN 95-3) — 그 측점에 놓인 조합 전부를 세로로 쌓음. * 조합 한 개 = M02 항목 카드(`M02_MasterTemplete_ItemCards`) 그대로 — 도면(도각 포함 · 보기 전용 CAD · * 여러 장이면 장 탭) · 일위대가 · 면적산출 = 공용 카드 셋을 한 줄 세 칸(156-4 · 창 = 공용 wide). * * 원천 = 그 구조물의 프로젝트 사본만(연결된 사본 · PLAN 143-1) — 없으면 「사본 없음」 * 카드 [수정](163-3) = 모달 몸통이 사본 전용 편집 화면(고르기 모달과 같은 `createEditView`)으로 바뀜 · * 도면 · 면적산출 = 그 부분 · 일위대가 카드 = 넣을 값만(담은로직 · 자재는 원본 층에서 · 서버 400 · 142-1) · * [뒤로] = 카드 다시 읽음 · 저장했으면 닫을 때 집계표 다시 셈 · 판 다르면 서버 409 그대로 * 조합 목록 = 집계표와 같음(`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 { showToast } from "@ui/ui_template_elements"; import { PART_OF, projectItems, withCopyValues } from "@ui/ui_template_item_picker_project"; import type { EditView } from "@ui/ui_template_item_picker_edit"; import type { LaborDoc } from "../M02_MasterTemplete/M02_MasterTemplete_Labor_Api"; import type { ValueDoc } from "../M02_MasterTemplete/M02_Labor_Values_Model"; import type { EditKind, 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; describe: (item: SummaryItem) => ComboLabel | null; /** 그 측점 시설 칸 전부(154 · 156-1) */ facilities: UnlinkedFacility[]; /** 바로 연결 · 끊기를 저장한 뒤 — 집계표 다시 셈 */ onLinked: () => void; } /** 사본 폴더 읽기 — 그 부분 파일이 없으면 던짐(카드가 「없음」) · * 일위대가 = 사본 `일위대가` · 입력값 · 고른자재 그대로(142-1 고르기 모달과 같은 길 · 161-1). */ 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 }; }, labor: { read: async () => { const [labor, values, picks] = await Promise.all([ items.read(folder, "일위대가") as Promise<{ 줄?: LaborDoc } | null>, items.read(folder, "입력값") as Promise, items.read(folder, "고른자재") as Promise | null>, ]); if (!labor?.줄) throw new Error(L("B08_Quantity_Sheets_NoCopy")); return { 문서: withCopyValues(labor.줄, values, picks) }; }, calc: () => items.calcLabor(folder), }, }; } /** 카드 [수정] — 사본 · 고칠 부분(일위대가 카드 = 넣을 값) · 카드 다시 읽기 */ type OpenEdit = ( copy: LinkedSnapshot, name: string, kind: EditKind | "inputs", reload: () => void, ) => void; /** 조합 한 칸 — 제목줄(이름 · 원천) + 안내 + 카드. */ function comboSection( request: SheetsRequest, combo: StationCombo, createItemCards: typeof import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards").createItemCards, handles: ItemCardsHandle[], openEdit: OpenEdit, ): 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 load = (): void => void cards.load({ id: copy.폴더, label: name }); // 카드 안 [저장]은 없음(자료 자리에 write 없음) — [수정]만 사본 편집 화면으로(163-3) const cards = createItemCards({ source: () => source, editable: () => true, onEdit: (kind) => openEdit(copy, name, kind, load), warnOnFail: () => false, // 못 읽으면 카드에 「없음」 labor: () => openEdit(copy, name, "inputs", load), // 일위대가 카드(161-1) [수정] = 넣을 값만 }); handles.push(cards); section.append(cards.root); load(); return section; } /** 모달을 엶 — 조합이 없으면 안내 한 줄 · 불러오다 실패하면 까닭 한 줄. */ export function openStructureSheets(request: SheetsRequest): void { const handles: ItemCardsHandle[] = []; let closed = false; let edited = false; // 사본을 고쳐 저장함 — 닫을 때 집계표 다시 셈(163-3) let editView: EditView | null = null; let modalBody: HTMLElement | null = null; // 머리 단추 칸(170-2) — 공용 모달은 열 때 단추가 있어야 머리에 붙임 → 빈 칸을 미리 넘김 · 편집 화면에서만 채움 const head = document.createElement("div"); head.className = "ui-modal__actions"; head.hidden = true; const clearHead = (): void => { head.replaceChildren(); head.hidden = true; }; /** 카드 [수정] — 몸통을 사본 편집 화면으로 · [뒤로] = 몸통 되돌리고 그 카드 다시 읽음 */ const openEdit: OpenEdit = (copy, name, kind, reload) => { const body = modalBody; if (!body || editView) return; void (async () => { try { const [{ createEditView }] = await Promise.all([ import("@ui/ui_template_item_picker_edit"), import("@ui/ui_template_item_picker.css"), ]); if (closed) return; const kept = [...body.children]; const host = document.createElement("div"); host.className = "ui-item-picker__edit-host"; const view = createEditView( { kind, row: { key: copy.항목.key, 열: copy.항목.key, 경로: [name] }, info: { 폴더: copy.폴더, 원본: copy.항목 }, items: projectItems(request.projectId), projectId: request.projectId, }, (saved) => { view.destroy(); editView = null; clearHead(); body.replaceChildren(...kept); edited ||= saved; if (saved) reload(); }, ); editView = view; // [← 뒤로] · [저장] = 머리 [닫기] 왼쪽 · 크기 = [닫기](sm 뗌) view.back.classList.remove("ui-btn--sm"); view.save.classList.remove("ui-btn--sm"); head.replaceChildren(view.back, view.save); head.hidden = false; host.append( ...(kind === "inputs" ? [note(L("B08_Quantity_Sheets_InputsOnly"))] : []), view.root, ); body.replaceChildren(host); view.start(); } catch (error) { showToast(`${L("B08_Quantity_Drawing_Failed")} — ${String(error)}`, "error"); } })(); }; openModal({ title: `${request.station} ${L("B08_Quantity_Sheets_Title")}`, closeLabel: L("Common_Btn_Close"), wide: true, // 공용 큰 창(96vw × 94vh · 156-4) dialogClass: "b08-sheets", actions: [head], beforeClose: async () => { if (editView && !(await editView.leave())) return false; // 저장 안 한 고침 — 버릴지 물음 editView?.destroy(); clearHead(); closed = true; // 아직 불러오는 중이면 뒤늦게 카드를 만들지 않음 handles.forEach((cards) => cards.destroy()); if (edited) request.onLinked(); // 사본을 고쳤으면 집계표 다시 셈(연결 저장과 같은 꼴) return true; }, mount: (body, close) => { modalBody = body; 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, openEdit), ), ); } catch (error) { if (closed) return; const reason = error instanceof Error ? error.message : String(error); body.replaceChildren(...linking, note(`${L("B08_Quantity_Drawing_Failed")} — ${reason}`)); } })(); }, }); }