/* ============================================================================= * B09_Estimation_UI_Tabs.ts * B09 탭 목록 · 표 짜기 공용(PLAN 144-1) — 탭 하나 = 화면 표 하나(실무 엑셀 탭 차례). * * 탭 = 원가 · 총괄 · 내역 · 대가 둘 · 산근 둘 · 기초 · 중기 둘 · 단가 넷 · 집계표 넷 * 표 = 공용 `createDataTable` · 「연결 없음」 칸 = 빈칸 · 호표 칸 누름 = 그 탭 그 줄로 이동(`link`) * 탭별 표 = `…_UI_Tabs_Base`(144) · `…_UI_Tabs_Cost`(145) · 여기는 차례 · 꼴만 * 연결 점검(옛 첫 탭) = 좌측 「데이터 점검」 묶음(203-1 `…_UI_Check`) * ========================================================================== */ import type { MapContextMenuItem } from "@ui/ui_template_context_menu"; import type { DataColumn, DataRow, DataTableOptions } from "@ui/ui_template_data_table"; import type { Row } from "./B09_Estimation_Calc_Base"; export type TabId = | "cost" | "total" | "bill" | "dg_list" | "dg" | "sg_list" | "sg" | "basis" | "machine" | "machine_blocks" | "material" | "labor" | "expense" | "compare" | "sum_material" | "sum_labor" | "sum_expense" | "sum_machine"; /** 엑셀 탭 차례 — [id, 탭 이름] */ export const TABS: Array<[TabId, string]> = [ ["cost", "공사원가계산서"], ["total", "총괄설계내역서"], ["bill", "설계내역서"], ["dg_list", "일위대가목록표"], ["dg", "일위대가표"], ["sg_list", "단가산출근거목록표"], ["sg", "단가산출근거"], ["basis", "환율및기초자료"], ["machine", "중기목록표"], ["machine_blocks", "중기사용료"], ["material", "재료비목록표"], ["labor", "노무비목록표"], ["expense", "경비목록표"], ["compare", "자재단가대비표"], ["sum_material", "재료비수량금액집계표"], ["sum_labor", "노무비수량금액집계표"], ["sum_expense", "경비수량금액집계표"], ["sum_machine", "중기시간금액집계표"], ]; export const tabName = (id: string): string => TABS.find(([t]) => t === id)?.[1] ?? id; /** 위 탭(181-1) — [위 탭 이름, 아래 탭들] · 아래 탭 하나뿐 = 위 탭 단독 · 첫 아래 탭 = 위 탭 키 */ export const TAB_GROUPS: Array<[string, TabId[]]> = [ ["공사원가계산서", ["cost"]], ["총괄설계내역서", ["total"]], ["설계내역서", ["bill"]], ["일위대가", ["dg_list", "dg"]], ["단가산출", ["sg_list", "sg"]], ["환율및기초자료", ["basis"]], ["중기", ["machine", "machine_blocks", "sum_machine", "expense", "sum_expense"]], ["재료", ["material", "compare", "sum_material"]], ["노무", ["labor", "sum_labor"]], ]; /** 탭이 든 위 탭 묶음 */ export const groupOf = (tab: TabId): [string, TabId[]] => TAB_GROUPS.find(([, tabs]) => tabs.includes(tab)) ?? TAB_GROUPS[0]; /** 다른 탭 줄 — 키가 같은 줄로 이동 */ export interface Link { tab: TabId; key: string; } /** 페이지가 탭 짜기에 주는 것 */ export interface TabContext { sheets: Row; payload: Row; user: Row; /** 사용자 값 고침 → 캐시 · 바로 다시 셈 */ setUser: (user: Row) => void; } /** 탭 한 장 = 표 하나 */ export interface TabTable { columns: DataColumn[]; rows: DataRow[]; total?: DataRow | null; frozen?: number; /** 표 위 안내 글(근거 · 끝수) */ hint?: string; /** 칸 누름 → 갈 줄(호표 칸) */ link?: (index: number, key: string) => Link | null; editable?: (index: number, key: string) => boolean; /** 고칠 칸 = 더블클릭 · Enter · F2 때만 글 상자(235-1 · 공용 표 editOnDouble) */ editOnDouble?: boolean; onEdit?: (index: number, key: string, text: string) => void; /** 칸 누름 → 그 자리 동작(예 관급 · 별도 표시 바꿈) */ onCell?: (index: number, key: string) => void; /** 줄 꼴 class(공용 표 `rowClass` · 예 제목 줄 굵게) */ rowClass?: (index: number) => string | null | undefined; /** 칸 풍선 글(157-3 · 예 「이름 규격」 칸의 원문번호) */ tip?: (index: number, key: string) => string | null | undefined; /** 칸 꼴 class(183-3 공용 표 `cellClass` · 예 고른 출처 칸 강조) */ cellClass?: (index: number, key: string) => string | null | undefined; /** 칸 누름 고르기 메뉴(183-3 · 칸 아래 공용 메뉴 · 빈 것 = 안 엶) */ choices?: (index: number, key: string) => MapContextMenuItem[] | null; /** 레벨(201-1 · 공용 표 level · 보기 전용) — 그 열 들여쓰기 */ level?: DataTableOptions["level"]; /** 같은 값 세로 병합할 열(201-1 · 공용 표 mergeSame) */ mergeSame?: string[]; } export type TabBuild = (ctx: TabContext) => TabTable | string; /** 201-1 호표 묶음 레벨(보기 전용) — 머리 · 합계(제목 줄) = 1 · 그 밖 = 2 · key 열을 들여씀 */ export const blockLevel = (key: string, top: (index: number) => boolean): TabTable["level"] => ({ key, of: (i) => (top(i) ? 1 : 2), }); /* ── 열 꼴 ────────────────────────────────────────────────────────────── */ export const text = (key: string, label: string, width?: number, group?: string[]): DataColumn => ({ key, label, width, group, }); export const money = (key: string, label: string, group?: string[], decimals = 0): DataColumn => ({ key, label, group, kind: "number", decimals, width: 96, }); export const qty = (key: string, label: string, decimals = 4, group?: string[]): DataColumn => ({ key, label, group, kind: "number", decimals, trim: true, width: 76, }); /** 글 → 값 · 빈칸 = null · 수가 아니면 undefined(무시) */ export function parseNumber(value: string): number | null | undefined { const t = value.replace(/,/g, "").trim(); if (!t) return null; const v = Number(t); return Number.isFinite(v) ? v : undefined; } /** 줄 키로 줄 번호 찾기(이동) */ export function rowIndex(rows: DataRow[], key: string): number { return rows.findIndex((row) => row.key === key); }