From 6f22fc9ebc8d175041aa18cf0986592e57d03f63 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 3 Jul 2025 02:47:09 +0000 Subject: (최겸) 기술영업 벤더별 아이템 조회 기능 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/excel-export.tsx | 181 +++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 lib/tech-vendor-possible-items/table/excel-export.tsx (limited to 'lib/tech-vendor-possible-items/table/excel-export.tsx') diff --git a/lib/tech-vendor-possible-items/table/excel-export.tsx b/lib/tech-vendor-possible-items/table/excel-export.tsx new file mode 100644 index 00000000..d3c4dea5 --- /dev/null +++ b/lib/tech-vendor-possible-items/table/excel-export.tsx @@ -0,0 +1,181 @@ +import * as ExcelJS from 'exceljs'; +import { saveAs } from "file-saver"; +import type { TechVendorPossibleItemsData } from '../service'; +import { format } from 'date-fns'; +import { ko } from 'date-fns/locale'; + +/** + * 기술영업 벤더 가능 아이템 데이터를 Excel 파일로 내보내기 + */ +export async function exportTechVendorPossibleItemsToExcel( + data: TechVendorPossibleItemsData[] +) { + try { + // 워크북 생성 + const workbook = new ExcelJS.Workbook(); + workbook.creator = 'Tech Vendor Possible Items Management System'; + workbook.created = new Date(); + + // 워크시트 생성 + const worksheet = workbook.addWorksheet('기술영업 벤더 가능 아이템'); + + // 컬럼 헤더 정의 및 스타일 적용 + worksheet.columns = [ + { header: '번호', key: 'id', width: 10 }, + { header: '벤더코드', key: 'vendorCode', width: 15 }, + { header: '벤더명', key: 'vendorName', width: 25 }, + { header: '벤더타입', key: 'techVendorType', width: 20 }, + { header: '아이템코드', key: 'itemCode', width: 20 }, + { header: '생성일시', key: 'createdAt', width: 20 }, + ]; + + // 헤더 스타일 적용 + const headerRow = worksheet.getRow(1); + headerRow.eachCell((cell) => { + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE6F3FF' } + }; + cell.font = { + bold: true, + color: { argb: 'FF1F4E79' } + }; + cell.border = { + top: { style: 'thin' }, + left: { style: 'thin' }, + bottom: { style: 'thin' }, + right: { style: 'thin' } + }; + cell.alignment = { + vertical: 'middle', + horizontal: 'center' + }; + }); + + // 데이터 추가 + data.forEach((item, index) => { + // 벤더 타입 파싱 + let vendorTypes = ''; + try { + const parsed = JSON.parse(item.techVendorType || "[]"); + vendorTypes = Array.isArray(parsed) ? parsed.join(', ') : item.techVendorType; + } catch { + vendorTypes = item.techVendorType; + } + + const row = worksheet.addRow({ + id: item.id, + vendorCode: item.vendorCode || '-', + vendorName: item.vendorName, + techVendorType: vendorTypes, + itemCode: item.itemCode, + createdAt: format(item.createdAt, 'yyyy-MM-dd HH:mm', { locale: ko }), + }); + + // 데이터 행 스타일 + row.eachCell((cell, colNumber) => { + cell.border = { + top: { style: 'thin' }, + left: { style: 'thin' }, + bottom: { style: 'thin' }, + right: { style: 'thin' } + }; + + if (colNumber === 1) { + // ID 컬럼 가운데 정렬 + cell.alignment = { vertical: 'middle', horizontal: 'center' }; + } else { + // 나머지 컬럼 왼쪽 정렬 + cell.alignment = { vertical: 'middle', horizontal: 'left' }; + } + }); + + // 홀수 행 배경색 + if (index % 2 === 1) { + row.eachCell((cell) => { + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF8F9FA' } + }; + }); + } + }); + + // 요약 정보 워크시트 생성 + const summarySheet = workbook.addWorksheet('요약 정보'); + + const summaryData = [ + ['기술영업 벤더 가능 아이템 현황', ''], + ['', ''], + ['총 항목 수:', data.length.toLocaleString()], + ['고유 벤더 수:', new Set(data.map(item => item.vendorId)).size.toLocaleString()], + ['고유 아이템 수:', new Set(data.map(item => item.itemCode)).size.toLocaleString()], + ['', ''], + ['벤더 타입별 분포:', ''], + ...getVendorTypeDistribution(data), + ['', ''], + ['내보내기 일시:', format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ko })], + ]; + + summaryData.forEach((rowData, index) => { + const row = summarySheet.addRow(rowData); + if (index === 0) { + // 제목 스타일 + row.getCell(1).font = { bold: true, size: 16, color: { argb: 'FF1F4E79' } }; + } else if (typeof rowData[0] === 'string' && rowData[0].includes(':') && rowData[1] === '') { + // 섹션 제목 스타일 + row.getCell(1).font = { bold: true, color: { argb: 'FF1F4E79' } }; + } + }); + + summarySheet.getColumn(1).width = 30; + summarySheet.getColumn(2).width = 20; + + // 파일 생성 및 다운로드 + const buffer = await workbook.xlsx.writeBuffer(); + const blob = new Blob([buffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + }); + + const fileName = `기술영업_벤더_가능_아이템_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`; + saveAs(blob, fileName); + + return { success: true }; + } catch (error) { + console.error("Excel 내보내기 중 오류:", error); + return { + success: false, + error: error instanceof Error ? error.message : "내보내기 중 오류가 발생했습니다." + }; + } +} + +/** + * 벤더 타입별 분포 계산 + */ +function getVendorTypeDistribution(data: TechVendorPossibleItemsData[]): [string, string][] { + const typeCount = new Map(); + + data.forEach(item => { + try { + const parsed = JSON.parse(item.techVendorType || "[]"); + const types = Array.isArray(parsed) ? parsed : [item.techVendorType]; + + types.forEach(type => { + if (type) { + typeCount.set(type, (typeCount.get(type) || 0) + 1); + } + }); + } catch { + if (item.techVendorType) { + typeCount.set(item.techVendorType, (typeCount.get(item.techVendorType) || 0) + 1); + } + } + }); + + return Array.from(typeCount.entries()) + .sort((a, b) => b[1] - a[1]) + .map(([type, count]) => [` - ${type}`, count.toLocaleString()]); +} \ No newline at end of file -- cgit v1.2.3