/* ============================================================================= * M02_MasterTemplete_AreaCellPick.ts * 면적산출 칸 고르기 시트(PLAN 40-3 · 43-1) — 그 열 면적산출 문서를 읽기만 시트로 붙이고 칸을 누르면 그 주소를 알림. * 일위대가 수량산출 모달(sub2 `Labor_QtyModal`)이 모달 안에 바로 씀 → 돌려받은 칸을 `linkAreaCell` 로 면적 문서에 * 이어 일위대가 [저장] 때 함께 저장(여기는 저장 안 함). * · 이미 이은 칸은 시트에 색 · 세모(알약 색 · `spreadsheet_links`) · 실제 값(식 칸 = 푼 값) * · 칸 누름 · Enter = 그 칸 · 그 칸에 다른 알약이 있으면 [함께 잇기](기본) · [바꾸기](`바꾸기: true`) · [취소](그대로) * ========================================================================== */ import { el } from "@ui/ui_template_elements"; import { askShare } from "../A00_Common/spreadsheet/spreadsheet_link_ask"; import { linksAt } from "../A00_Common/spreadsheet/spreadsheet_link_set"; import type { SpreadsheetHandle, Workbook } from "../A00_Common/spreadsheet/spreadsheet"; import { readTemplate, type Layer } from "./M02_MasterTemplete_Api_Fetch"; import "./M02_MasterTemplete_AreaPanel.css"; export { linkAreaCell } from "../A00_Common/spreadsheet/spreadsheet_link_set"; const TEXT = { loading: "면적산출을 읽는 중…", failed: "면적산출을 못 읽음 — {value}", }; const fill = (text: string, value: string): string => text.replace("{value}", value); /** 고른 칸 — `{칸: "B6"}` = 그 칸(다른 알약과 함께) · `{칸: "B6", 바꾸기: true}` = 그 칸 다른 알약을 빼고 */ export interface AreaCellPick { 칸: string; 바꾸기?: boolean; } export interface AreaSheetOptions { /** 그 열 면적산출 문서(저장 안 한 것 포함) — 없으면 `layer` · `column` 저장본을 읽음 */ doc?: Workbook | null; layer: Layer; /** 집계표 열 id */ column: string; /** 알약 키(로직 「이름|단위」 · 자재 「자재|MT키」) — 같은 칸 물음에서 자기는 뺌 */ pill: string; /** 칸을 고름(물음까지 끝난 뒤) */ onPick: (pick: AreaCellPick) => void; /** 시트를 다 읽음(그 문서) · 못 읽으면 null */ onLoad?: (book: Workbook | null) => void; } export interface AreaSheetHandle { destroy: () => void; } /** 읽기만 면적산출 시트를 `host` 에 붙임 — 칸을 눌러 뗌 · Enter = 그 칸(처음 고름 A1 · 화살표 옮김은 안 고름) */ export function mountAreaSheet(host: HTMLElement, opts: AreaSheetOptions): AreaSheetHandle { let handle: SpreadsheetHandle | null = null; let done = false; let picked = "A1"; let moved = false; // 누른 사이 고름이 바뀜 — 머리 · 굴림 막대를 눌렀다 뗀 것은 안 고름 let book: Workbook | null = null; host.replaceChildren(el("p", { className: "m02-cellpick__now", text: TEXT.loading })); const choose = async (): Promise => { const at = picked; const sheetId = book?.시트.find((s) => s.id === book?.활성)?.id ?? book?.시트[0]?.id; const others = book && sheetId ? linksAt(book, sheetId, at, opts.pill) : []; const choice = others.length ? await askShare( at, others.map((l) => l.이름 ?? l.키), ) : "함께"; if (!choice || done) return; opts.onPick(choice === "바꾸기" ? { 칸: at, 바꾸기: true } : { 칸: at }); }; void (async () => { try { const [{ createSpreadsheet }, got] = await Promise.all([ import("../A00_Common/spreadsheet/spreadsheet"), opts.doc ? Promise.resolve(opts.doc) : readTemplate(opts.layer, "area", opts.column, null).then((got) => got.문서 as Workbook), ]); if (done) return; book = got; host.replaceChildren(); handle = createSpreadsheet(host, structuredClone(got), { readOnly: true, tabs: false, onSelect: (s) => { picked = s.칸; moved = true; }, }); const grid = host.querySelector(".ss-grid-host"); grid?.addEventListener("mousedown", () => (moved = false), true); grid?.addEventListener("mouseup", (e) => { if ((e as MouseEvent).button === 0 && moved) void choose(); }); host.addEventListener("keydown", (e) => e.key === "Enter" && void choose()); opts.onLoad?.(got); } catch (error) { if (done) return; host.replaceChildren( el("p", { className: "m02-cellpick__now", text: fill(TEXT.failed, error instanceof Error ? error.message : ""), }), ); opts.onLoad?.(null); } })(); return { destroy: () => { done = true; handle?.destroy(); handle = null; }, }; }