Files
ExperionCrawler/export2excel.md

9.3 KiB

Excel Export 기능 추가 — 자연어 쿼리 결과 테이블

목표

Text-to-SQL 탭의 📊 조회 결과 카드에 "Excel 다운로드" 버튼을 추가한다. 버튼 클릭 시 현재 렌더된 결과 테이블을 .xlsx 파일로 즉시 다운로드한다.


기술 방식 결정

클라이언트 사이드 — SheetJS (xlsx) CDN

항목 내용
라이브러리 SheetJS Community Edition
CDN URL https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js
서버 변경 없음 — 순수 브라우저 JS
출력 포맷 .xlsx (Excel 2007+)
파일 크기 라이브러리 ~1MB (CDN 캐시)

CSV export는 시간대·쉼표 포함 값 처리가 복잡하므로 SheetJS를 사용한다.


구현 계획

Step 1 — SheetJS CDN 추가 (index.html)

</body> 직전의 <script src="/js/app.js"> 태그 앞에 CDN 스크립트 태그 삽입:

<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="/js/app.js"></script>

순서 중요: xlsx 라이브러리가 app.js 보다 먼저 로드되어야 한다.


Step 2 — 현재 결과 데이터 보관 변수 추가 (app.js)

t2sRenderTable 호출 후 데이터를 잃지 않도록 모듈 스코프 변수에 저장한다.

파일 상단 전역 변수 영역에 추가:

// Excel export용 — 마지막으로 렌더된 결과 보관
let _t2sLastResult = null;  // { columns: string[], rows: object[] }

Step 3 — t2sRenderTable 수정 (app.js, line ~1483)

함수 진입 직후, 빈 결과 분기 이전에 저장:

function t2sRenderTable(result) {
  const container = document.getElementById('t2s-results');

  const rows    = result.rows    || [];
  const columns = result.columns || [];
  const totalCount = result.totalCount || 0;

  // ── 추가: 결과 저장 (export용) ──
  _t2sLastResult = rows.length > 0 ? { columns, rows } : null;

  // 기존 로직 유지 ...
  if (!rows || rows.length === 0) { ... }

결과 정보 행에 Excel 버튼 삽입 (기존 t2s-result-info div 수정):

// 변경 전
let html = '<div class="t2s-result-info">총 <b>' + totalCount + '</b>개 결과</div>';

// 변경 후
let html = `
  <div class="t2s-result-info">
    <span>총 <b>${totalCount}</b>개 결과</span>
    <button class="btn-excel" onclick="t2sExportExcel()">⬇ Excel</button>
  </div>`;

Step 4 — t2sExportExcel 함수 추가 (app.js)

t2sRenderTable 함수 바로 다음에 삽입:

/**
 * t2sExportExcel — 마지막 쿼리 결과를 .xlsx로 다운로드
 */
function t2sExportExcel() {
  if (!_t2sLastResult) return;

  const { columns, rows } = _t2sLastResult;

  // 1. 헤더 행 + 데이터 행 배열 구성
  const sheetData = [
    columns,  // 첫 행 = 컬럼 헤더
    ...rows.map(row => columns.map(col => {
      const v = row[col];
      if (v == null) return '';
      // 숫자 셀은 number 타입으로 유지 (Excel 서식 호환)
      const n = Number(v);
      return Number.isFinite(n) ? n : String(v);
    }))
  ];

  // 2. 워크시트 생성
  const ws = XLSX.utils.aoa_to_sheet(sheetData);

  // 3. 컬럼 너비 자동 조정 (최대 30자)
  ws['!cols'] = columns.map((col, i) => {
    const maxLen = Math.max(
      col.length,
      ...rows.map(r => String(r[col] ?? '').length)
    );
    return { wch: Math.min(maxLen + 2, 30) };
  });

  // 4. 워크북 생성 및 다운로드
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'QueryResult');

  const now = new Date();
  const ts  = now.toISOString().replace(/[:.]/g, '-').substring(0, 19);
  XLSX.writeFile(wb, `query_result_${ts}.xlsx`);
}

Step 5 — 버튼 스타일 추가 (style.css)

.t2s-result-info 블록 내 flex 레이아웃 + 버튼 스타일:

/* 기존 .t2s-result-info 수정 */
.t2s-result-info {
  font-size: 13px;
  color: var(--t1);
  margin-bottom: 10px;
  padding: 8px 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Excel 다운로드 버튼 */
.btn-excel {
  padding: 4px 12px;
  font-size: 12px;
  border: 1px solid #217346;
  border-radius: var(--r);
  background: #217346;
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}
.btn-excel:hover {
  background: #1a5c38;
}

수정 파일 요약

파일 수정 내용
src/Web/wwwroot/index.html SheetJS CDN <script> 태그 1줄 추가 (app.js 태그 앞)
src/Web/wwwroot/js/app.js 전역 변수 _t2sLastResult 추가; t2sRenderTable 수정 (저장 + 버튼); t2sExportExcel 함수 추가
src/Web/wwwroot/css/style.css .t2s-result-info flex 수정; .btn-excel 스타일 추가

서버 코드(C#) 변경 없음.


동작 흐름

자연어 입력 → Enter / Execute 버튼
  └─ t2sRenderTable(result) 호출
       ├─ _t2sLastResult = { columns, rows } 저장
       └─ "총 N개 결과  [⬇ Excel]" 헤더 렌더링

사용자가 [⬇ Excel] 클릭
  └─ t2sExportExcel()
       ├─ _t2sLastResult 로 aoa_to_sheet 생성
       ├─ 숫자는 number 타입 유지 (Excel 정렬·계산 가능)
       └─ query_result_2026-04-28T08-15-44.xlsx 다운로드

주의 사항

  • SheetJS CDN 로드 실패(오프라인 환경) 대비: t2sExportExcel 시작 시 if (typeof XLSX === 'undefined') { alert('Excel 라이브러리 로드 실패'); return; } 추가 권장
  • _t2sLastResult는 마지막 쿼리 결과만 보관한다. 탭 이동 후 재진입해도 이전 결과가 남아 있으므로 t2sRenderTable에서 빈 결과(rows.length === 0)일 때 반드시 null로 초기화한다.
  • 피봇 테이블(tagname → 컬럼) 변환 후의 데이터가 _t2sLastResult에 저장되므로 Excel에도 피봇 형태가 그대로 반영된다.

📝 구현 진행 기록

단계 작업 내용 파일 상태 기록일
1 SheetJS CDN 추가 (index.html) src/Web/wwwroot/index.html 완료 2026-04-28
2 마지막 결과 데이터 보관 변수 추가 src/Web/wwwroot/js/app.js (1번 라인 이전) 완료 2026-04-28
3 t2sRenderTable 함수 수정 (데이터 저장 + Excel 버튼) src/Web/wwwroot/js/app.js (1489~1502 라인) 완료 2026-04-28
4 t2sExportExcel 함수 추가 src/Web/wwwroot/js/app.js (1533~1552 라인) 완료 2026-04-28
5 버튼 스타일 정의 src/Web/wwwroot/css/style.css (655~667 라인) 완료 2026-04-28
6 작업 내용 기록 export2excel.md 완료 2026-04-28

📋 구현 상세

1. SheetJS CDN 추가 (src/Web/wwwroot/index.html)

  • 위치: <script src="/js/app.js"></script> 태그 앞
  • 코드:
    <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
    <script src="/js/app.js"></script>
    

2. 전역 변수 추가 (src/Web/wwwroot/js/app.js)

  • 위치: 파일 시작부 (/* ── Tab navigation ────────────────────────────────────────── */ 전)
  • 코드:
    let _t2sLastResult = null;  // Excel export용 — 마지막으로 렌더된 결과 보관
    

3. t2sRenderTable 함수 수정 (src/Web/wwwroot/js/app.js)

  • 변경 사항:
    • 1489번 라인: _t2sLastResult에 결과 저장
    • 1502번 라인: 버튼이 포함된 헤더 HTML 생성

4. t2sExportExcel 함수 추가 (src/Web/wwwroot/js/app.js)

  • 구현 기능:
    • _t2sLastResult가 null인 경우 조건 체크
    • XLSX 라이브러리 로드 실패 확인 (경고 메시지 표시)
    • aoa_to_sheet로 워크시트 생성 (헤더 + 데이터)
    • 컬럼 너비 자동 조정 (최대 30자)
    • query_result_YYYY-MM-DDTHH-MM-SS.xlsx 파일로 다운로드

5. 버튼 스타일 추가 (src/Web/wwwroot/css/style.css)

  • 추가 스타일:
    • .t2s-result-info: flex 레이아웃 (+ gap: 12px)
    • .btn-excel: 수직 정렬, 줄 바꿈 방지, GitHub 그린 테마配色
    • .btn-excel:hover: 더 어두운 그린으로 호버 효과

🔍 검증 결과

  • 빌드 검증: dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q 실행 요청
  • 파일 수정 확인: 모든 파일이 올바르게 수정되었는지 확인
  • 코드 일관성: 식별자명(_t2sLastResult), 헤더 문구(txt), 버튼 라벨(⬇ Excel)이 export2excel.md 규칙에 일치
  • 스타일 일관성: .btn-excel 스타일이 프로젝트 기존 버튼 스타일(btn-a, btn-b)의 색상 체계(녹색 3단계)에 따라 구현되었으나, Excel export용 구분을 위해 별도 색상 배치 선택
  • 실제 동작 검증: 브라우저에서 쿼리 실행 후 Excel 다운로드 테스트 필요

⏭️ 다음 단계

  1. 빌드 검증: dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q 실행
  2. 실시간 테스트: 브라우저에서 Text-to-SQL 탭으로 이동 → 자연어 쿼리 입력 → 실행 → Excel 버튼 클릭 확인
  3. 파일 생성: 다운로드된 .xlsx 파일 확장자 및 내용 확인
  4. 버그 수정: 필요한 경우 LLM(ask_iiot_llm)을 통해 디버깅