Files
Aislo/B08_wf5_Quantity/B08_wf5_Quantity_UI_Page.ts
T
2026-07-20 19:51:20 +09:00

223 lines
6.9 KiB
TypeScript

/* =============================================================================
* B08_wf5_Quantity_UI_Page.ts
* 로그인 후 08: 5차 워크플로우 (수량 산출 UI 메인)
*
* 엑셀 내역서(2025년 산불진화임도...) 연동을 위한 전체 145개 템플릿 UI.
* 입력데이터(B07 또는 이전 산출값)는 비어있는(null) 상태로 조율되며, 수식 기반 템플릿 항목을 표시합니다.
* 오직 B08_wf5_Quantity 폴더 내의 파일만 다룹니다.
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { renderPendingWorkflow, workflowSteps } from "../A00_Common/b_page_scaffold";
import {
INITIAL_QUANTITY_TEMPLATES,
QUANTITY_CATEGORY_LABELS,
QuantityCategory,
QuantityItemTemplate,
} from "./B08_wf5_Quantity_Template";
import "./B08_wf5_Quantity_UI_Style.css";
/** locale 헬퍼 */
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
/** 현재 활성화된 공종 탭 */
let currentCategory: QuantityCategory = "earthwork";
/**
* B08 수량 산출 메인 렌더링 함수
*/
export async function renderB08Quantity(root: HTMLElement): Promise<void> {
// 1. 공통 Workflow Shell 렌더링
await renderPendingWorkflow(root, {
title: L("B08_Quantity_Title"),
steps: workflowSteps(),
activeStep: 5,
});
// 2. 본문 컨테이너 슬롯 확보 (Workflow Shell 내부 본문 영역)
const bodyContainer = root.querySelector(".workflow-body") || root;
bodyContainer.innerHTML = ""; // 기존 준비중 메시지 대체
const container = document.createElement("div");
container.className = "b08-quantity-container";
bodyContainer.appendChild(container);
// 3. UI 컴포넌트 조합
renderSummaryGrid(container);
renderTabBar(container);
renderTableArea(container);
renderActionBar(container);
}
/**
* 1. 상단 요약 카드 그리드 렌더링 (입력 데이터 펜딩 상태)
*/
function renderSummaryGrid(parent: HTMLElement): void {
const grid = document.createElement("div");
grid.className = "b08-summary-grid";
const summaryItems = [
{ title: "전체 엑셀 템플릿", val: "총 145개 항목", desc: "6개 세부 공종 100% 매핑 완료" },
{ title: "단가산출 / 일위대가", val: "65개 표준공종", desc: "토공 40종 + 일위대가 25종" },
{ title: "자재 & 노무비", val: "51개 자재/인력", desc: "자재 37종 + 노무인부 14종" },
{ title: "중기 & 일식/폐기물", val: "29개 중기/일식", desc: "중기시간 24종 + 폐기물 5종" },
];
grid.innerHTML = summaryItems
.map(
(item) => `
<div class="b08-card">
<div class="b08-card-header">${item.title}</div>
<div class="b08-card-value empty-state">${item.val}</div>
<div class="b08-card-sub">${item.desc}</div>
</div>
`,
)
.join("");
parent.appendChild(grid);
}
/**
* 2. 공종 탭 바 렌더링
*/
function renderTabBar(parent: HTMLElement): void {
const tabBar = document.createElement("div");
tabBar.className = "b08-tab-bar";
const categories: QuantityCategory[] = [
"earthwork",
"structure",
"material",
"equipment",
"waste",
"labor",
];
categories.forEach((cat) => {
const btn = document.createElement("button");
btn.className = `b08-tab-btn ${cat === currentCategory ? "active" : ""}`;
btn.textContent = QUANTITY_CATEGORY_LABELS[cat].ko;
btn.addEventListener("click", () => {
currentCategory = cat;
// 탭 업데이트
tabBar.querySelectorAll(".b08-tab-btn").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
// 테이블 재렌더링
const tableArea = parent.querySelector("#b08-table-area");
if (tableArea) {
tableArea.innerHTML = "";
renderTableContent(tableArea as HTMLElement);
}
});
tabBar.appendChild(btn);
});
parent.appendChild(tabBar);
}
/**
* 3. 수량 집계 데이터 테이블 영역 렌더링
*/
function renderTableArea(parent: HTMLElement): void {
const tableArea = document.createElement("div");
tableArea.id = "b08-table-area";
tableArea.className = "b08-table-wrapper";
parent.appendChild(tableArea);
renderTableContent(tableArea);
}
/**
* 테이블 내용 렌더링 (현재 카테고리 기준)
*/
function renderTableContent(parent: HTMLElement): void {
const items = INITIAL_QUANTITY_TEMPLATES.filter((item) => item.category === currentCategory);
const table = document.createElement("table");
table.className = "b08-table";
table.innerHTML = `
<thead>
<tr>
<th style="width: 60px;">호표</th>
<th>명칭</th>
<th>규격</th>
<th style="width: 60px;">단위</th>
<th>설계 산출 수량 (raw_qty)</th>
<th style="width: 80px;">할증률</th>
<th>최종 산출 수량</th>
<th>엑셀 수식 및 산출 근거 (Formula)</th>
<th>연동 시트 / 코드</th>
</tr>
</thead>
<tbody>
${items.map((item) => renderTableRow(item)).join("")}
</tbody>
`;
parent.appendChild(table);
}
/**
* 테이블 행 HTML 생성
*/
function renderTableRow(item: QuantityItemTemplate): string {
const rawQtyDisplay =
item.raw_qty !== null
? item.raw_qty.toLocaleString()
: `<span class="b08-empty-val">[입력 대기]</span>`;
const calcQtyDisplay =
item.calc_qty !== null
? item.calc_qty.toLocaleString()
: `<span class="b08-empty-val">[수식 자동계산]</span>`;
const codeBadge = item.code_ref
? `<span class="b08-badge b08-badge-code">${item.code_ref}</span>`
: "";
return `
<tr>
<td>${item.item_no}</td>
<td><strong>${item.name}</strong></td>
<td>${item.spec}</td>
<td>${item.unit}</td>
<td>${rawQtyDisplay}</td>
<td>${item.allowance_rate}</td>
<td>${calcQtyDisplay}</td>
<td><span class="b08-formula-cell">${item.formula_desc}</span></td>
<td>
<span class="b08-badge b08-badge-sheet">${item.excel_ref_sheet}</span>
${codeBadge}
</td>
</tr>
`;
}
/**
* 4. 하단 컨트롤/액션 바 렌더링
*/
function renderActionBar(parent: HTMLElement): void {
const actionBar = document.createElement("div");
actionBar.className = "b08-action-bar";
actionBar.innerHTML = `
<div class="b08-info-text">
💡 <strong>엑셀 내역서 145개 전체 템플릿 매핑 완료</strong>: 입력 수량값은 차후 이전 워크플로우(B07 등) 및 수식 엔진에 의해 자동 기입됩니다.
</div>
<button class="b08-btn-confirm">
수량 산출 확정 및 B09 견적서 생성 >
</button>
`;
const confirmBtn = actionBar.querySelector(".b08-btn-confirm");
confirmBtn?.addEventListener("click", () => {
alert("B08 수량 템플릿 항목(145종)이 확정되었습니다. B09 견적 파이프라인으로 전달됩니다.");
});
parent.appendChild(actionBar);
}