- 서버: 내역 응답에 집계표 넷(resources)·목록표(lists) — 내역이 쓴 자원을 처음 쓰인 차례로 되모음 (묶음 줄·구조물도 호표 줄도 구성까지 풀어 셈) · 재료비 집계표에 자재대(사급·관급) 줄 이어 붙임 - 중기시간금액집계표: 단가 열 없이 합계·노무·재료·경비 — 성분마다 반올림한 뒤 합(골든셋 131/144, 합계 한 번 반올림 107/144) - 재료·노무·경비 집계표: 반올림(수량 × 단가) 골든셋 279/279 — 시험 한 벌 더함 - 중기사용료 호표: 잡품 줄은 수량 칸 율(%) · 단가 칸 밑수(주연료비) · 금액 — 실무 표 그대로. 제잡비·공구손료 줄도 밑수를 단가 칸에 - 자재단가대비표: 슬롯 1~6 단가·페이지 · 채택 칸 굵게 · 최소단가 표시(서버 min_slot) - 목록표 일곱(일위대가·단가산출근거·중기·재료비·노무비·경비·일식견적): 색인만 — 서버 번호·단가 그대로, 일위대가·산근·중기 줄은 누르면 들어감 - 옛 중기 탭 줄을 새 탭으로 바꿔 끼움(중기경비계산서는 옛 표를 아래에 이어 보임) - 검증: ORCA — 중기 목록 9 · 굴착기 0.7 호표 합계 109,704 · 잡품 22 % × 21,418.1 = 4,711.9 · 노무 집계 벌목부 9,686,529 · 중기 집계 성분 합 · 시험 1638 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
94 lines
3.7 KiB
TypeScript
94 lines
3.7 KiB
TypeScript
/* =============================================================================
|
|
* B09_Estimation_UI_Shell.ts
|
|
* 로그인 후 09: 6차 워크플로우 (원가계산) — 화면 **틀** (PLAN 12장 · 2026-09-14 브레인 판정)
|
|
*
|
|
* - 틀은 탭 줄과 등록만. 탭마다 파일 하나(`B09_Estimation_UI_Tab_<이름>.ts`) — 계약은 `_Shell_Types`.
|
|
* - ⚠ 등록 권한 = 랩탑_서브. 다른 창은 탭 파일을 만들고 이름을 알려 등록을 부탁함.
|
|
* - 탭을 고를 때마다 본문·좌측 칸을 비우고 `render(ctx, arg)`. `ctx.open(key, arg)` = 다른 탭으로 들어가기.
|
|
* ========================================================================== */
|
|
|
|
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
|
|
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
|
|
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
|
import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav";
|
|
import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types";
|
|
import { L, el, injectSheetStyles } from "./B09_Estimation_UI_Sheet";
|
|
import { legacyTab } from "./B09_Estimation_UI_Tab_Legacy";
|
|
import { costSheetTab } from "./B09_Estimation_UI_Tab_CostSheet";
|
|
import { rateTableTab } from "./B09_Estimation_UI_Tab_RateTable";
|
|
import { billTab } from "./B09_Estimation_UI_Tab_Bill";
|
|
import { unitPriceTab } from "./B09_Estimation_UI_Tab_UnitPrice";
|
|
import { priceBasisTab } from "./B09_Estimation_UI_Tab_PriceBasis";
|
|
import { machineTab } from "./B09_Estimation_UI_Tab_Machine";
|
|
import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare";
|
|
import { summaryTab } from "./B09_Estimation_UI_Tab_Summary";
|
|
import { listsTab } from "./B09_Estimation_UI_Tab_Lists";
|
|
|
|
/** 탭 등록 — 한 줄에 탭 하나. 옛 탭(`legacyTab`)은 새 탭 파일이 서면 그 줄만 바꿈. */
|
|
const TABS: B09Tab[] = [
|
|
costSheetTab, // 랩탑_메인
|
|
rateTableTab, // 랩탑_메인
|
|
billTab,
|
|
unitPriceTab,
|
|
priceBasisTab,
|
|
machineTab,
|
|
priceCompareTab,
|
|
summaryTab,
|
|
listsTab,
|
|
legacyTab("supply", "B09_Estimation_Tab_Supply"),
|
|
legacyTab("base_data", "B09_Estimation_Tab_BaseData"),
|
|
legacyTab("design_doc", "B09_Estimation_Tab_DesignDoc"),
|
|
legacyTab("basis_sheet", "B09_Estimation_Tab_BasisSheet"),
|
|
];
|
|
|
|
export async function renderB09Estimation(root: HTMLElement): Promise<void> {
|
|
injectSheetStyles();
|
|
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
|
const page = el("div", "b09s-page");
|
|
const bar = el("div", "b09s-tabs");
|
|
const body = el("div", "b09s-body");
|
|
const panel = el("div");
|
|
page.append(bar, body);
|
|
|
|
let active = TABS[0].key;
|
|
const open = (key: string, arg?: string): void => {
|
|
const tab = TABS.find((item) => item.key === key);
|
|
if (!tab) return;
|
|
active = key;
|
|
drawBar();
|
|
body.replaceChildren();
|
|
panel.replaceChildren();
|
|
const ctx: B09TabContext = { projectId, body, panel, root, open };
|
|
tab.render(ctx, arg);
|
|
};
|
|
const drawBar = (): void => {
|
|
bar.replaceChildren(
|
|
...TABS.map((tab) => {
|
|
const button = el(
|
|
"button",
|
|
`b09s-tab${tab.key === active ? " is-active" : ""}`,
|
|
tab.label(),
|
|
);
|
|
button.type = "button";
|
|
button.dataset.tab = tab.key;
|
|
button.addEventListener("click", () => open(tab.key));
|
|
return button;
|
|
}),
|
|
);
|
|
};
|
|
|
|
const layout = createWorkflowLayout({
|
|
title: L("B09_Estimation_Title"),
|
|
steps: workflowSteps(),
|
|
activeStep: 6,
|
|
leftPanel: panel,
|
|
mainContent: page,
|
|
routes: WORKFLOW_STEP_ROUTES,
|
|
onStepClick: (stepIndex) => {
|
|
if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);
|
|
},
|
|
});
|
|
root.append(layout.root);
|
|
open(active);
|
|
}
|