Files
Aislo/B08_Quantity/B08_Quantity_UI_Page.ts
T
eomsangdonandClaude Opus 5.5 6a9506f1fb feat(b08): 좌측 패널 컨테이너 셋(구조물 · 토공 · 수리) + [저장] 줄 · 옵션 저장 길 GET/PUT quantity/options(122-1)
- 구조물 › 배관 1본 길이 · 토공 › 연암 · 경암 % 입력(공용 compact 입력 · 접이 카드)
- 조작 = 캐시 b08-options · [저장] = 프로젝트 층 M02 구조물집계표 변수 · project_settings rock_ratios_pct
- 브라우저 짝 식 파일 B08_Quantity_Options_Calc.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
2026-10-08 18:43:58 +09:00

142 lines
5.8 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Page.ts
* 로그인 후 08: 5차 워크플로우 (수량 산출) — 위 탭 화면 (PLAN 61)
*
* 탭 = 구조물 · 토공 · 수리집수면적표 (PLAN 95-1). 위 탭 단추 = 공용 기본 크기(md) · 보조 탭 단추 = sm(PLAN 69-1).
* 구조물 아래 보조 탭 = 구조물 집계표(61-1) · 총괄자재수량집계표 · 콘크리트소요자재집계표 ·
* 자재수량집계표(셋은 아직 빈 탭) — 구조물도는 집계표 행을 눌러 여는 모달(95-3).
* 옛 수량 산출 코드(`old_code/B08_Quantity/`)는 가져오지 않음 — 새로 씀.
* 좌측 패널 = 메인 탭과 같은 컨테이너 셋 + [저장] 줄(`B08_Quantity_UI_Options` · PLAN 122).
* ========================================================================== */
import "@ui/ui_template_general_layout.css";
import "./B08_Quantity_UI_Page.css";
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
import { createButton } from "@ui/ui_template_elements";
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
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 { renderStructureSummary } from "./B08_Quantity_UI_Summary";
import { renderHydro } from "./B08_Quantity_UI_Hydro";
import { renderEarthwork } from "./B08_Quantity_UI_Earthwork";
import { createOptionsPanel } from "./B08_Quantity_UI_Options";
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
const TABS = [
{ id: "structure", label: "B08_Quantity_Tab_Structure" },
{ id: "earthwork", label: "B08_Quantity_Tab_Earthwork" },
{ id: "hydrology", label: "B08_Quantity_Tab_Hydrology" },
] as const;
type TabId = (typeof TABS)[number]["id"];
/** 구조물 탭 보조 탭 — 첫째만 내용이 있음. */
const SHEETS = [
{ id: "summary", label: "B08_Quantity_Tab_StructureSummary" },
{ id: "material", label: "B08_Quantity_Tab_MaterialTotal" },
{ id: "concrete", label: "B08_Quantity_Tab_ConcreteMaterial" },
{ id: "quantity", label: "B08_Quantity_Tab_MaterialQuantity" },
] as const;
type SheetId = (typeof SHEETS)[number]["id"];
function pendingNote(): HTMLElement {
const pending = document.createElement("p");
pending.className = "ui-general-block__empty";
pending.textContent = L("B08_Quantity_Tab_Pending");
return pending;
}
/** 구조물 탭 — 보조 탭 줄 + 몸통(보조 탭마다 새 몸통 · 늦은 응답이 다른 보조 탭을 덮지 않게). */
function renderStructure(host: HTMLElement, projectId: string | null): void {
const tabs = document.createElement("div");
tabs.className = "b08-quantity__tabs b08-structure__tabs";
tabs.setAttribute("role", "tablist");
const view = document.createElement("div");
const select = (picked: SheetId): void => {
tabs.replaceChildren(
...SHEETS.map(({ id, label }) => {
const button = createButton({
label: L(label),
variant: id === picked ? "filled" : "ghost",
size: "sm",
onClick: () => id !== picked && select(id),
});
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", String(id === picked));
button.dataset.b08StructureSheet = id;
return button;
}),
);
const body = document.createElement("div");
view.replaceChildren(body);
if (picked === "summary" && projectId) void renderStructureSummary(body, projectId);
else body.append(pendingNote());
};
host.replaceChildren(tabs, view);
select("summary");
}
/** 탭마다 새 몸통 — 늦게 온 응답이 다른 탭 화면을 덮지 않게(떨어진 몸통에만 그림). */
function renderTab(host: HTMLElement, tab: TabId, projectId: string | null): void {
const view = document.createElement("div");
host.replaceChildren(view);
if (tab === "structure") return renderStructure(view, projectId);
if (tab === "hydrology" && projectId) return void renderHydro(view, projectId);
if (tab === "earthwork" && projectId) return void renderEarthwork(view, projectId);
view.append(pendingNote());
}
export function renderB08Quantity(root: HTMLElement): void {
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
const tabBar = document.createElement("div");
tabBar.className = "b08-quantity__tabs";
tabBar.setAttribute("role", "tablist");
const panel = document.createElement("div");
panel.className = "b08-quantity__panel";
panel.setAttribute("role", "tabpanel");
let current: TabId = "structure";
const select = (tab: TabId): void => {
current = tab;
tabBar.replaceChildren(
...TABS.map(({ id, label }) => {
const button = createButton({
label: L(label),
variant: id === tab ? "filled" : "ghost",
onClick: () => id !== tab && select(id),
});
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", String(id === tab));
button.dataset.b08Tab = id;
return button;
}),
);
renderTab(panel, tab, projectId);
};
const body = document.createElement("div");
body.className = "b08-quantity";
body.append(tabBar, panel);
select("structure");
const layout = createWorkflowLayout({
title: L("B08_Quantity_Title"),
steps: workflowSteps(),
activeStep: 5,
mainContent: body,
// [저장] 뒤 열린 탭을 서버 정본으로 다시 그림
leftPanel: createOptionsPanel(projectId, () => select(current)),
routes: WORKFLOW_STEP_ROUTES,
onStepClick: (stepIndex: number) => {
if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);
},
});
root.append(layout.root);
}