feat(B08): 토공 보조 탭 넷을 공용 많은 자료 표로 (PLAN 69-2 · 69-3 · 70-2)
- 토적표 32칸 · 측점 고정 · 묶음 머리 석 줄 · 합계 줄 / 토공집계표 · 운반 시트도 같은 표 · 카드 안 - 옛 토적표 전용 CSS 걷어냄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
* GET …/quantity/earthwork 한 번(서버 단독 · 읽기만) — 네 장이 같은 응답을 나눠 그림.
|
||||
* 토적표 = 줄 측점 · 칸 = 사용자 견본(절토 토사 · 암석 · 측구터파기 토사 · 암석 각 단면적 · 입적 ·
|
||||
* 보정량 · 보정량계 · 성토 · 유용토 · 차인토량 · 누가토량 · 면고르기 · 지장목제거 · 제근 · 노면정리 각
|
||||
* 거리 · 면적) · 맨 아래 합계 줄. 첫 측점은 앞이 없어 입적 · 면적이 「-」.
|
||||
* 표 모양은 1탭 집계표 것(`b08-summary__*`)을 다시 씀 · 나머지 셋은 `B08_Quantity_UI_Earthwork_Sheets`.
|
||||
* 거리 · 면적) · 맨 아래 합계 줄. 첫 측점은 앞이 없어 입적 · 면적이 빈 값(—).
|
||||
* 표 = 공용 많은 자료 표(`ui_template_data_table` · PLAN 70) · 카드 안 · 나머지 셋은 `…_Earthwork_Sheets`.
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { createButton, createCard } from "@ui/ui_template_elements";
|
||||
import { createDataTable, type DataColumn, type DataRow } from "@ui/ui_template_data_table";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { stationInterval } from "./B08_Quantity_UI_Summary";
|
||||
import {
|
||||
@@ -81,39 +82,51 @@ export interface EarthworkTable {
|
||||
haul_saved: boolean;
|
||||
}
|
||||
|
||||
const ROCK_KIND_LABEL: Record<string, string> = { ripping_rock: "리핑암", blasting_rock: "발파암" };
|
||||
const TRIPLE = ["단면적", "입적", "보정량"];
|
||||
const FACE_GROUPS: Array<[string, Array<[string, string]>]> = [
|
||||
[
|
||||
"면고르기",
|
||||
[
|
||||
["grading_fill", "성토면"],
|
||||
["grading_cut", "절토면"],
|
||||
],
|
||||
],
|
||||
[
|
||||
"지장목제거",
|
||||
[
|
||||
["tree_removal_fill", "성토"],
|
||||
["tree_removal_cut", "절토"],
|
||||
],
|
||||
],
|
||||
["제근", [["grubbing", ""]]],
|
||||
["노면정리", [["road_surface", ""]]],
|
||||
/** 칸 묶음 — [머리 묶음, 키 앞머리] · 단면적 · 입적 · 보정량 셋 */
|
||||
const TRIPLES: Array<[string[], string]> = [
|
||||
[["절토", "토사"], "cut_soil"],
|
||||
[["절토", "암석"], "cut_rock"],
|
||||
[["측구터파기", "토사"], "ditch_soil"],
|
||||
[["측구터파기", "암석"], "ditch_rock"],
|
||||
];
|
||||
/** 면 계열 — [머리 묶음, 키] · 거리 · 면적 둘 */
|
||||
const FACES: Array<[string[], string]> = [
|
||||
[["면고르기", "성토면"], "grading_fill"],
|
||||
[["면고르기", "절토면"], "grading_cut"],
|
||||
[["지장목제거", "성토"], "tree_removal_fill"],
|
||||
[["지장목제거", "절토"], "tree_removal_cut"],
|
||||
[["제근"], "grubbing"],
|
||||
[["노면정리"], "road_surface"],
|
||||
];
|
||||
const FACE_KEYS = FACE_GROUPS.flatMap(([, faces]) => faces.map(([key]) => key));
|
||||
const DASH = "-";
|
||||
|
||||
const num = (value: number | undefined | null): string =>
|
||||
value == null || !Number.isFinite(value) ? "" : value.toFixed(2);
|
||||
const n2 = (key: string, label: string, group?: string[]): DataColumn => ({
|
||||
key,
|
||||
label,
|
||||
group,
|
||||
kind: "number",
|
||||
decimals: 2,
|
||||
});
|
||||
|
||||
export function cell(tag: "th" | "td", text: string, span?: { row?: number; col?: number }) {
|
||||
const node = document.createElement(tag);
|
||||
node.textContent = text;
|
||||
if (span?.row) node.rowSpan = span.row;
|
||||
if (span?.col) node.colSpan = span.col;
|
||||
return node;
|
||||
}
|
||||
/** 견본 차례 32칸 */
|
||||
const COLUMNS: DataColumn[] = [
|
||||
{ key: "station", label: "측점", width: 72 },
|
||||
n2("distance", "거리"),
|
||||
...TRIPLES.flatMap(([group, key]) => [
|
||||
n2(`${key}_area`, "단면적", group),
|
||||
n2(`${key}_volume`, "입적", group),
|
||||
n2(`${key}_adjusted`, "보정량", group),
|
||||
]),
|
||||
n2("adjusted_total", "보정량계"),
|
||||
n2("fill_area", "단면적", ["성토"]),
|
||||
n2("fill_volume", "입적", ["성토"]),
|
||||
n2("diverted", "유용토"),
|
||||
n2("balance", "차인토량"),
|
||||
n2("cumulative", "누가토량"),
|
||||
...FACES.flatMap(([group, key]) => [
|
||||
n2(`${key}_length`, "거리", group),
|
||||
n2(`${key}_area`, "면적", group),
|
||||
]),
|
||||
];
|
||||
|
||||
export function note(text: string): HTMLElement {
|
||||
const p = document.createElement("p");
|
||||
@@ -122,125 +135,63 @@ export function note(text: string): HTMLElement {
|
||||
return p;
|
||||
}
|
||||
|
||||
/** 표 한 장 — 1탭 집계표 모양 · 공용 컨테이너(카드) 안 */
|
||||
/** 표 한 장 — 공용 컨테이너(카드) 안에 설명 · 공용 표 · 아래 줄 */
|
||||
export function tableCard(
|
||||
info: HTMLElement[],
|
||||
className: string,
|
||||
parts: HTMLElement[],
|
||||
table: HTMLElement,
|
||||
after: HTMLElement[] = [],
|
||||
): HTMLElement {
|
||||
const el = document.createElement("table");
|
||||
el.className = `ui-general-block__table b08-summary__table ${className}`;
|
||||
el.append(...parts);
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "ui-general-block__table-wrap b08-summary__wrap";
|
||||
wrap.append(el);
|
||||
return createCard({ body: [...info, wrap] });
|
||||
return createCard({ body: [...info, table, ...after] });
|
||||
}
|
||||
|
||||
/** 머리 석 줄 — 견본 차례 · 32칸 */
|
||||
function head(): HTMLTableSectionElement {
|
||||
const station = cell("th", "측점", { row: 3 });
|
||||
station.classList.add("b08-summary__station");
|
||||
const top = document.createElement("tr");
|
||||
top.append(
|
||||
station,
|
||||
cell("th", "거리", { row: 3 }),
|
||||
cell("th", "절토", { col: 6 }),
|
||||
cell("th", "측구터파기", { col: 6 }),
|
||||
cell("th", "보정량계", { row: 3 }),
|
||||
cell("th", "성토", { col: 2, row: 2 }),
|
||||
cell("th", "유용토", { row: 3 }),
|
||||
cell("th", "차인토량", { row: 3 }),
|
||||
cell("th", "누가토량", { row: 3 }),
|
||||
...FACE_GROUPS.map(([label, faces]) =>
|
||||
cell("th", label, { col: faces.length * 2, row: faces.length > 1 ? 1 : 2 }),
|
||||
),
|
||||
);
|
||||
const mid = document.createElement("tr");
|
||||
for (let i = 0; i < 2; i += 1)
|
||||
mid.append(cell("th", "토사", { col: 3 }), cell("th", "암석", { col: 3 }));
|
||||
for (const [, faces] of FACE_GROUPS) {
|
||||
if (faces.length > 1) mid.append(...faces.map(([, label]) => cell("th", label, { col: 2 })));
|
||||
function stationRow(row: EarthworkRow, first: boolean, interval: number): DataRow {
|
||||
// 첫 측점은 앞이 없어 입적 · 보정량 · 면적이 없음(빈 값)
|
||||
const v = (value: number): number | null => (first ? null : value);
|
||||
const out: DataRow = {
|
||||
station: stationLabel(row.chainage_m, interval) + (row.notes.length ? " *" : ""),
|
||||
distance: row.distance_m,
|
||||
adjusted_total: v(row.adjusted_total_m3),
|
||||
fill_area: row.fill_area_m2,
|
||||
fill_volume: v(row.fill_volume_m3),
|
||||
diverted: v(row.diverted_m3),
|
||||
balance: v(row.balance_m3),
|
||||
cumulative: v(row.cumulative_m3),
|
||||
};
|
||||
const r = row as unknown as Record<string, number>;
|
||||
for (const [, key] of TRIPLES) {
|
||||
out[`${key}_area`] = r[`${key}_area_m2`];
|
||||
out[`${key}_volume`] = v(r[`${key}_volume_m3`]);
|
||||
out[`${key}_adjusted`] = v(r[`${key}_adjusted_m3`]);
|
||||
}
|
||||
const sub = document.createElement("tr");
|
||||
for (let i = 0; i < 4; i += 1) sub.append(...TRIPLE.map((t) => cell("th", t)));
|
||||
sub.append(cell("th", "단면적"), cell("th", "입적"));
|
||||
for (const _ of FACE_KEYS) sub.append(cell("th", "거리"), cell("th", "면적"));
|
||||
const thead = document.createElement("thead");
|
||||
thead.append(top, mid, sub);
|
||||
return thead;
|
||||
}
|
||||
|
||||
function bodyRow(row: EarthworkRow, first: boolean, interval: number): HTMLTableRowElement {
|
||||
const tr = document.createElement("tr");
|
||||
tr.dataset.b08Earthwork = String(row.chainage_m);
|
||||
const station = cell(
|
||||
"th",
|
||||
stationLabel(row.chainage_m, interval) + (row.notes.length ? " *" : ""),
|
||||
);
|
||||
station.className = "b08-summary__station";
|
||||
if (row.notes.length) station.title = row.notes.join("\n");
|
||||
// 첫 측점은 앞이 없어 입적 · 보정량 · 면적이 없음(견본 「-」)
|
||||
const v = (value: number): string => (first ? DASH : num(value));
|
||||
const triple = (area: number, volume: number, adjusted: number) => [
|
||||
num(area),
|
||||
v(volume),
|
||||
v(adjusted),
|
||||
];
|
||||
const values = [
|
||||
num(row.distance_m),
|
||||
...triple(row.cut_soil_area_m2, row.cut_soil_volume_m3, row.cut_soil_adjusted_m3),
|
||||
...triple(row.cut_rock_area_m2, row.cut_rock_volume_m3, row.cut_rock_adjusted_m3),
|
||||
...triple(row.ditch_soil_area_m2, row.ditch_soil_volume_m3, row.ditch_soil_adjusted_m3),
|
||||
...triple(row.ditch_rock_area_m2, row.ditch_rock_volume_m3, row.ditch_rock_adjusted_m3),
|
||||
v(row.adjusted_total_m3),
|
||||
num(row.fill_area_m2),
|
||||
v(row.fill_volume_m3),
|
||||
v(row.diverted_m3),
|
||||
v(row.balance_m3),
|
||||
v(row.cumulative_m3),
|
||||
...FACE_KEYS.flatMap((key) => {
|
||||
const face = row.faces?.[key];
|
||||
return [num(face?.length_m), first ? DASH : num(face?.area_m2)];
|
||||
}),
|
||||
];
|
||||
const cells = values.map((text) => cell("td", text));
|
||||
// 암석 칸 — 어느 갈래(환산계수)로 셌는지 마우스를 올려 봄
|
||||
if (row.cut_rock_area_m2 > 0) cells[4].title = ROCK_KIND_LABEL[row.cut_rock_kind] ?? "";
|
||||
tr.append(station, ...cells);
|
||||
return tr;
|
||||
for (const [, key] of FACES) {
|
||||
out[`${key}_length`] = row.faces?.[key]?.length_m ?? null;
|
||||
out[`${key}_area`] = first ? null : (row.faces?.[key]?.area_m2 ?? null);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 합계 줄 — 단면적 · 거리는 합이 뜻이 없어 비움(옛 엔진 `totals` 와 같음) · 누가토량 = 마지막 줄 */
|
||||
function totalRow(table: EarthworkTable): HTMLTableRowElement {
|
||||
function totalRow(table: EarthworkTable): DataRow {
|
||||
const t = table.totals;
|
||||
const last = table.rows[table.rows.length - 1];
|
||||
const triple = (key: string) => ["", num(t[`${key}_volume_m3`]), num(t[`${key}_adjusted_m3`])];
|
||||
const values = [
|
||||
num(t.distance_m),
|
||||
...triple("cut_soil"),
|
||||
...triple("cut_rock"),
|
||||
...triple("ditch_soil"),
|
||||
...triple("ditch_rock"),
|
||||
num(t.adjusted_total_m3),
|
||||
"",
|
||||
num(t.fill_volume_m3),
|
||||
num(t.diverted_m3),
|
||||
num(t.balance_m3),
|
||||
num(last?.cumulative_m3),
|
||||
// 값이 하나도 없는 칸(제근 — B07 에 값 없음)은 합계도 비움
|
||||
...FACE_KEYS.flatMap((key) => [
|
||||
"",
|
||||
table.rows.some((row) => row.faces?.[key]?.area_m2 != null) ? num(t[`${key}_area_m2`]) : "",
|
||||
]),
|
||||
];
|
||||
const tr = document.createElement("tr");
|
||||
tr.className = "b08-earthwork__total";
|
||||
tr.dataset.b08EarthworkTotal = "";
|
||||
const label = cell("th", "합계");
|
||||
label.className = "b08-summary__station";
|
||||
tr.append(label, ...values.map((v) => cell("td", v)));
|
||||
return tr;
|
||||
const out: DataRow = {
|
||||
station: "합계",
|
||||
distance: t.distance_m,
|
||||
adjusted_total: t.adjusted_total_m3,
|
||||
fill_volume: t.fill_volume_m3,
|
||||
diverted: t.diverted_m3,
|
||||
balance: t.balance_m3,
|
||||
cumulative: table.rows[table.rows.length - 1]?.cumulative_m3,
|
||||
};
|
||||
for (const [, key] of TRIPLES) {
|
||||
out[`${key}_volume`] = t[`${key}_volume_m3`];
|
||||
out[`${key}_adjusted`] = t[`${key}_adjusted_m3`];
|
||||
}
|
||||
// 값이 하나도 없는 칸(제근 — B07 에 값 없음)은 합계도 비움
|
||||
for (const [, key] of FACES) {
|
||||
const any = table.rows.some((row) => row.faces?.[key]?.area_m2 != null);
|
||||
out[`${key}_area`] = any ? t[`${key}_area_m2`] : null;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function renderSheet(host: HTMLElement, table: EarthworkTable, interval: number): void {
|
||||
@@ -248,20 +199,26 @@ function renderSheet(host: HTMLElement, table: EarthworkTable, interval: number)
|
||||
host.replaceChildren(note("측점 단면적이 아직 없습니다 — 횡단 설계를 먼저 마치세요."));
|
||||
return;
|
||||
}
|
||||
const c = (kind: string): string => num(table.conversion_factors[kind]?.compacted);
|
||||
const info = note(
|
||||
`평균단면적법 · 노선 ${table.route_id}${table.confirmed ? "(확정)" : "(확정 전 최신 노선)"} · ` +
|
||||
`측점 ${table.station_count} · 다짐 환산계수 토사 ${c("soil")} · 리핑암 ${c("ripping_rock")} · ` +
|
||||
`발파암 ${c("blasting_rock")} · 보정량계 = 절토 보정량 · 면 거리 = B07 횡단도 수량 · ` +
|
||||
`단위 m · ㎡ · ㎥ · * = 사유 있음(마우스를 올림)`,
|
||||
);
|
||||
const tbody = document.createElement("tbody");
|
||||
tbody.append(...table.rows.map((row, i) => bodyRow(row, i === 0, interval)));
|
||||
const tfoot = document.createElement("tfoot");
|
||||
tfoot.append(totalRow(table));
|
||||
const card = tableCard([info], "b08-earthwork__table", [head(), tbody, tfoot]);
|
||||
card.querySelector("table")!.dataset.b08Earthwork = "table";
|
||||
host.replaceChildren(card);
|
||||
const c = (kind: string): string => table.conversion_factors[kind]?.compacted.toFixed(2) ?? "";
|
||||
const flagged = table.rows.filter((row) => row.notes.length);
|
||||
const info = [
|
||||
note(
|
||||
`평균단면적법 · 노선 ${table.route_id}${table.confirmed ? "(확정)" : "(확정 전 최신 노선)"} · ` +
|
||||
`측점 ${table.station_count} · 다짐 환산계수 토사 ${c("soil")} · 리핑암 ${c("ripping_rock")} · ` +
|
||||
`발파암 ${c("blasting_rock")} · 보정량계 = 절토 보정량 · 면 거리 = B07 횡단도 수량 · ` +
|
||||
`단위 m · ㎡ · ㎥`,
|
||||
),
|
||||
...(flagged.length ? [note(`* ${flagged[0].notes[0]} — ${flagged.length}곳`)] : []),
|
||||
];
|
||||
const grid = createDataTable({
|
||||
columns: COLUMNS,
|
||||
rows: table.rows.map((row, i) => stationRow(row, i === 0, interval)),
|
||||
frozen: 1,
|
||||
total: totalRow(table),
|
||||
caption: "토적표",
|
||||
});
|
||||
grid.root.dataset.b08Earthwork = "table";
|
||||
host.replaceChildren(tableCard(info, grid.root));
|
||||
}
|
||||
|
||||
const SHEETS = [
|
||||
|
||||
@@ -4,60 +4,45 @@
|
||||
*
|
||||
* 칸 · 식 = 거창 실무 워크북 같은 이름 시트 그대로 · 값은 서버가 셈(`…_Engine_Earthwork_Summary`).
|
||||
* 운반 Q · L = 저장된 유토곡선 배분(B07 토적도와 같은 정본) — 없으면 그 사유만 보임.
|
||||
* 표 = 공용 많은 자료 표(`ui_template_data_table`).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
cell,
|
||||
note,
|
||||
tableCard,
|
||||
type EarthworkTable,
|
||||
type HaulList,
|
||||
} from "./B08_Quantity_UI_Earthwork";
|
||||
import { createDataTable, type DataColumn, type DataRow } from "@ui/ui_template_data_table";
|
||||
import { note, tableCard, type EarthworkTable, type HaulList } from "./B08_Quantity_UI_Earthwork";
|
||||
|
||||
const fmt = (value: number | null | undefined): string =>
|
||||
value == null || !Number.isFinite(value)
|
||||
? "—"
|
||||
: value.toLocaleString("ko-KR", { maximumFractionDigits: 2 });
|
||||
|
||||
function row(cells: HTMLElement[], className = ""): HTMLTableRowElement {
|
||||
const tr = document.createElement("tr");
|
||||
if (className) tr.className = className;
|
||||
tr.append(...cells);
|
||||
return tr;
|
||||
}
|
||||
const SUMMARY_COLUMNS: DataColumn[] = [
|
||||
{ key: "group", label: "구분", width: 120 },
|
||||
{ key: "kind", label: "공종", width: 72 },
|
||||
{ key: "spec", label: "규격", width: 128 },
|
||||
{ key: "unit", label: "단위", width: 56 },
|
||||
{ key: "value", label: "계", kind: "number", decimals: 2, width: 112 },
|
||||
{ key: "note", label: "비고", width: 200 },
|
||||
];
|
||||
|
||||
function station(text: string, span?: { row?: number; col?: number }): HTMLElement {
|
||||
const th = cell("th", text, span);
|
||||
th.className = "b08-summary__station";
|
||||
return th;
|
||||
}
|
||||
|
||||
/** 토공집계표 — 구분 · 공종 · 규격 · 단위 · 계 · 비고 · 같은 구분은 한 칸으로 묶음 */
|
||||
/** 토공집계표 — 구분 · 공종 · 규격 · 단위 · 계 · 비고 */
|
||||
export function renderEarthworkSummary(host: HTMLElement, table: EarthworkTable): void {
|
||||
const thead = document.createElement("thead");
|
||||
thead.append(
|
||||
row([station("구분"), ...["공종", "규격", "단위", "계", "비고"].map((t) => cell("th", t))]),
|
||||
);
|
||||
const tbody = document.createElement("tbody");
|
||||
const rows = table.summary;
|
||||
rows.forEach((item, i) => {
|
||||
const cells: HTMLElement[] = [];
|
||||
if (rows[i - 1]?.group !== item.group) {
|
||||
let span = 1;
|
||||
while (rows[i + span]?.group === item.group) span += 1;
|
||||
cells.push(station(item.group, { row: span }));
|
||||
}
|
||||
const value = cell("td", fmt(item.value));
|
||||
value.className = "b08-earthwork__num";
|
||||
cells.push(cell("td", item.kind), cell("td", item.spec), cell("td", item.unit), value);
|
||||
cells.push(cell("td", item.note));
|
||||
tbody.append(row(cells));
|
||||
});
|
||||
const info = note(
|
||||
"토적표 합계 · 평균운반거리 · 종방향 무대운반거리를 실무 토공집계표 식대로 묶음 · " +
|
||||
"리핑암 = 연암 · 발파암 = 경암 · 구조물 쪽 줄은 구조물 수량에서(— = 아직 값 없음)",
|
||||
);
|
||||
host.replaceChildren(tableCard([info], "b08-earthwork__sheet", [thead, tbody]));
|
||||
const grid = createDataTable({
|
||||
columns: SUMMARY_COLUMNS,
|
||||
// 칸이 없는 글(공종 · 규격 · 비고)은 빈칸 — 공용 표는 "" 를 「—」(값 없음)로 그림
|
||||
rows: table.summary.map((row) => ({
|
||||
...row,
|
||||
kind: row.kind || " ",
|
||||
spec: row.spec || " ",
|
||||
note: row.note || " ",
|
||||
})),
|
||||
frozen: 1,
|
||||
caption: "토공집계표",
|
||||
});
|
||||
host.replaceChildren(tableCard([info], grid.root));
|
||||
}
|
||||
|
||||
function haulNote(table: EarthworkTable): HTMLElement[] {
|
||||
@@ -73,42 +58,41 @@ function haulSheet(
|
||||
groups: Array<[string, HaulList]>,
|
||||
info: string,
|
||||
): void {
|
||||
const thead = document.createElement("thead");
|
||||
thead.append(
|
||||
row([station("번호", { row: 2 }), ...groups.map(([label]) => cell("th", label, { col: 3 }))]),
|
||||
row(groups.flatMap(() => ["토량(Q)", "거리(L)", "Q×L"].map((t) => cell("th", t)))),
|
||||
);
|
||||
const count = Math.max(0, ...groups.map(([, list]) => list.rows.length));
|
||||
const tbody = document.createElement("tbody");
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const cells = groups.flatMap(([, list]) => {
|
||||
const item = list.rows[i];
|
||||
return item ? [fmt(item.q), fmt(item.l), fmt(item.ql)] : ["", "", ""];
|
||||
});
|
||||
tbody.append(row([station(String(i + 1)), ...cells.map((t) => cell("td", t))]));
|
||||
}
|
||||
const tfoot = document.createElement("tfoot");
|
||||
tfoot.append(
|
||||
row(
|
||||
const columns: DataColumn[] = [
|
||||
{ key: "no", label: "번호", width: 56 },
|
||||
...groups.flatMap(([label], g) =>
|
||||
[
|
||||
station("합계"),
|
||||
...groups.flatMap(([, list]) => [fmt(list.q), "", fmt(list.ql)].map((t) => cell("td", t))),
|
||||
],
|
||||
"b08-earthwork__total",
|
||||
["q", "토량(Q)"],
|
||||
["l", "거리(L)"],
|
||||
["ql", "Q×L"],
|
||||
].map(([key, text]) => ({
|
||||
key: `${key}${g}`,
|
||||
label: text,
|
||||
group: [label],
|
||||
kind: "number" as const,
|
||||
decimals: 2,
|
||||
width: 104,
|
||||
})),
|
||||
),
|
||||
);
|
||||
];
|
||||
const count = Math.max(0, ...groups.map(([, list]) => list.rows.length));
|
||||
const rows: DataRow[] = Array.from({ length: count }, (_, i) => {
|
||||
const out: DataRow = { no: String(i + 1) };
|
||||
groups.forEach(([, list], g) => {
|
||||
const item = list.rows[i];
|
||||
if (item) Object.assign(out, { [`q${g}`]: item.q, [`l${g}`]: item.l, [`ql${g}`]: item.ql });
|
||||
});
|
||||
return out;
|
||||
});
|
||||
const total: DataRow = { no: "합계" };
|
||||
groups.forEach(([, list], g) => Object.assign(total, { [`q${g}`]: list.q, [`ql${g}`]: list.ql }));
|
||||
const averages = groups.map(([label, list], i) =>
|
||||
note(
|
||||
`${i + 1}) ${label} 평균운반거리: ${fmt(list.ql)} ÷ ${fmt(list.q)} = ${fmt(list.average_m)} M`,
|
||||
),
|
||||
);
|
||||
const card = tableCard([note(info), ...haulNote(table)], "b08-earthwork__haul", [
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
]);
|
||||
card.append(...averages);
|
||||
host.replaceChildren(card);
|
||||
const grid = createDataTable({ columns, rows, frozen: 1, total, caption: info });
|
||||
host.replaceChildren(tableCard([note(info), ...haulNote(table)], grid.root, averages));
|
||||
}
|
||||
|
||||
export function renderHaulDistance(host: HTMLElement, table: EarthworkTable): void {
|
||||
|
||||
@@ -67,33 +67,3 @@
|
||||
.b08-summary__name.is-missing {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* 토공 탭 — 수는 오른쪽 · 합계 줄은 아래에 붙어 굵게 */
|
||||
.b08-earthwork__table td,
|
||||
.b08-earthwork__haul td,
|
||||
.b08-earthwork__sheet .b08-earthwork__num {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 토적표 머리 셋째 줄 — 둘째 줄 아래에 붙음 */
|
||||
.b08-summary__table thead tr + tr + tr th {
|
||||
top: calc(2 * (1lh + 2 * var(--spacing-4, 4px) + 1px));
|
||||
}
|
||||
|
||||
.b08-earthwork__total th,
|
||||
.b08-earthwork__total td {
|
||||
position: sticky;
|
||||
/* 칸 테두리는 표(border-collapse) 것이라 붙은 칸과 같이 안 움직임 — 그림자로 선을 긋고
|
||||
아래로 1px 더 내려 뒤 줄이 비치지 않게 */
|
||||
bottom: -1px;
|
||||
z-index: 2;
|
||||
font-weight: 700;
|
||||
background: var(--color-surface-raised, var(--color-surface));
|
||||
box-shadow:
|
||||
inset 0 2px 0 var(--color-text-muted),
|
||||
0 1px 0 var(--color-surface-raised, var(--color-surface));
|
||||
}
|
||||
|
||||
.b08-earthwork__total th {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user