/* ============================================================================= * B09_Estimation_UI_BaseData.ts * 기초자료 탭 · 중기 탭 — 목록표 넷을 그린다 (사용자 확정 12번 「내야 할 표 16개 전체」). * * 기초자료 탭 : 노무비목록표 · 재료비목록표 · 경비목록표 * 중기 탭 : 중기목록표 (합계 + 노무·재료·경비 3분할) * * 서식은 지어내지 않았다 — 실무 내역서(영월 기번6 · 봉화 기번41)의 같은 이름 시트를 * 그대로 옮겼다. 칸 이름·차례가 그 시트와 같다. * * 화면 조립부(`B09_Estimation_UI_Page.ts`)가 이미 700줄을 크게 넘어 여기로 뺐다. * 표를 그리는 일만 하고 **상태를 들지 않는다** — 부르는 쪽이 자료를 넘긴다. * ========================================================================== */ import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; import { API_BASE_URL } from "@config/config_frontend"; function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } /** 목록표 한 줄 — 실무 시트 칸 그대로. */ export interface BaseDataRow { code: string; name: string; spec: string; unit: string; unit_price_krw: string | null; note: string; } /** 중기목록표 한 줄 — 합계와 3분할을 함께 보인다. */ export interface MachineRow { code: string; name: string; spec: string; unit: string; total_krw: string | null; labor_krw: string | null; material_krw: string | null; expense_krw: string | null; note: string; } export interface BaseDataDto { status: string; labor: BaseDataRow[]; material: BaseDataRow[]; expense: BaseDataRow[]; machine: MachineRow[]; } export async function fetchBaseData(projectId: string): Promise { const response = await fetch( `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/estimation/base-data`, { credentials: "include" }, ); if (!response.ok) throw new Error(`base-data ${response.status}`); return (await response.json()) as BaseDataDto; } function money(value: string | null): string { if (value === null || value === "") return ""; const parsed = Number(value); return Number.isFinite(parsed) ? parsed.toLocaleString("ko-KR") : value; } function head(text: string): HTMLElement { const el = document.createElement("div"); el.className = "b09-hint"; el.style.fontWeight = "600"; el.textContent = text; return el; } function note(text: string): HTMLElement { const el = document.createElement("div"); el.className = "b09-hint"; el.textContent = text; return el; } function table(headers: string[], rows: string[][], leftCols: number[]): HTMLElement { const el = document.createElement("table"); el.className = "b09-sheet"; const thead = document.createElement("thead"); const headRow = document.createElement("tr"); headers.forEach((text, index) => { const th = document.createElement("th"); th.textContent = text; if (leftCols.includes(index)) th.className = "b09-left"; headRow.append(th); }); thead.append(headRow); const tbody = document.createElement("tbody"); for (const cells of rows) { const tr = document.createElement("tr"); cells.forEach((text, index) => { const td = document.createElement("td"); td.textContent = text; if (leftCols.includes(index)) td.className = "b09-left"; tr.append(td); }); tbody.append(tr); } el.append(thead, tbody); return el; } /** 목록표 한 장 — 코드·명칭·규격·단위·단가·비고 (실무 시트와 같은 칸). */ function catalogTable(rows: BaseDataRow[]): HTMLElement { return table( ["코드번호", "명 칭", "규 격", "단위", "단 가", "비 고"], rows.map((row) => [ row.code, row.name, row.spec, row.unit, money(row.unit_price_krw), row.note, ]), [0, 1, 2, 5], ); } /** * 기초자료 탭 — 목록표 셋. * * ⚠ 표가 비거나 한 줄뿐일 때 **그냥 두지 않는다** — 「다 채운 것」으로 읽히기 때문이다. * 재료비목록표가 지금 그 자리다(사급 자재 카탈로그가 아직 안 섰다). */ export function drawBaseDataTab(body: HTMLElement, data: BaseDataDto): void { const groups: Array<[string, BaseDataRow[], string]> = [ ["노무비목록표", data.labor, ""], [ "재료비목록표", data.material, data.material.length <= 1 ? "⚠ 사급 자재 카탈로그가 아직 서지 않아 줄이 거의 없습니다 — 자재값 출처(업체 견적·물가지)를 붙이면 채워집니다." : "", ], ["경비목록표", data.expense, "기계 취득가격입니다(천원) — 시간당 사용료는 「중기」 탭입니다."], ]; for (const [title, rows, hint] of groups) { body.append(head(`${title} (${rows.length})`)); if (hint) body.append(note(hint)); if (rows.length === 0) { body.append(note("아직 선 줄이 없습니다 — 값을 0 으로 채우지 않습니다.")); continue; } body.append(catalogTable(rows)); } } /** 중기 탭 — 중기목록표. 합계와 3분할을 함께 보인다(실무 시트와 같은 칸). */ export function drawMachineTab(body: HTMLElement, data: BaseDataDto): void { body.append(head(`중기목록표 (${data.machine.length})`)); if (data.machine.length === 0) { body.append(note("아직 선 줄이 없습니다 — 값을 0 으로 채우지 않습니다.")); return; } body.append( table( ["코드번호", "명 칭", "규 격", "단위", "합 계", "노 무 비", "재 료 비", "경 비", "비 고"], data.machine.map((row) => [ row.code, row.name, row.spec, row.unit, money(row.total_krw), money(row.labor_krw), money(row.material_krw), money(row.expense_krw), row.note, ]), [0, 1, 2, 8], ), ); // ⚠ 계산 과정을 감추지 않는다(PLAN 8-13). 조종원 환산이 실무와 다른 것을 여기서 밝힌다. body.append( note( "조종원 노임은 「노임 ÷ 8시간」으로 셉니다. 실무·임도교본은 여기에 " + "「× 16/12 × 25/20」(약 1.67배)을 더 곱합니다 — 그 계수의 규정 원문(기재부 " + "예정가격 작성기준)을 아직 확인하지 못해 적용하지 않았습니다. 확정되면 " + "기계가 든 공종 단가가 약 19~24% 오릅니다.", ), ); body.append(note("잡재료(주연료의 %)는 연료 소요량에 포함되어 있습니다 — 따로 세지 않습니다.")); } /** 두 탭이 함께 쓰는 「아직 못 불러왔습니다」 문구. */ export function drawBaseDataError(body: HTMLElement): void { body.append(note(L("B09_Estimation_Tab_Pending"))); }