- Implemented new API endpoints for multi-table Excel upload and auto-detection of table chains. - The GET endpoint `/api/data/multi-table/auto-detect` allows automatic detection of foreign key relationships based on the provided root table. - The POST endpoint `/api/data/multi-table/upload` handles the upload of multi-table data, including validation and logging of the upload process. - Updated the frontend to include options for multi-table Excel upload in the button configuration panel and integrated the corresponding action handler. This feature enhances the data management capabilities by allowing users to upload and manage data across multiple related tables efficiently.
191 lines
5.3 KiB
TypeScript
191 lines
5.3 KiB
TypeScript
/**
|
|
* 엑셀 내보내기 유틸리티
|
|
* xlsx 라이브러리를 사용하여 데이터를 엑셀 파일로 변환
|
|
*/
|
|
|
|
import * as XLSX from "xlsx";
|
|
|
|
/**
|
|
* 데이터를 엑셀 파일로 내보내기
|
|
* @param data 내보낼 데이터 배열
|
|
* @param fileName 파일명 (기본: "export.xlsx")
|
|
* @param sheetName 시트명 (기본: "Sheet1")
|
|
* @param includeHeaders 헤더 포함 여부 (기본: true)
|
|
*/
|
|
export async function exportToExcel(
|
|
data: Record<string, any>[],
|
|
fileName: string = "export.xlsx",
|
|
sheetName: string = "Sheet1",
|
|
includeHeaders: boolean = true
|
|
): Promise<void> {
|
|
try {
|
|
console.log("📥 엑셀 내보내기 시작:", {
|
|
dataCount: data.length,
|
|
fileName,
|
|
sheetName,
|
|
includeHeaders,
|
|
});
|
|
|
|
if (data.length === 0) {
|
|
throw new Error("내보낼 데이터가 없습니다.");
|
|
}
|
|
|
|
// 워크북 생성
|
|
const workbook = XLSX.utils.book_new();
|
|
|
|
// 데이터를 워크시트로 변환
|
|
const worksheet = XLSX.utils.json_to_sheet(data, {
|
|
header: includeHeaders ? undefined : [],
|
|
skipHeader: !includeHeaders,
|
|
});
|
|
|
|
// 컬럼 너비 자동 조정
|
|
const columnWidths = autoSizeColumns(data);
|
|
worksheet["!cols"] = columnWidths;
|
|
|
|
// 워크시트를 워크북에 추가
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
|
|
// 파일 다운로드
|
|
XLSX.writeFile(workbook, fileName);
|
|
|
|
console.log("✅ 엑셀 내보내기 완료:", fileName);
|
|
} catch (error) {
|
|
console.error("❌ 엑셀 내보내기 실패:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 컬럼 너비 자동 조정
|
|
*/
|
|
function autoSizeColumns(data: Record<string, any>[]): Array<{ wch: number }> {
|
|
if (data.length === 0) return [];
|
|
|
|
const keys = Object.keys(data[0]);
|
|
const columnWidths: Array<{ wch: number }> = [];
|
|
|
|
keys.forEach((key) => {
|
|
// 헤더 길이
|
|
let maxWidth = key.length;
|
|
|
|
// 데이터 길이 확인
|
|
data.forEach((row) => {
|
|
const value = row[key];
|
|
const valueLength = value ? String(value).length : 0;
|
|
maxWidth = Math.max(maxWidth, valueLength);
|
|
});
|
|
|
|
// 최소 10, 최대 50으로 제한
|
|
columnWidths.push({ wch: Math.min(Math.max(maxWidth, 10), 50) });
|
|
});
|
|
|
|
return columnWidths;
|
|
}
|
|
|
|
/**
|
|
* 엑셀 파일을 읽어서 JSON 데이터로 변환
|
|
* @param file 읽을 파일
|
|
* @param sheetName 읽을 시트명 (기본: 첫 번째 시트)
|
|
* @returns JSON 데이터 배열
|
|
*/
|
|
export async function importFromExcel(
|
|
file: File,
|
|
sheetName?: string
|
|
): Promise<Record<string, any>[]> {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
try {
|
|
const data = e.target?.result;
|
|
if (!data) {
|
|
reject(new Error("파일을 읽을 수 없습니다."));
|
|
return;
|
|
}
|
|
|
|
// 워크북 읽기 (cellDates: 엑셀 시리얼 날짜를 JS Date 객체로 변환)
|
|
const workbook = XLSX.read(data, { type: "binary", cellDates: true });
|
|
|
|
// 시트 선택 (지정된 시트 또는 첫 번째 시트)
|
|
const targetSheetName = sheetName || workbook.SheetNames[0];
|
|
const worksheet = workbook.Sheets[targetSheetName];
|
|
|
|
if (!worksheet) {
|
|
reject(new Error(`시트 "${targetSheetName}"를 찾을 수 없습니다.`));
|
|
return;
|
|
}
|
|
|
|
// JSON으로 변환 (빈 셀도 포함하여 모든 컬럼 키 유지)
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
|
|
defval: "",
|
|
});
|
|
|
|
// Date 객체를 yyyy-mm-dd 문자열로 변환
|
|
const processedData = (jsonData as Record<string, any>[]).map((row) => {
|
|
const newRow: Record<string, any> = {};
|
|
for (const [key, value] of Object.entries(row)) {
|
|
if (value instanceof Date && !isNaN(value.getTime())) {
|
|
const y = value.getUTCFullYear();
|
|
const m = String(value.getUTCMonth() + 1).padStart(2, "0");
|
|
const d = String(value.getUTCDate()).padStart(2, "0");
|
|
newRow[key] = `${y}-${m}-${d}`;
|
|
} else {
|
|
newRow[key] = value;
|
|
}
|
|
}
|
|
return newRow;
|
|
});
|
|
|
|
console.log("✅ 엑셀 가져오기 완료:", {
|
|
sheetName: targetSheetName,
|
|
rowCount: processedData.length,
|
|
});
|
|
|
|
resolve(processedData);
|
|
} catch (error) {
|
|
console.error("❌ 엑셀 가져오기 실패:", error);
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
reader.onerror = () => {
|
|
reject(new Error("파일 읽기 중 오류가 발생했습니다."));
|
|
};
|
|
|
|
reader.readAsBinaryString(file);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 엑셀 파일의 시트 목록 가져오기
|
|
*/
|
|
export async function getExcelSheetNames(file: File): Promise<string[]> {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
try {
|
|
const data = e.target?.result;
|
|
if (!data) {
|
|
reject(new Error("파일을 읽을 수 없습니다."));
|
|
return;
|
|
}
|
|
|
|
const workbook = XLSX.read(data, { type: "binary" });
|
|
resolve(workbook.SheetNames);
|
|
} catch (error) {
|
|
console.error("❌ 시트 목록 가져오기 실패:", error);
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
reader.onerror = () => {
|
|
reject(new Error("파일 읽기 중 오류가 발생했습니다."));
|
|
};
|
|
|
|
reader.readAsBinaryString(file);
|
|
});
|
|
}
|
|
|