feat(ui): 헤더 단계를 시안대로 ①②③으로, 철근재료표는 ③에서 여는 화면으로

시안(11-03·11-06)의 헤더는 ① 형상 생성 · ② 형상계획 · ③ 철근배근뿐이다 (사용자 결정 2026-11-06).
- 헤더의 ④ 재료 버튼을 없앰
- 철근재료표 화면(교각별 표·전체 합계·엑셀·인쇄)은 그대로 두고 ③의 [철근재료표 보기]로 연다
- 재료표 화면에 [← ③ 철근배근] 버튼, 열려 있는 동안 헤더는 ③을 강조
- 브라우저 검증 스크립트 4개가 ③을 거쳐 재료표를 열도록 수정

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-06 14:31:46 +09:00
1 parent 2b26b7c095
commit 3d0d484524
6 files changed
+22 -14

No files matched your search

+1 -1
View File
@@ -17,7 +17,6 @@
<button id="nav-shape" class="step current" data-label="① 형상 생성" aria-label="① 형상 생성" type="button" aria-current="step">① 형상 <em id="step1-status"></em></button>
<button id="nav-plan" class="step" data-label="② 형상계획" aria-label="② 형상계획" type="button">② 계획 <em id="step2-status"></em></button>
<button id="nav-rebar" class="step" data-label="③ 철근배근" aria-label="③ 철근배근" type="button">③ 배근 <em id="step3-status"></em></button>
<button id="nav-quantity" class="step" data-label="④ 재료표" aria-label="④ 재료표" type="button">④ 재료 <em id="step4-status"></em></button>
</nav>
<div class="actions">
<span id="save-status" class="savest">저장 전</span>
@@ -276,6 +275,7 @@
</aside>
<section class="panel viewer" aria-labelledby="quantity-title">
<div class="viewer-head">
<button id="quantity-back" class="btn-o" type="button">← ③ 철근배근</button>
<h2 id="quantity-title">철근재료표</h2>
<button id="quantity-export" class="btn-o" type="button">엑셀로 내보내기</button>
<button id="quantity-print" class="btn-o" type="button">인쇄</button>
+3 -1
View File
@@ -104,7 +104,9 @@ let ws, send;
await until(`document.getElementById('cip-pile-cageLengthMm')?.value === '12000'`);
check('pile panel shares the column connection', await ev(`document.querySelector('[data-cast-part="pile"]').textContent.includes('공통 연결 방식: 연속')`));
await shot('02-pile-input');
await click(`document.getElementById('nav-quantity')`);
// ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
await click(`document.getElementById('nav-rebar')`);
await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity clearly marks incomplete design', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료') && document.getElementById('quantity-body').textContent.includes('접합부')`));
await shot('03-quantity-incomplete');
await click(`document.getElementById('save-project')`);
+3 -1
View File
@@ -90,7 +90,9 @@ let ws, send;
await until(`document.getElementById('cip-pile-cageLengthMm')?.value === '12000'`);
check('pile panel shares the column connection', await ev(`document.querySelector('[data-cast-part="pile"]').textContent.includes('공통 연결 방식: 연속')`));
await shot('02-pile-input');
await click(`document.getElementById('nav-quantity')`);
// ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
await click(`document.getElementById('nav-rebar')`);
await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity clearly marks incomplete design', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료') && document.getElementById('quantity-body').textContent.includes('접합부')`));
await shot('03-quantity-incomplete');
await click(`document.getElementById('save-project')`);
+3 -1
View File
@@ -81,7 +81,9 @@ const delay=ms=>new Promise(r=>setTimeout(r,ms)); let ws,send;
await send('Emulation.setDeviceMetricsOverride',{width,height:1000,deviceScaleFactor:1,mobile:false});await delay(150);
check(`header ${width} keeps one row and no horizontal overflow`,await ev(`document.querySelector('.top').getBoundingClientRect().height<=65&&document.documentElement.scrollWidth<=innerWidth`));
}
await click(`document.getElementById('nav-quantity')`);
// ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
await click(`document.getElementById('nav-rebar')`);
await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity contains actual dedicated mains and reports remaining incomplete details',await ev(`document.getElementById('quantity-body').textContent.includes('말뚝-P1')&&document.getElementById('quantity-body').textContent.includes('기둥-P1')&&document.getElementById('quantity-body').textContent.includes('전체 수량 미완료')`));
await shot('04-quantity');
await click(`document.getElementById('nav-rebar')`);await member('기둥');const beforeSave=await ev(table);
+3 -1
View File
@@ -137,7 +137,9 @@ let ws, send;
await click(`[...document.querySelectorAll('#rebar-issues li')].find(e=>e.textContent.includes(${JSON.stringify(text)}))?.querySelector('button')`);
check(`${text}: warning opens unified editor`, await ev(`!!document.getElementById('cip-apply') && document.querySelector('#rebar-tabs button[aria-pressed="true"]').textContent.startsWith('기둥·말뚝')`));
}
await click(`document.getElementById('nav-quantity')`);
// ④ is no longer a header step (header ①②③, 2026-11-06): open the 재료표 from ③.
await click(`document.getElementById('nav-rebar')`);
await click(`[...document.querySelectorAll('button')].find(b=>b.textContent==='철근재료표 보기')`);
check('quantity remains explicitly incomplete', await ev(`document.getElementById('quantity-body').textContent.includes('전체 수량 미완료')`));
check('no runtime exceptions', report.errors.length===0);
report.result='PASS';
+9 -9
View File
@@ -142,7 +142,7 @@ interface Pier extends PierValues {
}
/** Part toggles in the order of 시안 ① "부재 표시". Each toggle controls one or more scene parts. */
/** The four stages: ① 형상 생성, ② 형상계획, ③ 철근배근, ④ 재료표 (결과 화면). */
/** The stages: ① 형상 생성, ② 형상계획, ③ 철근배근. 'quantity' is the 철근재료표 screen opened from ③ (시안: header ①②③, 2026-11-06). */
type Stage = 'shape' | 'plan' | 'rebar' | 'quantity';
/** 부재 표시 toggles (2026-11-03). `pier` parts follow the pier scenes; `abut` names follow the abutment groups (abut-scene.ts). */
@@ -2636,11 +2636,12 @@ function setStage(stage: Stage): void {
placeViewer(stage);
if (rebar3d) rebar3d.group.visible = stage === 'rebar';
// The highlight is the 'current' class (style.css); aria-current is set alongside it for assistive tech.
const nav: [string, Stage][] = [['nav-shape', 'shape'], ['nav-plan', 'plan'], ['nav-rebar', 'rebar'], ['nav-quantity', 'quantity']];
for (const [id, key] of nav) {
// The 재료표 screen belongs to ③, so ③ stays highlighted while it is open.
const nav: [string, Stage[]][] = [['nav-shape', ['shape']], ['nav-plan', ['plan']], ['nav-rebar', ['rebar', 'quantity']]];
for (const [id, keys] of nav) {
const btn = $<HTMLButtonElement>(id);
btn.classList.toggle('current', stage === key);
btn.setAttribute('aria-current', String(stage === key));
btn.classList.toggle('current', keys.includes(stage));
btn.setAttribute('aria-current', String(keys.includes(stage)));
}
renderAll();
}
@@ -4478,11 +4479,11 @@ function renderRebar(): void {
if (focused && state.rebarSelection.size) focused.scrollIntoView({ block: 'center' });
}
/** ③ keeps only a link to the 철근재료표 screen ④ (승인 2026-10-23). */
/** ③ keeps a summary line and a link to the 철근재료표 screen (opened from ③; header ①②③, 2026-11-06). */
function renderQuantityLink(box: HTMLElement, rows: RebarRow[], incomplete: readonly string[] = []): void {
const q = buildQuantity(rows, state.material, incomplete);
const line = el('p', `철근재료표 · ${q.missing.length ? '계산된 부재 소계 · 전체 수량 미완료' : '합계'} ${q.kg.toFixed(1)} kg · 할증 포함 ${q.kgWithWaste.toFixed(1)} kg`, 'pnote');
const link = el('button', '④ 재료표에서 보기', 'btn small');
const link = el('button', '철근재료표 보기', 'btn small');
link.type = 'button';
link.addEventListener('click', () => setStage('quantity'));
box.append(line, link);
@@ -4536,7 +4537,6 @@ function renderQuantityScreen(): void {
allButton.textContent = `전체 합계 (${generated}기)`;
allButton.setAttribute('aria-pressed', String(state.quantityAll));
const targets = quantityTargets();
setHeaderStatus('step4-status', '', targets.length ? (state.quantityAll ? `${targets.length}기` : targets[0].input.name) : '예정');
$('quantity-note').textContent = targets.length
? `${state.quantityAll ? `전체 ${targets.length}기` : targets[0].input.name} · 할증 ${state.material.waste}% (기본 0%, 가정). 삿갓 길이는 h − 100으로 가정(Q-K5). 교각 목록에서 고른 교각만 보며, 전체를 누르면 모든 교각을 보여 줍니다.`
: '계산된 교각이 없습니다. 교각을 생성하세요.';
@@ -5900,7 +5900,7 @@ $<HTMLButtonElement>('regen').addEventListener('click', regenerateChanged);
$<HTMLButtonElement>('nav-shape').addEventListener('click', () => setStage('shape'));
$<HTMLButtonElement>('nav-plan').addEventListener('click', () => setStage('plan'));
$<HTMLButtonElement>('nav-rebar').addEventListener('click', () => setStage('rebar'));
$<HTMLButtonElement>('nav-quantity').addEventListener('click', () => setStage('quantity'));
$<HTMLButtonElement>('quantity-back').addEventListener('click', () => setStage('rebar'));
$<HTMLInputElement>('mat-fck').addEventListener('change', (ev) => setMaterial('fck', (ev.target as HTMLInputElement).value));
$<HTMLInputElement>('mat-waste').addEventListener('change', (ev) => setWaste((ev.target as HTMLInputElement).value));
for (const k of ['small16', 'main25', 'main35', 'main51'] as const) {