From 10f90dc68dec42e9a64e081cc0dce6a484447290 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 29 Jul 2025 11:48:59 +0000 Subject: (대표님, 박서영, 최겸) document-list-only, gtc, vendorDocu, docu-list-rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/exportFullData.ts | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 lib/exportFullData.ts (limited to 'lib/exportFullData.ts') diff --git a/lib/exportFullData.ts b/lib/exportFullData.ts new file mode 100644 index 00000000..fde5aac2 --- /dev/null +++ b/lib/exportFullData.ts @@ -0,0 +1,189 @@ +import ExcelJS from "exceljs" + +/** + * 컬럼 정의 인터페이스 + */ +export interface ExcelColumnDef { + id: string + header: string + accessor: string | ((row: any) => any) + group?: string +} + +/** + * `exportFullDataToExcel`: 전체 데이터를 Excel로 내보내기 + * - data: 전체 데이터 배열 + * - columns: 컬럼 정의 배열 + * - filename: 다운로드할 엑셀 파일 이름(확장자 제외) + * - useGroupHeader: 그룹화 헤더를 사용할지 여부 (기본 false) + */ +export async function exportFullDataToExcel( + data: TData[], + columns: ExcelColumnDef[], + { + filename = "export", + useGroupHeader = true, + }: { + filename?: string + useGroupHeader?: boolean + } = {} +): Promise { + let sheetData: any[][] + + if (useGroupHeader) { + // ────────────── 2줄 헤더 (row1 = 그룹명, row2 = 컬럼헤더) ────────────── + const row1: string[] = [] + const row2: string[] = [] + + columns.forEach((col) => { + // group + row1.push(col.group ?? "") + // header + row2.push(col.header) + }) + + // 데이터 행 생성 + const dataRows = data.map((item) => + columns.map((col) => { + let val: any + if (typeof col.accessor === "function") { + val = col.accessor(item) + } else { + val = (item as any)[col.accessor] + } + + if (val == null) return "" + return typeof val === "object" ? JSON.stringify(val) : val + }) + ) + + // 최종 sheetData: [ [그룹들...], [헤더들...], ...데이터들 ] + sheetData = [row1, row2, ...dataRows] + } else { + // ────────────── 기존 1줄 헤더 ────────────── + const headerRow = columns.map((col) => col.header) + + // 데이터 행 생성 + const dataRows = data.map((item) => + columns.map((col) => { + let val: any + if (typeof col.accessor === "function") { + val = col.accessor(item) + } else { + val = (item as any)[col.accessor] + } + + if (val == null) return "" + return typeof val === "object" ? JSON.stringify(val) : val + }) + ) + + sheetData = [headerRow, ...dataRows] + } + + // ────────────── ExcelJS 워크북/시트 생성 ────────────── + const workbook = new ExcelJS.Workbook() + const worksheet = workbook.addWorksheet("Sheet1") + + // 칼럼별 최대 길이 추적 + const maxColumnLengths = columns.map(() => 0) + sheetData.forEach((row) => { + row.forEach((cellValue, colIdx) => { + const cellText = cellValue?.toString() ?? "" + if (cellText.length > maxColumnLengths[colIdx]) { + maxColumnLengths[colIdx] = cellText.length + } + }) + }) + + // 시트에 데이터 추가 + 헤더 스타일 + sheetData.forEach((arr, idx) => { + const row = worksheet.addRow(arr) + + // 헤더 스타일 적용 + if (useGroupHeader) { + // 2줄 헤더 + if (idx < 2) { + row.font = { bold: true } + row.alignment = { horizontal: "center" } + row.eachCell((cell) => { + cell.fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFCCCCCC" }, + } + }) + } + } else { + // 1줄 헤더 + if (idx === 0) { + row.font = { bold: true } + row.alignment = { horizontal: "center" } + row.eachCell((cell) => { + cell.fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: "FFCCCCCC" }, + } + }) + } + } + }) + + // ────────────── (핵심) 그룹 헤더 병합 로직 ────────────── + if (useGroupHeader) { + // row1 (인덱스 1) = 그룹명 행 + const groupRowIndex = 1 + const groupRow = worksheet.getRow(groupRowIndex) + + // 같은 값이 연속되는 열을 병합 + let start = 1 // 시작 열 인덱스 (1-based) + let prevValue = groupRow.getCell(start).value + + for (let c = 2; c <= columns.length; c++) { + const cellVal = groupRow.getCell(c).value + if (cellVal !== prevValue) { + // 이전 그룹명이 빈 문자열이 아니면 병합 + if (prevValue && prevValue.toString().trim() !== "") { + worksheet.mergeCells( + groupRowIndex, + start, + groupRowIndex, + c - 1 + ) + } + // 다음 구간 시작 + start = c + prevValue = cellVal + } + } + + // 마지막 구간까지 병합 + if (prevValue && prevValue.toString().trim() !== "") { + worksheet.mergeCells( + groupRowIndex, + start, + groupRowIndex, + columns.length + ) + } + } + + // ────────────── 칼럼 너비 자동 조정 ────────────── + maxColumnLengths.forEach((len, idx) => { + // 최소 너비 10, +2 여백 + worksheet.getColumn(idx + 1).width = Math.max(len + 2, 10) + }) + + // ────────────── 최종 파일 다운로드 ────────────── + const buffer = await workbook.xlsx.writeBuffer() + const blob = new Blob([buffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }) + const url = URL.createObjectURL(blob) + const link = document.createElement("a") + link.href = url + link.download = `${filename}.xlsx` + link.click() + URL.revokeObjectURL(url) +} \ No newline at end of file -- cgit v1.2.3