feat(B08): 구조물 집계표를 공용 많은 자료 표로(70-2)
- 집계표 = createDataTable — 머리 두 줄(조합 구분 · 이름) · 측점 열 고정 · 칸은 빈칸 · 빠진 조합 빨강 · 풍선 글 그대로 - 공용 표 하위 호환 옵션 — empty(빈 값 글) · 열 title · className · 빠진 항목 머리 색 · 틀 폭은 표만큼 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -24,6 +24,10 @@ export interface DataColumn {
|
||||
decimals?: number;
|
||||
/** 열 폭 px(기본 숫자 84 · 글 96) */
|
||||
width?: number;
|
||||
/** 맨 아래 머리 칸 풍선 글(기본 label) */
|
||||
title?: string;
|
||||
/** 맨 아래 머리 칸에 더할 class(예 빠진 항목 빨강) */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type DataRow = Record<string, unknown>;
|
||||
@@ -37,6 +41,8 @@ export interface DataTableOptions {
|
||||
total?: DataRow | null;
|
||||
/** 표 이름(화면 읽기용) */
|
||||
caption?: string;
|
||||
/** 빈 값 글(기본 「—」 · 사용자가 채울 칸은 "") */
|
||||
empty?: string;
|
||||
}
|
||||
|
||||
export interface DataTable {
|
||||
@@ -53,10 +59,14 @@ function colWidth(c: DataColumn): number {
|
||||
}
|
||||
|
||||
/** 값 한 칸 → [보일 글, 빈 값인지] */
|
||||
export function formatCell(v: unknown, fmt: Intl.NumberFormat | null): [string, boolean] {
|
||||
if (v === null || v === undefined || v === "") return [EMPTY, true];
|
||||
export function formatCell(
|
||||
v: unknown,
|
||||
fmt: Intl.NumberFormat | null,
|
||||
empty = EMPTY,
|
||||
): [string, boolean] {
|
||||
if (v === null || v === undefined || v === "") return [empty, true];
|
||||
if (fmt && typeof v === "number")
|
||||
return Number.isFinite(v) ? [fmt.format(v), false] : [EMPTY, true];
|
||||
return Number.isFinite(v) ? [fmt.format(v), false] : [empty, true];
|
||||
return [String(v), false];
|
||||
}
|
||||
|
||||
@@ -65,10 +75,13 @@ function headHtml(cols: DataColumn[], left: (number | null)[]): string {
|
||||
const same = (a: DataColumn, b: DataColumn, n: number) =>
|
||||
(b.group?.length ?? 0) > n &&
|
||||
a.group!.slice(0, n + 1).join("\u0000") === b.group!.slice(0, n + 1).join("\u0000");
|
||||
const stick = (from: number, to: number) =>
|
||||
left[to] !== null
|
||||
? ` class="is-frozen${left[to + 1] == null ? " is-edge" : ""}" style="left:${left[from]}px"`
|
||||
: "";
|
||||
const stick = (from: number, to: number, extra = "") => {
|
||||
const frz = left[to] !== null;
|
||||
const cls = [frz && "is-frozen", frz && left[to + 1] == null && "is-edge", extra]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return (cls ? ` class="${esc(cls)}"` : "") + (frz ? ` style="left:${left[from]}px"` : "");
|
||||
};
|
||||
const rows: string[] = [];
|
||||
for (let lv = 0; lv <= depth; lv++) {
|
||||
let tr = "";
|
||||
@@ -83,7 +96,8 @@ function headHtml(cols: DataColumn[], left: (number | null)[]): string {
|
||||
} else if (g === lv) {
|
||||
const num = c.kind === "number" ? ' data-num=""' : "";
|
||||
const span = depth - lv + 1;
|
||||
tr += `<th scope="col" rowspan="${span}"${num}${stick(j, j)} title="${esc(c.label)}">${esc(c.label)}</th>`;
|
||||
const tip = esc(c.title ?? c.label);
|
||||
tr += `<th scope="col" rowspan="${span}"${num}${stick(j, j, c.className)} title="${tip}">${esc(c.label)}</th>`;
|
||||
}
|
||||
}
|
||||
rows.push(`<tr>${tr}</tr>`);
|
||||
@@ -97,10 +111,11 @@ function rowHtml(
|
||||
left: (number | null)[],
|
||||
r: DataRow,
|
||||
alt = false,
|
||||
blank = EMPTY,
|
||||
): string {
|
||||
let tr = "";
|
||||
for (let j = 0; j < cols.length; j++) {
|
||||
const [txt, empty] = formatCell(r[cols[j].key], fmts[j]);
|
||||
const [txt, empty] = formatCell(r[cols[j].key], fmts[j], blank);
|
||||
const frz = left[j] !== null;
|
||||
const cls = [frz && "is-frozen", frz && left[j + 1] == null && "is-edge", empty && "is-empty"]
|
||||
.filter(Boolean)
|
||||
@@ -147,9 +162,10 @@ export function renderDataTableHtml(o: DataTableOptions, win?: RowWindow): strin
|
||||
const from = win ? Math.max(0, Math.min(win.from, n)) : 0;
|
||||
const to = win ? Math.max(from, Math.min(win.to, n)) : n;
|
||||
let body = win ? pad(from * win.rowH, cols.length) : "";
|
||||
for (let i = from; i < to; i++) body += rowHtml(cols, fmts, left, o.rows[i], i % 2 === 1);
|
||||
const blank = o.empty ?? EMPTY;
|
||||
for (let i = from; i < to; i++) body += rowHtml(cols, fmts, left, o.rows[i], i % 2 === 1, blank);
|
||||
if (win) body += pad((n - to) * win.rowH, cols.length);
|
||||
const foot = o.total ? `<tfoot>${rowHtml(cols, fmts, left, o.total)}</tfoot>` : "";
|
||||
const foot = o.total ? `<tfoot>${rowHtml(cols, fmts, left, o.total, false, blank)}</tfoot>` : "";
|
||||
const cap = o.caption ? `<caption>${esc(o.caption)}</caption>` : "";
|
||||
return (
|
||||
`<table class="ui-data-table__table" style="width:${x}px">${cap}<colgroup>${colgroup}</colgroup>` +
|
||||
|
||||
Reference in New Issue
Block a user