feat(B08): 탭 구조물 · 토공 · 수리 + 구조물 보조 넷 · 구조물도 탭 지움 · 보조 탭 위 간격 8px(95-1 · 95-2)

구조물도 모달(집계표 행 누름 · 그 측점 조합 전부 · 복사본 먼저) · 공용 표 onRowClick opt-in(95-3) ·
수리 상세 폭 제한 풂 · 카드 높이 화면 따라 · @container(95-4). 화면 검증 전.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
2026-10-08 13:46:09 +09:00
co-authored by Claude Sonnet 5.5
parent b08796ab4e
commit c3af85113c
15 changed files with 409 additions and 152 deletions
+4
View File
@@ -133,3 +133,7 @@
color: var(--color-text);
font-weight: var(--font-weight-bold);
}
.ui-data-table__table tbody tr.is-clickable {
cursor: pointer;
}
+13 -2
View File
@@ -43,6 +43,8 @@ export interface DataTableOptions {
caption?: string;
/** 빈 값 글(기본 「-」 · 사용자가 채울 칸은 "") */
empty?: string;
/** 줄 누름(본문 줄만 · 합계 제외) — 주면 줄에 `data-i`(줄 번호)·`is-clickable` 이 붙음 */
onRowClick?: (index: number) => void;
}
export interface DataTable {
@@ -112,6 +114,7 @@ function rowHtml(
r: DataRow,
alt = false,
blank = EMPTY,
index: number | null = null,
): string {
let tr = "";
for (let j = 0; j < cols.length; j++) {
@@ -126,7 +129,9 @@ function rowHtml(
(fmts[j] ? ' data-num=""' : "");
tr += `<td${attrs}>${esc(txt)}</td>`;
}
return alt ? `<tr class="is-alt">${tr}</tr>` : `<tr>${tr}</tr>`;
const cls = [alt && "is-alt", index !== null && "is-clickable"].filter(Boolean).join(" ");
const data = index !== null ? ` data-i="${index}"` : "";
return cls || data ? `<tr${cls ? ` class="${cls}"` : ""}${data}>${tr}</tr>` : `<tr>${tr}</tr>`;
}
/** 그릴 줄 구간 — 밖의 줄은 빈 줄 높이(rowH px)로만 채움 */
@@ -163,7 +168,9 @@ export function renderDataTableHtml(o: DataTableOptions, win?: RowWindow): strin
const to = win ? Math.max(from, Math.min(win.to, n)) : n;
let body = win ? pad(from * win.rowH, cols.length) : "";
const blank = o.empty ?? EMPTY;
for (let i = from; i < to; i++) body += rowHtml(cols, fmts, left, o.rows[i], i % 2 === 1, blank);
const pick = o.onRowClick !== undefined;
for (let i = from; i < to; i++)
body += rowHtml(cols, fmts, left, o.rows[i], i % 2 === 1, blank, pick ? i : null);
if (win) body += pad((n - to) * win.rowH, cols.length);
const foot = o.total ? `<tfoot>${rowHtml(cols, fmts, left, o.total, false, blank)}</tfoot>` : "";
const cap = o.caption ? `<caption>${esc(o.caption)}</caption>` : "";
@@ -199,6 +206,10 @@ export function createDataTable(opts: DataTableOptions): DataTable {
}
};
root.addEventListener("scroll", () => draw(false), { passive: true });
root.addEventListener("click", (event) => {
const row = (event.target as Element).closest<HTMLElement>("tr[data-i]");
if (row && root.contains(row)) state.onRowClick?.(Number(row.dataset.i));
});
draw(true);
return {
root,
-1
View File
@@ -615,7 +615,6 @@ export const ui_locales_b2 = {
B08_Quantity_Tab_Summary: ["토공집계", "Earthwork Summary"],
B08_Quantity_Tab_Haul: ["운반거리", "Haul Distance"],
B08_Quantity_Tab_StructureSummary: ["구조물 집계표", "Structure Summary"],
B08_Quantity_Tab_StructureSheet: ["구조물도", "Structure Sheets"],
B08_Quantity_Tab_UnitQuantity: ["구조물 원단위", "Structure Unit Quantity"],
B08_Quantity_Tab_Material: ["자재총괄", "Material Summary"],
B08_Quantity_Tab_Preparation: ["준비공·사방공", "Preparation & Erosion Control"],
+11
View File
@@ -19,6 +19,17 @@ export const ui_locales_b4 = {
],
B08_Quantity_Tab_Hydrology: ["수리집수면적표", "Hydraulic Catchment Table"],
B08_Quantity_Tab_Pending: ["차례대로 만들 탭입니다.", "This tab comes later, in order."],
B08_Quantity_Tab_Structure: ["구조물", "Structure"],
B08_Quantity_Tab_MaterialTotal: ["총괄자재수량집계표", "Total Material Quantity Table"],
B08_Quantity_Tab_ConcreteMaterial: [
"콘크리트소요자재집계표",
"Concrete Material Requirement Table",
],
B08_Quantity_Tab_MaterialQuantity: ["자재수량집계표", "Material Quantity Table"],
B08_Quantity_Sheets_Title: ["구조물도", "Structure Sheets"],
B08_Quantity_Sheets_FromCopy: ["프로젝트 복사본", "Project copy"],
B08_Quantity_Sheets_FromMaster: ["M02 원본", "M02 original"],
B08_Quantity_Sheets_NoCopy: ["복사본에 없음", "Not in the project copy"],
B08_Quantity_Drawing_Loading: ["구조물도를 불러오는 중…", "Loading structure sheets…"],
B08_Quantity_Drawing_Failed: [
"구조물도를 불러오지 못했습니다",