summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-possible-items/table/excel-export.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendor-possible-items/table/excel-export.tsx')
-rw-r--r--lib/tech-vendor-possible-items/table/excel-export.tsx106
1 files changed, 99 insertions, 7 deletions
diff --git a/lib/tech-vendor-possible-items/table/excel-export.tsx b/lib/tech-vendor-possible-items/table/excel-export.tsx
index d3c4dea5..e6fcceed 100644
--- a/lib/tech-vendor-possible-items/table/excel-export.tsx
+++ b/lib/tech-vendor-possible-items/table/excel-export.tsx
@@ -5,7 +5,7 @@ import { format } from 'date-fns';
import { ko } from 'date-fns/locale';
/**
- * 기술영업 벤더 가능 아이템 데이터를 Excel 파일로 내보내기
+ * 기술영업 벤더 가능 아이템 데이터를 Excel 파일로 내보내기 (새로운 스키마 버전)
*/
export async function exportTechVendorPossibleItemsToExcel(
data: TechVendorPossibleItemsData[]
@@ -19,13 +19,18 @@ export async function exportTechVendorPossibleItemsToExcel(
// 워크시트 생성
const worksheet = workbook.addWorksheet('기술영업 벤더 가능 아이템');
- // 컬럼 헤더 정의 및 스타일 적용
+ // 컬럼 헤더 정의 및 스타일 적용 (새로운 스키마에 맞춰)
worksheet.columns = [
{ header: '번호', key: 'id', width: 10 },
{ header: '벤더코드', key: 'vendorCode', width: 15 },
{ header: '벤더명', key: 'vendorName', width: 25 },
+ { header: '벤더이메일', key: 'vendorEmail', width: 30 },
{ header: '벤더타입', key: 'techVendorType', width: 20 },
{ header: '아이템코드', key: 'itemCode', width: 20 },
+ { header: '공종', key: 'workType', width: 15 },
+ { header: '선종', key: 'shipTypes', width: 20 },
+ { header: '아이템리스트', key: 'itemList', width: 30 },
+ { header: '서브아이템리스트', key: 'subItemList', width: 30 },
{ header: '생성일시', key: 'createdAt', width: 20 },
];
@@ -53,7 +58,7 @@ export async function exportTechVendorPossibleItemsToExcel(
};
});
- // 데이터 추가
+ // 데이터 추가 (새로운 스키마 필드들 포함)
data.forEach((item, index) => {
// 벤더 타입 파싱
let vendorTypes = '';
@@ -68,8 +73,13 @@ export async function exportTechVendorPossibleItemsToExcel(
id: item.id,
vendorCode: item.vendorCode || '-',
vendorName: item.vendorName,
+ vendorEmail: item.vendorEmail || '-',
techVendorType: vendorTypes,
itemCode: item.itemCode,
+ workType: item.workType || '-',
+ shipTypes: item.shipTypes || '-',
+ itemList: item.itemList || '-',
+ subItemList: item.subItemList || '-',
createdAt: format(item.createdAt, 'yyyy-MM-dd HH:mm', { locale: ko }),
});
@@ -89,6 +99,15 @@ export async function exportTechVendorPossibleItemsToExcel(
// 나머지 컬럼 왼쪽 정렬
cell.alignment = { vertical: 'middle', horizontal: 'left' };
}
+
+ // 텍스트 줄바꿈 처리 (긴 텍스트 필드들)
+ if (colNumber >= 9 && colNumber <= 10) { // itemList, subItemList 컬럼
+ cell.alignment = {
+ vertical: 'top',
+ horizontal: 'left',
+ wrapText: true
+ };
+ }
});
// 홀수 행 배경색
@@ -103,19 +122,29 @@ export async function exportTechVendorPossibleItemsToExcel(
}
});
- // 요약 정보 워크시트 생성
+ // 요약 정보 워크시트 생성 (새로운 스키마 통계 포함)
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),
['', ''],
+ ['⚙️ 공종별 분포:', ''],
+ ...getWorkTypeDistribution(data),
+ ['', ''],
+ ['🚢 선종별 분포:', ''],
+ ...getShipTypeDistribution(data),
+ ['', ''],
+ ['📈 데이터 완성도:', ''],
+ ...getDataCompleteness(data),
+ ['', ''],
['내보내기 일시:', format(new Date(), 'yyyy-MM-dd HH:mm:ss', { locale: ko })],
];
@@ -127,10 +156,13 @@ export async function exportTechVendorPossibleItemsToExcel(
} else if (typeof rowData[0] === 'string' && rowData[0].includes(':') && rowData[1] === '') {
// 섹션 제목 스타일
row.getCell(1).font = { bold: true, color: { argb: 'FF1F4E79' } };
+ } else if (typeof rowData[0] === 'string' && rowData[0].includes('📊') || rowData[0].includes('🏢') || rowData[0].includes('⚙️') || rowData[0].includes('🚢') || rowData[0].includes('📈')) {
+ // 이모지 섹션 제목 스타일
+ row.getCell(1).font = { bold: true, color: { argb: 'FF1F4E79' } };
}
});
- summarySheet.getColumn(1).width = 30;
+ summarySheet.getColumn(1).width = 40;
summarySheet.getColumn(2).width = 20;
// 파일 생성 및 다운로드
@@ -178,4 +210,64 @@ function getVendorTypeDistribution(data: TechVendorPossibleItemsData[]): [string
return Array.from(typeCount.entries())
.sort((a, b) => b[1] - a[1])
.map(([type, count]) => [` - ${type}`, count.toLocaleString()]);
+}
+
+/**
+ * 공종별 분포 계산
+ */
+function getWorkTypeDistribution(data: TechVendorPossibleItemsData[]): [string, string][] {
+ const workTypeCount = new Map<string, number>();
+
+ data.forEach(item => {
+ const workType = item.workType || '미분류';
+ workTypeCount.set(workType, (workTypeCount.get(workType) || 0) + 1);
+ });
+
+ return Array.from(workTypeCount.entries())
+ .sort((a, b) => b[1] - a[1])
+ .slice(0, 10) // 상위 10개만 표시
+ .map(([type, count]) => [` - ${type}`, count.toLocaleString()]);
+}
+
+/**
+ * 선종별 분포 계산
+ */
+function getShipTypeDistribution(data: TechVendorPossibleItemsData[]): [string, string][] {
+ const shipTypeCount = new Map<string, number>();
+
+ data.forEach(item => {
+ if (item.shipTypes) {
+ // 여러 선종이 콤마로 구분되어 있을 수 있음
+ const shipTypes = item.shipTypes.split(',').map(s => s.trim());
+ shipTypes.forEach(shipType => {
+ if (shipType) {
+ shipTypeCount.set(shipType, (shipTypeCount.get(shipType) || 0) + 1);
+ }
+ });
+ } else {
+ shipTypeCount.set('미분류', (shipTypeCount.get('미분류') || 0) + 1);
+ }
+ });
+
+ return Array.from(shipTypeCount.entries())
+ .sort((a, b) => b[1] - a[1])
+ .slice(0, 10) // 상위 10개만 표시
+ .map(([type, count]) => [` - ${type}`, count.toLocaleString()]);
+}
+
+/**
+ * 데이터 완성도 계산
+ */
+function getDataCompleteness(data: TechVendorPossibleItemsData[]): [string, string][] {
+ const total = data.length;
+
+ const completeness = [
+ ['벤더이메일 있음', `${data.filter(item => item.vendorEmail).length}/${total} (${((data.filter(item => item.vendorEmail).length / total) * 100).toFixed(1)}%)`],
+ ['공종 있음', `${data.filter(item => item.workType).length}/${total} (${((data.filter(item => item.workType).length / total) * 100).toFixed(1)}%)`],
+ ['선종 있음', `${data.filter(item => item.shipTypes).length}/${total} (${((data.filter(item => item.shipTypes).length / total) * 100).toFixed(1)}%)`],
+ ['아이템리스트 있음', `${data.filter(item => item.itemList).length}/${total} (${((data.filter(item => item.itemList).length / total) * 100).toFixed(1)}%)`],
+ ['서브아이템리스트 있음', `${data.filter(item => item.subItemList).length}/${total} (${((data.filter(item => item.subItemList).length / total) * 100).toFixed(1)}%)`],
+ ];
+
+ return completeness.map(([label, stat]) => [` - ${label}`, stat]);
} \ No newline at end of file