- 비고 앞 「연결」 칸 — 손 항목 줄만 「연결 ▾」 · 연결됨 = 호표 번호 ▾ · 누름 = 그 칸 아래 연결 메뉴 - 메뉴 = 일위대가 호표 · 단가산출 호표 · 단가산출 로직 + 로직 입력값 · 연결 풀기(166-2 고르기 창 재사용) - 단추 칸 = 이동 안 함 · 묶음 줄 · 자동 줄 = 안 엶 - 시험 test_182 연결 단추(칸 · 메뉴 · 고르기 창 → ref) · node 입력 = 표준입력(명령 줄 길이) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
98 lines
4.7 KiB
TypeScript
98 lines
4.7 KiB
TypeScript
/* =============================================================================
|
|
* B09_Estimation_UI_Tabs_Bill.ts
|
|
* B09 설계내역서 탭(PLAN 145-3) — 실무 엑셀 꼴(공종 · 명칭 · 규격 · 수량 · 단위 · 합계 · 노무 · 재료 · 경비 · 비고).
|
|
*
|
|
* 값 = `B09_Estimation_Calc_Bill`(조작 중 브라우저 · [저장] 때 서버 정본)
|
|
* 고칠 칸 = 관급자재대 조달수수료 율(%) · 항목 수량(166-2 · 덮어쓰기 · 빈칸 = 자동) ·
|
|
* 손 줄 명칭 · 규격 · 단위 · 수량 · 빈칸 = 「연결 없음」(수량 · 단가 원천 없음)
|
|
* 줄 누름 = 단가를 낸 탭 줄(자재 → 재료비목록표 · 구조물 → 일위대가목록표)
|
|
* 우클릭 = 줄 추가 · 지우기 · 연결 · 레벨(`…_UI_Tabs_Bill_Edit`)
|
|
* 레벨(182) = 줄 `level`(셈이 냄) → class is-lvN · 글자 크기 · 첫 칸 들여쓰기(CSS) · 묶음 줄 = 이름만 고침
|
|
* 「연결 ▾」 칸(182) = 손 항목 줄만 · 누름 = 우클릭 [연결] 과 같은 메뉴(연결됨 = 호표 번호)
|
|
* ========================================================================== */
|
|
|
|
import type { DataRow } from "@ui/ui_template_data_table";
|
|
import type { Row } from "./B09_Estimation_Calc_Base";
|
|
import { money, parseNumber, qty, text, type TabBuild, type TabId } from "./B09_Estimation_UI_Tabs";
|
|
import {
|
|
billMenu,
|
|
editQty,
|
|
editText,
|
|
handEditable,
|
|
openLinkMenu,
|
|
type BillTable,
|
|
} from "./B09_Estimation_UI_Tabs_Bill_Edit";
|
|
|
|
export const buildBill: TabBuild = (ctx): BillTable => {
|
|
const { sheets, user, setUser } = ctx;
|
|
const bill: Row = sheets.bill || { rows: [], totals: null };
|
|
const rows: DataRow[] = bill.rows.map((r: Row) => ({
|
|
...r,
|
|
connect: r.hand && r.kind === "item" ? `${r.note || "연결"} ▾` : "",
|
|
}));
|
|
const direct: Row = bill.totals?.direct || {};
|
|
const pair = (key: string, label: string) => [
|
|
money(`price_${key}`, "단가", [label]),
|
|
money(key, "금액", [label]),
|
|
];
|
|
return {
|
|
columns: [
|
|
text("name", "공종 · 명칭", 220),
|
|
text("spec", "규격", 160),
|
|
qty("qty", "수량", 3),
|
|
text("unit", "단위", 56),
|
|
...pair("sum", "합계"),
|
|
...pair("labor", "노무비"),
|
|
...pair("material", "재료비"),
|
|
...pair("expense", "경비"),
|
|
text("connect", "연결", 96),
|
|
text("note", "비고", 200),
|
|
],
|
|
rows,
|
|
total: { name: "순공사비 계(관급 제외)", ...direct },
|
|
frozen: 1,
|
|
hint:
|
|
"공종 나무 = 엑셀 꼴 고정 · 금액 = ROUNDDOWN(단가 × 수량, 0) 비목마다 · 4 자재대 = 대비표 「별도」 · 관급자재대 = 「관급」 표시 자재 · 조달수수료 율(%)은 수량 칸에 넣음 · " +
|
|
"수량 칸 = 덮어쓰기(빈칸 = 자동) · 우클릭 · 「연결 ▾」 = 줄 추가 · 지우기 · 일위대가 · 단가산출 연결(새 산근 단가 = [저장] 뒤) · " +
|
|
"레벨 = 우클릭 묶음 줄로 · 하위 줄 추가 · 들여쓰기 · 내어쓰기(묶음 금액 = 아래 항목 합 · 공종 합은 항목만) · " +
|
|
`빈 줄 ${direct.blank ?? 0} — 계는 아는 금액만 · 원가계산서는 빈 줄이 없을 때만 받음`,
|
|
editable: (i, key) => {
|
|
const r = rows[i];
|
|
if (r?.kind === "group") return key === "name";
|
|
if (!r || r.kind !== "item") return false;
|
|
return r.hand ? handEditable(key) : key === "qty";
|
|
},
|
|
onEdit: (i, key, value) => {
|
|
const r = rows[i];
|
|
if (!r) return;
|
|
if (r.hand && key !== "qty") return editText(ctx, String(r.key), key, value);
|
|
if (key !== "qty") return;
|
|
const v = parseNumber(value);
|
|
if (v === undefined) return;
|
|
if (r.key !== "fee") return editQty(ctx, r, v);
|
|
setUser({ ...user, estimate: { ...(user.estimate || {}), procurement_fee_pct: v } });
|
|
},
|
|
tip: (i, key) => {
|
|
const r = rows[i];
|
|
if (key !== "qty" || !r || !("qty_auto" in r)) return null;
|
|
return `자동 값 ${r.qty_auto ?? "빈칸"} — 우클릭 [수량 자동으로 되돌리기]`;
|
|
},
|
|
// 182 레벨 — 글자 크기 · 들여쓰기(is-lvN) · 제목 · 묶음 줄 굵게
|
|
rowClass: (i) => {
|
|
const r = rows[i];
|
|
if (!r) return null;
|
|
const title = r.kind === "title" ? "is-title" : r.kind === "group" ? "is-group" : "";
|
|
return [`is-lv${r.level ?? 1}`, title, r.hand ? "is-hand" : ""].filter((c) => c).join(" ");
|
|
},
|
|
menu: (i) => billMenu(ctx, rows, i),
|
|
onCell: (i, key) => {
|
|
if (key === "connect") openLinkMenu(ctx, rows, i);
|
|
},
|
|
link: (i, key) => {
|
|
if (key === "connect") return null; // 「연결 ▾」 = 메뉴(이동 안 함)
|
|
const ref = rows[i]?.ref as Row | null | undefined;
|
|
return ref?.key ? { tab: ref.tab as TabId, key: String(ref.key) } : null;
|
|
},
|
|
};
|
|
};
|