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
@@ -82,6 +82,12 @@ console.log(
frozen: 1,
empty: "",
}),
pick: m.renderDataTableHtml({
columns: [{ key: "x", label: "X" }],
rows: [{ x: 1 }, { x: 2 }],
total: { x: 3 },
onRowClick: () => undefined,
}),
escaped: m.renderDataTableHtml({ columns: [{ key: "x", label: "<b>" }], rows: [{ x: "a&<" }] }),
noTotal: m.renderDataTableHtml({ columns: [tc], rows: [] }).includes("<tfoot>"),
}),
@@ -72,9 +72,29 @@ def test_빈_목록():
assert out == [[], [], []]
def test_측점_조합들():
# 95-3 — 한 측점(1cm 안) 조합 전부 · 본체 → 그룹 차례 · 구조물 id 따라감 · 같은 구조물 같은 조합은 한 건
things = [
{"structure_id": "s1", "chainage_m": 40.004, "summary_item": _item("SS1"),
"group_items": {"g1": _item("SS3"), "g2": _item("SS1")}},
{"structure_id": "pipe@40.0", "chainage_m": 40.0, "summary_item": _item("SS1")},
{"structure_id": "s2", "chainage_m": 80.0, "summary_item": _item("SS2")},
] # fmt: skip
out = _run(
f"\nconst things = {json.dumps(things, ensure_ascii=False)};"
"\nconsole.log(JSON.stringify([combosAt(things, 40).map((c) => [c.id, c.structureId]),"
" combosAt(things, 80).length, combosAt(things, 99).length]));"
)
assert out == [
[["system||SS1", "s1"], ["system||SS3", "s1"], ["system||SS1", "pipe@40.0"]],
1,
0,
]
def test_집계표는_공용_표():
# PLAN 70-2 — 공용 많은 자료 표 · 측점 열 고정 · 칸은 빈칸(「—」 아님)
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
assert "createDataTable(" in ui and "@ui/ui_template_data_table" in ui
assert "frozen: 1" in ui and 'empty: ""' in ui
assert "frozen: 1" in ui and 'empty: ""' in ui and "onRowClick" in ui
assert 'document.createElement("table")' not in ui
@@ -1,15 +1,28 @@
"""61-2 B08 2탭 구조물도 — 탭에 붙음 · 읽기만 · 집계표와 같은 조합 목록 · 안내 글 키."""
"""61-2 · 95-3 B08 구조물도 모달 — 집계표 행에서 열림 · 읽기만 · 집계표와 같은 조합 목록 · 안내 글 키."""
import re
from pathlib import Path
DRAWING = Path("B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8")
SUMMARY = Path("B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8")
PAGE = Path("B08_Quantity/B08_Quantity_UI_Page.ts").read_text(encoding="utf-8")
LOCALE = Path("ui_template/ui_template_locale_b4.ts").read_text(encoding="utf-8")
LOCALE_B2 = Path("ui_template/ui_template_locale_b2.ts").read_text(encoding="utf-8")
def test_sheet_tab_wired():
assert 'tab === "sheet"' in PAGE and "renderStructureSheets(view, projectId)" in PAGE
def test_sheet_tab_gone_modal_wired():
# 구조물도 탭 없음 · 집계표 행 누름이 모달을 늦게 불러 엶
assert '"sheet"' not in PAGE and "renderStructureSheets" not in PAGE
assert "B08_Quantity_Tab_StructureSheet" not in PAGE + LOCALE + LOCALE_B2
assert "onRowClick" in SUMMARY and 'import("./B08_Quantity_UI_Drawing")' in SUMMARY
assert "combosAt(things, station)" in SUMMARY
def test_modal_own_class_and_cleanup():
css = Path("B08_Quantity/B08_Quantity_UI_Drawing.css").read_text(encoding="utf-8")
assert "min(1320px, 96vw)" in css and "--modal-max: 94vh" in css
assert "beforeClose" in DRAWING and "cards.destroy()" in DRAWING
assert 'dialogClass: "b08-sheets"' in DRAWING
def test_read_only():
@@ -19,10 +32,12 @@ def test_read_only():
assert "editable: () => false" in DRAWING
def test_same_columns_as_summary():
assert "summaryColumns(" in DRAWING and "./B08_Quantity_Summary_Data" in DRAWING
def test_copy_first_then_master():
# 같은 항목의 프로젝트 복사본이 있으면 복사본 · 없으면 M02 원본 층
assert "sameItem(own, row)" in DRAWING and "structureFolder(" in DRAWING
assert "readTemplate(layer, part, column, null)" in DRAWING
def test_locale_keys_exist():
for key in set(re.findall(r'L\("([A-Za-z0-9_]+)"\)', DRAWING)):
assert f"{key}:" in LOCALE or key.startswith("B08_Quantity_Summary_"), key
assert f"{key}:" in LOCALE or key.startswith(("B08_Quantity_Summary_", "Common_")), key
+9
View File
@@ -92,3 +92,12 @@ def test_빈_칸_글_풍선_꾸밈_옵션(r):
assert '<td class="is-empty"></td>' in o # empty="" — 「-」 대신 빈칸
assert '<th scope="col" rowspan="1" class="is-missing" title="풍선">메쌓기</th>' in o
assert 'rowspan="2" class="is-frozen is-edge" style="left:0px" title="측점">측점</th>' in o
def test_줄_누름_옵션일_때만_data_i(r):
# 95-3 — onRowClick 을 주면 본문 줄에만 data-i · is-clickable(합계 줄 제외) · 안 주면 글 그대로
pick = r["pick"]
assert '<tr class="is-clickable" data-i="0">' in pick
assert '<tr class="is-alt is-clickable" data-i="1">' in pick
assert pick.count("data-i=") == 2
assert "data-i" not in r["opts"] and "is-clickable" not in r["foot"]