Files
Aislo/B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts
T
eomsangdonandClaude Sonnet 5.5 88dd941d73 fix(B08·B09): 표 아래 안내 글 제작 · 셈 설명 지움 · 늘 한 줄 · 페이지 스크롤 0(236-1)
- B09 표 탭 안내 글 = 쓸모 있는 한 줄(lineHint) · 묶음 탭과 같은 줄임표
- B08 표 아래 안내 · 경고 줄 = footNote 한 줄 · 서버 알림은 한 줄로 이어 붙임
- 새 시험 test_236_1 · 창 높이별 창 끝까지 셈 헬퍼

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
2026-10-10 19:09:56 +09:00

166 lines
5.6 KiB
TypeScript

/* =============================================================================
* B09_Estimation_UI_Tabs_Labor.ts
* B09 대가 탭 둘(PLAN 145-2) — 일위대가목록표 · 일위대가표(실무 엑셀 꼴 · 값 = `B09_Estimation_Calc_Labor`).
*
* 목록표 = 호표 한 줄(합계 · 노무비 · 재료비 · 경비) · 줄 누름 = 그 블록으로
* 일위대가표 = 블록(머리 · [품셈 인용] · 줄 · 합계) · 줄 누름 = 단가를 낸 탭 · 줄(대가 줄 = 그 블록)
* 줄 꼴(155-2) = 머리 · 합계 굵게(`is-title`) · 인용 · 줄 = 들여쓰기 「ㄴ」(2단 · 겹 대가는 안 펼침)
* 연결된 구조물 조합 사본이 없으면 빈 표 「연결 없음」
* 빈 줄(166-6) = 합계 = 아는 줄 합 · 옆 「빈 줄 n」 · 풍선 = 빈 줄 이름 · 내역 단가엔 안 넘김
* ========================================================================== */
import type { DataRow } from "@ui/ui_template_data_table";
import type { Row } from "./B09_Estimation_Calc_Base";
import {
blockLevel,
money,
qty,
text,
type Link,
type TabBuild,
type TabId,
type TabTable,
} from "./B09_Estimation_UI_Tabs";
import { raw } from "./B09_Estimation_UI_Tabs_Sangeun";
const EMPTY =
"연결 없음 — 연결된 구조물 조합 사본(일위대가)이 없음 · B05 · B06 에서 조합을 고르고 [저장]";
/** 하위 줄 앞 — 들여쓰기 + 「ㄴ」(공용 표가 앞 빈칸을 접지 않음) */
const CHILD = "ㄴ "; // 들여쓰기 = 공용 표 level(201-1)
const BLANK = "빈 줄은 금액을 모르는 줄(합계는 아는 줄만)";
/** 「빈 줄 n」 작은 글 · 풍선 = 빈 줄 이름 */
const blankNote = (b: Row): string => (b.blank ? `빈 줄 ${b.blank}` : "");
const blankTip = (b: Row | undefined): string | null =>
b?.blank ? `빈 줄 — ${(b.blank_names as string[]).join(" · ")}` : null;
/** 블록 줄 → 단가를 낸 탭 */
const LINE_TAB: Record<string, TabId> = {
labor: "labor",
material: "material",
machine: "machine",
daega: "dg",
sangeun: "sg",
};
export const buildDaegaList: TabBuild = ({ sheets }): TabTable | string => {
const rows: DataRow[] = (sheets.daega || []).map((r: Row) => ({
...r,
no: `대가${r.no}`,
blank_note: blankNote(r),
}));
if (!rows.length) return EMPTY;
return {
columns: [
text("no", "호표", 80),
text("kind", "꼴", 70),
text("name", "명칭", 240),
text("spec", "규격", 200),
text("unit", "단위", 56),
money("sum", "합계"),
text("blank_note", "", 64),
money("labor", "노무비"),
money("material", "재료비"),
money("expense", "경비"),
],
rows,
frozen: 3,
hint: `줄을 누르면 그 블록으로 이동 · ${BLANK}`,
link: (i: number): Link | null => (rows[i] ? { tab: "dg", key: String(rows[i].key) } : null),
tip: (i, key) =>
key === "spec"
? raw(rows[i]?.spec, rows[i]?.spec_raw)
: key === "blank_note" || key === "sum"
? blankTip(rows[i])
: null,
};
};
export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => {
const blocks: Row[] = sheets.daega_blocks || [];
if (!blocks.length) return EMPTY;
const rows: DataRow[] = [];
const links: Array<Link | null> = [];
const titles = new Set<number>();
for (const b of blocks) {
titles.add(rows.length);
rows.push({
key: b.id,
name: `제 ${b.no} 호표 ${b.name}`,
no: b.no,
title: b.name, // 171-3 묶음 카드 머리
spec: b.spec,
spec_raw: b.spec_raw,
unit: b.unit,
note: b.kind,
});
links.push(null);
if (b.cite) {
rows.push({ key: `${b.id}|cite`, name: `${CHILD}${b.cite}`, note: "품셈 인용" });
links.push(null);
}
b.lines.forEach((ln: Row, n: number) => {
rows.push({
key: `${b.id}|${n}`,
name: `${CHILD}${ln.name || ""}`,
spec: ln.spec || "",
spec_raw: ln.spec_raw,
qty: ln.qty,
unit: ln.unit || "",
sum_price: ln.price,
sum: ln.amount,
labor_price: ln.prices.labor,
labor: ln.amounts.labor,
material_price: ln.prices.material,
material: ln.amounts.material,
expense_price: ln.prices.expense,
expense: ln.amounts.expense,
note: ln.ref_no || ln.why || "",
});
const tab = LINE_TAB[ln.kind];
links.push(tab && ln.key ? { tab, key: String(ln.key) } : null);
});
titles.add(rows.length);
rows.push({
key: `${b.id}|합계`,
name: "합 계",
sum: b.sum,
labor: b.labor,
material: b.material,
expense: b.expense,
note: blankNote(b),
blank: b.blank,
blank_names: b.blank_names,
});
links.push(null);
}
const pair = (key: string, label: string) => [
money(`${key}_price`, "단가", [label], 1),
money(key, "금액", [label], 1),
];
return {
columns: [
text("name", "명칭", 240),
text("spec", "규격", 160),
qty("qty", "수량"),
text("unit", "단위", 56),
...pair("sum", "합계"),
...pair("labor", "노무비"),
...pair("material", "재료비"),
...pair("expense", "경비"),
text("note", "비고", 120),
],
rows,
frozen: 1,
hint: `줄을 누르면 단가를 낸 탭으로 이동 · ${BLANK}`,
link: (i: number) => links[i] ?? null,
rowClass: (i: number) => (titles.has(i) ? "is-title" : null),
level: blockLevel("name", (i) => titles.has(i)),
tip: (i: number, key: string) =>
key === "spec"
? raw(rows[i]?.spec, rows[i]?.spec_raw)
: key === "note" || key === "sum"
? blankTip(rows[i])
: null,
};
};