feat(import): 엑셀 불러오기 미리보기에 전체 교각의 입력값 표시

- 교각 정보: 교각마다 열 하나, 행은 형식·EL·기초·말뚝·두부보·기둥·받침/IP 수·불러오기 여부
- 받침과 IP: 모든 교각을 한 표에 두고 교각 열을 추가, 표는 스크롤
- 교각 이름 클릭으로 미리볼 교각을 고르던 방식은 제거 (전체를 보여 줌)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-27 15:26:35 +09:00
1 parent 844177b40e
commit b0a71fc73f
3 files changed
+55 -43

No files matched your search

+13 -9
View File
@@ -291,17 +291,21 @@
<h3>미리보기</h3> <h3>미리보기</h3>
<p id="preview-title" class="muted"></p> <p id="preview-title" class="muted"></p>
<h4>교각 정보</h4> <h4>교각 정보</h4>
<table id="pv-pier" class="grid-table"><tbody></tbody></table> <div class="pv-scroll"><table id="pv-pier" class="grid-table"></table></div>
<h4>받침</h4> <h4>받침</h4>
<table id="pv-bearings" class="grid-table"> <div class="pv-scroll">
<thead><tr><th>번호</th><th>x (m)</th><th>y (m)</th><th>z (m)</th><th>방위각</th><th>위치</th></tr></thead> <table id="pv-bearings" class="grid-table">
<tbody></tbody> <thead><tr><th>교각</th><th>번호</th><th>x (m)</th><th>y (m)</th><th>z (m)</th><th>방위각</th><th>위치</th></tr></thead>
</table> <tbody></tbody>
</table>
</div>
<h4>IP</h4> <h4>IP</h4>
<table id="pv-ips" class="grid-table"> <div class="pv-scroll">
<thead><tr><th>점</th><th>x (m)</th><th>y (m)</th><th>꺾임각</th></tr></thead> <table id="pv-ips" class="grid-table">
<tbody></tbody> <thead><tr><th>교각</th><th>점</th><th>x (m)</th><th>y (m)</th><th>꺾임각</th></tr></thead>
</table> <tbody></tbody>
</table>
</div>
</section> </section>
</div> </div>
</div> </div>
+39 -34
View File
@@ -108,7 +108,6 @@ interface Pending {
fileName: string; fileName: string;
entries: Pier[]; entries: Pier[];
sheets: SheetReport[]; sheets: SheetReport[];
preview: string | null;
} }
const state = { const state = {
@@ -2842,7 +2841,7 @@ function renderDialog(): void {
$('file-name').textContent = p ? `선택된 파일: ${p.fileName}` : ''; $('file-name').textContent = p ? `선택된 파일: ${p.fileName}` : '';
if (!p) { if (!p) {
sheetsBox.appendChild(el('li', '파일을 선택하면 시트별 인식 결과가 표시됩니다.')); sheetsBox.appendChild(el('li', '파일을 선택하면 시트별 인식 결과가 표시됩니다.'));
renderPreview(null); renderPreview([]);
updateApply(); updateApply();
return; return;
} }
@@ -2873,38 +2872,36 @@ function renderDialog(): void {
entry.checked = check.checked; entry.checked = check.checked;
updateApply(); updateApply();
}); });
const name = el('button', `${entry.input.name} · ${entry.input.type}`, 'pier-name'); const name = el('span', `${entry.input.name} · ${entry.input.type}`, 'pier-name');
name.type = 'button';
name.setAttribute('aria-pressed', String(p.preview === entry.input.name));
name.addEventListener('click', () => {
p.preview = entry.input.name;
renderDialog();
});
row.append(check, name); row.append(check, name);
li.appendChild(row); li.appendChild(row);
} }
sheetsBox.appendChild(li); sheetsBox.appendChild(li);
} }
const preview = p.entries.find((e) => e.input.name === p.preview) ?? p.entries.find((e) => selectable(p, e)) ?? null; renderPreview(p ? p.entries.map((e) => ({ input: e.input, checked: e.checked })) : []);
renderPreview(preview ? preview.input : null);
updateApply(); updateApply();
} }
function renderPreview(input: PierInput | null): void { /** Preview of every pier in the file: one column per pier for the 교각 정보, all 받침·IP rows with a 교각 column. */
function renderPreview(items: { input: PierInput; checked: boolean }[]): void {
const title = $('preview-title'); const title = $('preview-title');
const pierTable = $<HTMLTableElement>('pv-pier').tBodies[0]; const pierTable = $<HTMLTableElement>('pv-pier');
const bearingBody = $<HTMLTableElement>('pv-bearings').tBodies[0]; const bearingBody = $<HTMLTableElement>('pv-bearings').tBodies[0];
const ipBody = $<HTMLTableElement>('pv-ips').tBodies[0]; const ipBody = $<HTMLTableElement>('pv-ips').tBodies[0];
pierTable.replaceChildren(); pierTable.replaceChildren();
bearingBody.replaceChildren(); bearingBody.replaceChildren();
ipBody.replaceChildren(); ipBody.replaceChildren();
if (!input) { if (items.length === 0) {
title.textContent = '미리볼 교각이 없습니다.'; title.textContent = '미리볼 교각이 없습니다.';
return; return;
} }
title.textContent = `${input.name} · 시트 ${input.sheet}`; const sheets = [...new Set(items.map((i) => i.input.sheet))].join(', ');
const pairs: Pair[] = [ title.textContent = `${items.length}기 · 시트 ${sheets}`;
['교각명', input.name],
const head = pierTable.createTHead().insertRow();
head.appendChild(el('th', '항목'));
for (const { input } of items) head.appendChild(el('th', input.name));
const rowsOf = (input: PierInput): Pair[] => [
['교각형식', input.type], ['교각형식', input.type],
['두부보 EL (코핑 상면)', f3(input.copingTopEl)], ['두부보 EL (코핑 상면)', f3(input.copingTopEl)],
['기초 하단 EL', f3(input.footingBottomEl)], ['기초 하단 EL', f3(input.footingBottomEl)],
@@ -2913,22 +2910,31 @@ function renderPreview(input: PierInput | null): void {
['말뚝길이 (mm)', String(input.pileLengthMm)], ['말뚝길이 (mm)', String(input.pileLengthMm)],
['두부보 폭 × 길이 (mm)', `${input.copingWidthMm} × ${input.copingLengthMm}`], ['두부보 폭 × 길이 (mm)', `${input.copingWidthMm} × ${input.copingLengthMm}`],
['기둥갯수', String(input.columnCount)], ['기둥갯수', String(input.columnCount)],
['받침 · IP', `${input.bearings.length} · ${input.ips.length}`],
]; ];
for (const [k, v] of pairs) { const labels = rowsOf(items[0].input).map(([k]) => k);
const tr = el('tr'); const body = pierTable.createTBody();
tr.append(el('th', k), el('td', v)); labels.forEach((label, r) => {
pierTable.appendChild(tr); const tr = body.insertRow();
} tr.appendChild(el('th', label));
for (const b of input.bearings) { for (const { input } of items) tr.appendChild(el('td', rowsOf(input)[r][1]));
const tr = el('tr'); });
const azimuth = Number.isNaN(b.azimuthDeg) ? '오류' : `${b.azimuthDeg.toFixed(4)}°`; const included = body.insertRow();
for (const v of [String(b.no), b.x.toFixed(3), b.y.toFixed(3), f3(b.z), azimuth, b.row]) tr.appendChild(el('td', v)); included.appendChild(el('th', '불러오기'));
bearingBody.appendChild(tr); for (const { checked } of items) included.appendChild(el('td', checked ? '예' : '아니오'));
}
for (const ip of input.ips) { for (const { input } of items) {
const tr = el('tr'); for (const b of input.bearings) {
for (const v of [ip.name, ip.x.toFixed(3), ip.y.toFixed(3), `${ip.angleDeg}°`]) tr.appendChild(el('td', v)); const tr = el('tr');
ipBody.appendChild(tr); const azimuth = Number.isNaN(b.azimuthDeg) ? '오류' : `${b.azimuthDeg.toFixed(4)}°`;
for (const v of [input.name, String(b.no), b.x.toFixed(3), b.y.toFixed(3), f3(b.z), azimuth, b.row]) tr.appendChild(el('td', v));
bearingBody.appendChild(tr);
}
for (const ip of input.ips) {
const tr = el('tr');
for (const v of [input.name, ip.name, ip.x.toFixed(3), ip.y.toFixed(3), `${ip.angleDeg}°`]) tr.appendChild(el('td', v));
ipBody.appendChild(tr);
}
} }
} }
@@ -2952,9 +2958,8 @@ function loadBytes(bytes: Uint8Array, fileName: string): void {
for (const e of own) sheet.issues.push(...e.issues.filter((i) => i.level === 'E')); for (const e of own) sheet.issues.push(...e.issues.filter((i) => i.level === 'E'));
sheet.ok = sheet.ok && own.every((e) => e.model !== null); sheet.ok = sheet.ok && own.every((e) => e.model !== null);
} }
const pending: Pending = { fileName, entries, sheets, preview: null }; const pending: Pending = { fileName, entries, sheets };
for (const e of entries) e.checked = selectable(pending, e); for (const e of entries) e.checked = selectable(pending, e);
pending.preview = entries.find((e) => selectable(pending, e))?.input.name ?? entries[0]?.input.name ?? null;
state.pending = pending; state.pending = pending;
renderDialog(); renderDialog();
} }
+3
View File
@@ -810,3 +810,6 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
@media (max-width: 800px) { @media (max-width: 800px) {
.footing-grid { grid-template-columns: 1fr; } .footing-grid { grid-template-columns: 1fr; }
} }
/* Import preview: all piers' 받침·IP rows scroll inside the dialog. */
.pv-scroll { max-height: 240px; overflow: auto; }