/* ============================================================================= * M02_MasterTemplete_Hopyo_Side.ts * 좌측 「일위대가 실무자료」 컨테이너(PLAN 324-4 · 359-1 · 일위대가 사전조합 아래) — 층 = 구조물 조합 층(화면 층) · * [파일 업로드](구조물 조합 층 단추 꼴 `m02-layers__btn` · 숨은 file 입력 .xls · .xlsx · 쓰기 권한 없으면 꺼짐) + * 파일 줄 → 누르면 바로 엑셀 보기 창(329 · `M02_MasterTemplete_Hopyo_Sheet` 늦게 불러옴 · 지우기 · 검산 · 셈 중은 창 머리) · * 고름 표시 = 그 파일 창이 열린 동안 · 올린 직후도 엑셀 보기 창 · * 파일 목록 아래 [별칭 장부](338-3) → 본 칸 장부 화면(`M02_MasterTemplete_Hopyo_Ledger`). * ========================================================================== */ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { listHopyo, pageScope, uploadHopyo, type HopyoFile } from "./M02_MasterTemplete_Hopyo_Api"; import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplete_Hopyo_Ledger"; export const HOPYO_TITLE = "일위대가 실무자료"; export interface HopyoSectionOptions { /** 이 층 쓰기 권한 — 없으면 올리기 꺼짐 · 엑셀 보기 창 보기만 */ canWrite: boolean; /** 저장 안 한 고침을 버릴지 확인 — 취소면 false */ confirmLeave: () => Promise; /** 메인 칸에 화면을 띄움 */ show: (label: string, node: HTMLElement) => (dirty: boolean) => void; /** 화면을 열었음 — 좌측 다른 고름 표시를 지움 */ onOpen: () => void; } export interface HopyoSection { title: string; body: HTMLElement; /** 다른 화면이 열렸으면 고름 표시를 지움 */ paint: () => void; refresh: () => Promise; } const why = (error: unknown): string => (error instanceof Error ? error.message : String(error)); export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection { const scope = pageScope(); const list = el("div", { className: "m02-side__items ui-sidebar-list" }); const input = el("input", { attrs: { type: "file", accept: ".xls,.xlsx", "data-hopyo-file": "" }, }) as HTMLInputElement; input.hidden = true; // 구조물 조합 층 단추(`TreePanel_Roots` [마스터])와 같은 꼴 — 보통 크기 · 같은 클래스 const upload = createButton({ label: "파일 업로드", variant: "filled", disabled: !opt.canWrite, onClick: () => input.click(), }); upload.classList.add("m02-layers__btn"); upload.title = opt.canWrite ? "설계내역서 엑셀(일위대가 시트)" : "이 층 쓰기 권한 없음"; const ledger = el("button", { className: "m02-side__item ui-sidebar-row", text: LEDGER_TITLE, attrs: { type: "button", title: LEDGER_HINT, "data-hopyo-ledger-open": "" }, }); ledger.addEventListener("click", () => void showLedger()); const body = el("div", { className: "m02-hopyo-side", children: [el("div", { className: "m02-side__row", children: [upload, input] }), list, ledger], }); let files: HopyoFile[] = []; let open: { key: string; view: HTMLElement } | null = null; let sheetKey: string | null = null; // 엑셀 보기 창이 열린 파일 let seq = 0; input.addEventListener("change", () => { const file = input.files?.[0]; input.value = ""; if (!file) return; upload.disabled = true; uploadHopyo(scope, file) .then(async (got) => { const wrong = got.어긋남 ? ` · 검산 어긋남 ${got.어긋남}` : ""; showToast(`올림 — 호표 ${got.호표수}${wrong}`, got.어긋남 ? "warning" : "success"); await refresh(); await showSheet(got); }) .catch((error) => showToast(why(error), "error")) .finally(() => (upload.disabled = !opt.canWrite)); }); function paintList(): void { ledger.classList.toggle("is-active", open?.key === LEDGER_TITLE); list.replaceChildren( ...(files.length ? files.map((f) => { const row = el("button", { className: `m02-side__item ui-sidebar-row${sheetKey === f.키 ? " is-active" : ""}`, text: `${f.파일} · 호표 ${f.호표수}`, attrs: { type: "button", title: `${f.키} · ${f.올린때}`, "data-hopyo-key": f.키 }, }); row.addEventListener("click", () => void showSheet(f)); return row; }) : [el("p", { className: "m02-side__empty", text: "올린 실무자료 없음" })]), ); } /** 파일 줄 누름 · 올린 직후 = 바로 엑셀 보기 창(본 칸은 그대로 · 열 때 불러옴) */ async function showSheet(file: HopyoFile): Promise { const sheet = await import("./M02_MasterTemplete_Hopyo_Sheet").catch((error: unknown) => { showToast(why(error), "error"); return null; }); if (!sheet) return; sheetKey = file.키; paintList(); sheet.openHopyoSheet({ scope, key: file.키, 파일: file.파일, canWrite: opt.canWrite, onClose: () => { if (sheetKey === file.키) sheetKey = null; paintList(); }, onDeleted: () => void refresh(), }); } async function showLedger(): Promise { if (open?.key === LEDGER_TITLE && open.view.isConnected) return; if (!(await opt.confirmLeave())) return; const view = buildHopyoLedger({ scope, canWrite: opt.canWrite }); opt.show(`${HOPYO_TITLE} — ${LEDGER_TITLE}`, view); opt.onOpen(); open = { key: LEDGER_TITLE, view }; paintList(); } async function refresh(): Promise { const mine = ++seq; try { const got = await listHopyo(scope); if (mine === seq) files = got; } catch (error) { if (mine === seq) files = []; showToast(why(error), "error"); } if (mine === seq) paintList(); } const paint = (): void => { if (open && !open.view.isConnected) { open = null; paintList(); } }; void refresh(); return { title: HOPYO_TITLE, body, paint, refresh }; }