- 옛 접기 손잡이 · 폭 손잡이 · 사이드바 카드 틀을 공용 오른쪽 접는 패널(47-3)로 갈음 — 하나만 열림 · 폭 공유 · 모두 접힘 - 「도면」 패널 = 그 열 구조물 도면 보기만(확대 · 이동) · 처음 펼 때 읽고 그림 - DrawingCard 미리보기 한 벌(mountStructure)을 카드 · 도면 패널이 같이 씀 - 시험: test_m02_structure_panel 기대를 공용 패널로 · ui_checks/47-6 시나리오 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
125 lines
5.1 KiB
TypeScript
125 lines
5.1 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_AreaPanel.ts
|
|
* 면적산출 상세 오른쪽 패널(PLAN 39-4 D2 · D6 · 47-6) — 틀 = 공용 오른쪽 접는 패널(하나만 열림 · 폭 공유)
|
|
* · 「일위대가」 = 그 열 일위대가의 담은 로직 + 자재목록(기준 · 반영) · 몸 = 일위대가 보기 갈래(`Labor_View`)
|
|
* · 「도면」 = 그 열 구조물 도면 보기만(`AreaDrawing` · 처음 펼 때 그림)
|
|
* · 줄 누름 = 칸 지정 모달(`AreaLink` · 그 줄 알약 키를 면적 칸에 잇기 · 풀기)
|
|
* · 시트 칸 값 · 연결이 바뀌면 `refresh` — 받은 기준에 바로 곱함 · 칸 주소도 넘김(서버 안 부름 · `Labor_View.setQty`)
|
|
* ========================================================================== */
|
|
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import { createSidePanels } from "@ui/ui_template_side_panels";
|
|
import type { LinkItem, SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
|
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
|
|
import { createAreaDrawing } from "./M02_MasterTemplete_AreaDrawing";
|
|
import { openAreaLink } from "./M02_MasterTemplete_AreaLink";
|
|
import { createLaborView, type LaborViewRow } from "./M02_MasterTemplete_Labor_View";
|
|
import "./M02_MasterTemplete_AreaPanel.css";
|
|
|
|
const TEXT = {
|
|
labor: "일위대가",
|
|
drawing: "도면",
|
|
noPill: "이 줄은 면적산출 알약이 없음 — 돈 아닌 로직이거나 계산 전",
|
|
};
|
|
|
|
/** 기억 없을 때 폭(px) — 금액 칸 넷이 가로 굴림 없이 보이게 */
|
|
const DEFAULT_WIDTH = 576;
|
|
|
|
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;
|
|
}
|
|
|
|
/** 칸 값 → 수(쉼표 뗌) — 빈 글 · 글 · 참거짓은 null(서버 `M02_Labor_Doc._number` 와 같음) */
|
|
function toNumber(raw: unknown): number | null {
|
|
if (raw === null || raw === undefined || typeof raw === "boolean") return null;
|
|
const text = String(raw).replace(/,/g, "").trim();
|
|
const n = Number(text);
|
|
return text !== "" && Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
/** 시트 알약 값 → `{키: 수}` — 서버 `M02_Labor_Doc.area_values` 와 같은 규칙(짝): 식 칸 = 계산 값의 수 ·
|
|
* 그 밖 = 칸 값 · 빈 칸 · 글은 빠짐(「수량 없음」 · 반영 「—」) */
|
|
function quantities(sheet: SpreadsheetHandle | null): Record<string, number> {
|
|
const out: Record<string, number> = {};
|
|
if (!sheet) return out;
|
|
const values = sheet.getLinkedValues();
|
|
for (const s of sheet.getDoc().시트)
|
|
for (const l of s.연결 ?? []) {
|
|
const cell = s.칸[l.칸];
|
|
const v = values[l.키];
|
|
const n = toNumber(cell?.식 !== undefined ? (v && "수" in v ? v.수 : null) : cell?.값);
|
|
if (n !== null) out[l.키] = n;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** 시트 연결 → `{알약 키: 칸 주소}` — 저장 전 잇기 · 풀기도(줄 아래 「수량 B6 = …」) */
|
|
function cellsOf(sheet: SpreadsheetHandle | null): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
for (const s of sheet?.getDoc().시트 ?? []) for (const l of s.연결 ?? []) out[l.키] = l.칸;
|
|
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 drawing = createAreaDrawing(opts.layer, opts.column);
|
|
const panels = createSidePanels({
|
|
prefKey: "m02-area-panels",
|
|
defaultWidth: DEFAULT_WIDTH,
|
|
panels: [
|
|
{ id: "labor", title: TEXT.labor, body: view.root },
|
|
{ id: "drawing", title: TEXT.drawing, body: drawing.root },
|
|
],
|
|
onChange: (open) => open === "drawing" && drawing.show(),
|
|
});
|
|
if (panels.current() === "drawing") drawing.show();
|
|
const root = panels.root;
|
|
|
|
// 처음 = 저장 안 한 시트 칸 값으로 셈(서버 calc 한 번)
|
|
const refresh = (): void => view.setQty(quantities(opts.sheet()), cellsOf(opts.sheet()));
|
|
void view.show(opts.layer, opts.column, quantities(opts.sheet()));
|
|
refresh(); // 칸 주소는 지금 시트 것 — 보기가 저장본 대신 씀
|
|
return {
|
|
root,
|
|
refresh,
|
|
destroy: () => {
|
|
view.destroy();
|
|
drawing.destroy();
|
|
root.remove();
|
|
},
|
|
};
|
|
}
|