/* ============================================================================= * B08_Quantity_UI_Earthwork.ts * 토공 탭(PLAN 61-3 · 69-2 · 69-3) — 보조 탭 넷 = 토공집계표 · 토적표 · 평균운반거리 · 종방향 무대운반거리. * * GET …/quantity/earthwork 한 번(서버 단독 · 읽기만) — 네 장이 같은 응답을 나눠 그림. * 토적표 = 줄 측점 · 칸 = 사용자 견본(절토 토사 · 연암 · 측구파기 토사 · 암 각 단면적 · 입적 · * 보정량 · 보정량계 · 성토 · 유용토 · 차인토량 · 누가토량 · 면고르기 · 지장목제거 · 제근 · 노면정리 각 * 거리 · 면적) · 맨 아래 합계 줄. 첫 측점은 앞이 없어 입적 · 면적이 빈 값(—). * 표 = 공용 많은 자료 표(`ui_template_data_table` · PLAN 70) · 카드 안 · 나머지 셋은 `…_Earthwork_Sheets`. * 절토 연암 · 경암 = 암 × 좌측 패널 비율 · 암 C = 비율 가중 — 조작 중 같은 식으로 바로 다시 셈 * (`…_Options_Calc` · PLAN 122 · 125-1). * 자료 읽기 · 표 짜기 = `B08_Quantity_Earthwork_Tables`(화면 없음 · 인쇄 미리보기도 씀 · 302-3) * ========================================================================== */ import { createButton, createCard } from "@ui/ui_template_elements"; import { createDataTable } from "@ui/ui_template_data_table"; import { footNote } from "./B08_Quantity_UI_Foot"; import { EARTHWORK_SHEETS, earthworkSheetOptions, loadEarthwork, type EarthworkTable, } from "./B08_Quantity_Earthwork_Tables"; import { renderEarthworkSummary, renderFreeHaul, renderHaulDistance, } from "./B08_Quantity_UI_Earthwork_Sheets"; import { onOptionsChange } from "./B08_Quantity_UI_Options"; export function note(text: string): HTMLElement { const p = document.createElement("p"); p.className = "ui-general-block__empty"; p.textContent = text; return p; } /** 표 한 장 — 공용 컨테이너(카드) 안에 공용 표 · 아래 줄 · 맨 아래 안내 · 경고 줄(202-1) */ export function tableCard( info: HTMLElement[], table: HTMLElement, after: HTMLElement[] = [], ): HTMLElement { return createCard({ body: [table, ...after, ...info] }); } function renderSheet(host: HTMLElement, table: EarthworkTable, interval: number): void { if (!table.rows.length) { host.replaceChildren(note("측점 단면적이 아직 없습니다 — 횡단 설계를 먼저 마치세요.")); return; } const flagged = table.rows.filter((row) => row.notes.length); const info = [ footNote( `노선 ${table.route_id}${table.confirmed ? "(확정)" : "(확정 전 최신 노선)"} · ` + `측점 ${table.station_count} · 단위 m · ㎡ · ㎥`, ), ...(flagged.length ? [footNote(`* ${flagged[0].notes[0]} — ${flagged.length}곳`)] : []), ]; const grid = createDataTable({ ...earthworkSheetOptions(table, interval), fillViewport: true }); grid.root.dataset.b08Earthwork = "table"; host.replaceChildren(tableCard(info, grid.root)); } const SHEETS = EARTHWORK_SHEETS; type SheetId = (typeof SHEETS)[number]["id"]; export async function renderEarthwork(host: HTMLElement, projectId: string): Promise { host.replaceChildren(note("토공 수량을 만드는 중…")); let loaded: Awaited>; try { loaded = await loadEarthwork(projectId); } catch (error) { host.replaceChildren(note(`토공 수량을 불러오지 못했습니다 — ${String(error)}`)); return; } const { table, interval, applyRock } = loaded; const tabs = document.createElement("div"); tabs.className = "b08-quantity__tabs ui-subtabs b08-earthwork__tabs"; tabs.setAttribute("role", "tablist"); const view = document.createElement("div"); let current: SheetId = "summary"; const select = (picked: SheetId): void => { current = picked; tabs.replaceChildren( ...SHEETS.map(({ id, label }) => { const button = createButton({ 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.b08EarthworkSheet = id; return button; }), ); if (picked === "sheet") renderSheet(view, table, interval); else if (picked === "summary") renderEarthworkSummary(view, table); else if (picked === "haul") renderHaulDistance(view, table); else renderFreeHaul(view, table); }; host.replaceChildren(tabs, view); select("summary"); onOptionsChange(host, () => { applyRock(); select(current); }); }