/* ============================================================================= * M02_MasterTemplete_AreaPanel.ts * 면적산출 상세 오른쪽 패널(PLAN 39-4 D2 · D6) — 그 열 일위대가의 담은 로직 + 자재목록(기준 · 반영). * 몸 = 일위대가 보기 갈래(`Labor_View` · 고치기 없음) · 틀 = 공용 사이드바 카드 · 접기 손잡이 · 폭 손잡이 * (폭 · 접힘 기억은 옛 조합 패널 키 그대로). * · 줄 누름 = 칸 지정 모달(`AreaLink` · 그 줄 알약 키를 면적 칸에 잇기 · 풀기) * · 시트 칸 값이 바뀌면 `refresh` — 받은 기준에 바로 곱함(서버 안 부름 · `Labor_View.setQty`) * ========================================================================== */ import { el, showToast } from "@ui/ui_template_elements"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; import { createPanelResizer } from "@ui/ui_template_resizer"; import { readStateRaw, stateKey, writeStateRaw } from "../A00_Common/b_page_state"; import type { LinkItem, SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet"; import type { Layer } from "./M02_MasterTemplete_Api_Fetch"; import { openAreaLink } from "./M02_MasterTemplete_AreaLink"; import { createLaborView, type LaborViewRow } from "./M02_MasterTemplete_Labor_View"; import "./M02_MasterTemplete_AreaPanel.css"; const TEXT = { handle: "일위대가", title: "이 열의 일위대가", noPill: "이 줄은 면적산출 알약이 없음 — 돈 아닌 로직이거나 계산 전", }; /** 최소 폭(px) · 최대 = 편집 칸 폭의 비율 */ const MIN_WIDTH = 280; const MAX_RATIO = 0.6; export interface AreaPanelOptions { layer: Layer; /** 집계표 열 id */ column: string; sheet: () => SpreadsheetHandle | null; /** 시트에서 지금 고른 칸(A1) */ picked: () => string; /** 알약 막대 항목 — 칸 지정 때 연결에 같이 적을 이름 · 단위 · 자재 */ pills: () => LinkItem[]; readOnly: boolean; } export interface AreaPanel { root: HTMLElement; /** 시트 칸 값 · 연결이 바뀜 — 반영을 바로 다시 곱함 */ refresh: () => void; destroy: () => void; } /** 시트 알약 값 → 수만(`{키: 수}`) — 수 아닌 칸은 빠짐(「수량 없음」) */ function quantities(sheet: SpreadsheetHandle | null): Record { const out: Record = {}; for (const [key, v] of Object.entries(sheet?.getLinkedValues() ?? {})) { const n = "수" in v ? Number(v.수) : NaN; if (Number.isFinite(n)) out[key] = n; } return out; } export function buildAreaPanel(opts: AreaPanelOptions): AreaPanel { const openLink = (row: LaborViewRow): void => { const sheet = opts.sheet(); if (!sheet) return; if (!row.pill) return showToast(TEXT.noPill, "info"); const item = opts.pills().find((p) => p.키 === row.pill); openAreaLink({ label: row.name, key: row.pill, info: { 이름: item?.이름 ?? row.name, ...((item?.단위 ?? row.unit) ? { 단위: item?.단위 ?? row.unit } : {}), ...(item?.자재 ? { 자재: item.자재 } : {}), }, sheet, picked: opts.picked, readOnly: opts.readOnly, }); }; const view = createLaborView(openLink); const body = el("div", { className: "m02-apanel__body ui-sidebar-panel", children: [ el("section", { className: "ui-collapsible ui-sidebar-section ui-sidebar-card", children: [el("h3", { className: "ui-collapsible__title", text: TEXT.title }), view.root], }), ], }); attachCollapsible(body); const root = el("aside", { className: "m02-apanel" }); const handle = createWorkflowPanelHandle("side", "right", TEXT.handle); // 왼쪽 경계를 끌어 폭 조절 — 폭은 브라우저에 기억 · 더블클릭 = 기본 폭 const resizer = createPanelResizer({ axis: "horizontal", target: root, cssVar: "--m02-apanel-width", direction: -1, min: MIN_WIDTH, max: () => (root.parentElement?.clientWidth ?? window.innerWidth) * MAX_RATIO, storageKey: stateKey("m02-combo-panel-width") ?? undefined, }); root.append(handle.root, resizer.root, body); const setCollapsed = (collapsed: boolean): void => { root.classList.toggle("is-collapsed", collapsed); handle.setOpen(!collapsed); writeStateRaw("m02-combo-panel-collapsed", String(collapsed)); }; handle.root.addEventListener("click", () => setCollapsed(!root.classList.contains("is-collapsed")), ); setCollapsed(readStateRaw("m02-combo-panel-collapsed") === "true"); // 처음 = 저장 안 한 시트 칸 값으로 셈(서버 calc 한 번) void view.show(opts.layer, opts.column, quantities(opts.sheet())); return { root, refresh: () => view.setQty(quantities(opts.sheet())), destroy: () => { view.destroy(); resizer.dispose(); root.remove(); }, }; }