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

+39 -34
View File
@@ -108,7 +108,6 @@ interface Pending {
fileName: string;
entries: Pier[];
sheets: SheetReport[];
preview: string | null;
}
const state = {
@@ -2842,7 +2841,7 @@ function renderDialog(): void {
$('file-name').textContent = p ? `선택된 파일: ${p.fileName}` : '';
if (!p) {
sheetsBox.appendChild(el('li', '파일을 선택하면 시트별 인식 결과가 표시됩니다.'));
renderPreview(null);
renderPreview([]);
updateApply();
return;
}
@@ -2873,38 +2872,36 @@ function renderDialog(): void {
entry.checked = check.checked;
updateApply();
});
const name = el('button', `${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();
});
const name = el('span', `${entry.input.name} · ${entry.input.type}`, 'pier-name');
row.append(check, name);
li.appendChild(row);
}
sheetsBox.appendChild(li);
}
const preview = p.entries.find((e) => e.input.name === p.preview) ?? p.entries.find((e) => selectable(p, e)) ?? null;
renderPreview(preview ? preview.input : null);
renderPreview(p ? p.entries.map((e) => ({ input: e.input, checked: e.checked })) : []);
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 pierTable = $<HTMLTableElement>('pv-pier').tBodies[0];
const pierTable = $<HTMLTableElement>('pv-pier');
const bearingBody = $<HTMLTableElement>('pv-bearings').tBodies[0];
const ipBody = $<HTMLTableElement>('pv-ips').tBodies[0];
pierTable.replaceChildren();
bearingBody.replaceChildren();
ipBody.replaceChildren();
if (!input) {
if (items.length === 0) {
title.textContent = '미리볼 교각이 없습니다.';
return;
}
title.textContent = `${input.name} · 시트 ${input.sheet}`;
const pairs: Pair[] = [
['교각명', input.name],
const sheets = [...new Set(items.map((i) => i.input.sheet))].join(', ');
title.textContent = `${items.length}기 · 시트 ${sheets}`;
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],
['두부보 EL (코핑 상면)', f3(input.copingTopEl)],
['기초 하단 EL', f3(input.footingBottomEl)],
@@ -2913,22 +2910,31 @@ function renderPreview(input: PierInput | null): void {
['말뚝길이 (mm)', String(input.pileLengthMm)],
['두부보 폭 × 길이 (mm)', `${input.copingWidthMm} × ${input.copingLengthMm}`],
['기둥갯수', String(input.columnCount)],
['받침 · IP', `${input.bearings.length} · ${input.ips.length}`],
];
for (const [k, v] of pairs) {
const tr = el('tr');
tr.append(el('th', k), el('td', v));
pierTable.appendChild(tr);
}
for (const b of input.bearings) {
const tr = el('tr');
const azimuth = Number.isNaN(b.azimuthDeg) ? '오류' : `${b.azimuthDeg.toFixed(4)}°`;
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));
bearingBody.appendChild(tr);
}
for (const ip of input.ips) {
const tr = el('tr');
for (const v of [ip.name, ip.x.toFixed(3), ip.y.toFixed(3), `${ip.angleDeg}°`]) tr.appendChild(el('td', v));
ipBody.appendChild(tr);
const labels = rowsOf(items[0].input).map(([k]) => k);
const body = pierTable.createTBody();
labels.forEach((label, r) => {
const tr = body.insertRow();
tr.appendChild(el('th', label));
for (const { input } of items) tr.appendChild(el('td', rowsOf(input)[r][1]));
});
const included = body.insertRow();
included.appendChild(el('th', '불러오기'));
for (const { checked } of items) included.appendChild(el('td', checked ? '예' : '아니오'));
for (const { input } of items) {
for (const b of input.bearings) {
const tr = el('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'));
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);
pending.preview = entries.find((e) => selectable(pending, e))?.input.name ?? entries[0]?.input.name ?? null;
state.pending = pending;
renderDialog();
}
+3
View File
@@ -810,3 +810,6 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
@media (max-width: 800px) {
.footing-grid { grid-template-columns: 1fr; }
}
/* Import preview: all piers' 받침·IP rows scroll inside the dialog. */
.pv-scroll { max-height: 240px; overflow: auto; }