"use server" import { QnaViewSelect } from "@/db/schema" interface ExportQnaDataParams { format: "csv" | "excel" data: QnaViewSelect[] fields: string[] } /** * Q&A 데이터 내보내기 */ export async function exportQnaData({ format, data, fields }: ExportQnaDataParams) { try { // 필드 매핑 const fieldMapping: Record = { title: "제목", authorName: "작성자", companyName: "회사명", authorDomain: "도메인", vendorType: "벤더타입", totalAnswers: "답변수", totalComments: "댓글수", createdAt: "작성일", lastActivityAt: "최근활동", hasAnswers: "답변여부", isPopular: "인기질문", } // 데이터 변환 const exportData = data.map(qna => { const row: Record = {} fields.forEach(field => { const label = fieldMapping[field] || field switch (field) { case "createdAt": case "lastActivityAt": row[label] = qna[field as keyof QnaViewSelect] ? new Date(qna[field as keyof QnaViewSelect] as string).toLocaleDateString("ko-KR") : "" break case "hasAnswers": row[label] = qna.hasAnswers ? "예" : "아니오" break case "isPopular": row[label] = qna.isPopular ? "예" : "아니오" break case "authorDomain": const domainLabels: Record = { partners: "협력업체", tech: "기술업체", admin: "관리자" } row[label] = domainLabels[qna.authorDomain as string] || qna.authorDomain break case "vendorType": const typeLabels: Record = { vendor: "일반벤더", techVendor: "기술벤더" } row[label] = typeLabels[qna.vendorType as string] || qna.vendorType || "" break default: row[label] = qna[field as keyof QnaViewSelect] || "" } }) return row }) if (format === "csv") { return generateCSV(exportData) } else { return generateExcel(exportData) } } catch (error) { console.error("내보내기 오류:", error) return { success: false, error: "데이터 내보내기 중 오류가 발생했습니다." } } } /** * CSV 파일 생성 */ function generateCSV(data: Record[]) { try { if (data.length === 0) { return { success: false, error: "내보낼 데이터가 없습니다." } } const headers = Object.keys(data[0]) const csvContent = [ headers.join(","), // 헤더 ...data.map(row => headers.map(header => { const value = row[header] // CSV에서 쉼표와 따옴표 이스케이프 if (typeof value === "string" && (value.includes(",") || value.includes('"'))) { return `"${value.replace(/"/g, '""')}"` } return value }).join(",") ) ].join("\n") // BOM 추가 (한글 인코딩을 위해) const csvWithBOM = "\uFEFF" + csvContent const blob = new Blob([csvWithBOM], { type: "text/csv;charset=utf-8;" }) // 파일 다운로드 처리는 클라이언트에서 수행 const url = URL.createObjectURL(blob) const fileName = `qna_export_${new Date().toISOString().split('T')[0]}.csv` // 클라이언트에서 다운로드 처리 if (typeof window !== "undefined") { const link = document.createElement("a") link.href = url link.download = fileName document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } return { success: true, message: "CSV 파일이 다운로드되었습니다." } } catch (error) { return { success: false, error: "CSV 생성 중 오류가 발생했습니다." } } } /** * Excel 파일 생성 */ function generateExcel(data: Record[]) { try { if (data.length === 0) { return { success: false, error: "내보낼 데이터가 없습니다." } } // Excel 생성을 위해 SheetJS 라이브러리 사용 // 실제 구현에서는 xlsx 라이브러리를 사용해야 함 const headers = Object.keys(data[0]) const worksheet = [ headers, // 헤더 행 ...data.map(row => headers.map(header => row[header])) // 데이터 행들 ] // 간단한 CSV 형태로 반환 (실제로는 xlsx 라이브러리 사용 권장) const csvContent = worksheet.map(row => row.map(cell => { if (typeof cell === "string" && (cell.includes(",") || cell.includes('"'))) { return `"${cell.replace(/"/g, '""')}"` } return cell }).join(",") ).join("\n") const csvWithBOM = "\uFEFF" + csvContent const blob = new Blob([csvWithBOM], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }) const url = URL.createObjectURL(blob) const fileName = `qna_export_${new Date().toISOString().split('T')[0]}.xlsx` // 클라이언트에서 다운로드 처리 if (typeof window !== "undefined") { const link = document.createElement("a") link.href = url link.download = fileName document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } return { success: true, message: "Excel 파일이 다운로드되었습니다." } } catch (error) { return { success: false, error: "Excel 생성 중 오류가 발생했습니다." } } } /** * Q&A 통계 내보내기 */ export async function exportQnaStats(data: QnaViewSelect[]) { try { const stats = { 총질문수: data.length, 답변된질문: data.filter(q => q.hasAnswers).length, 답변대기질문: data.filter(q => !q.hasAnswers).length, 인기질문: data.filter(q => q.isPopular).length, 평균답변수: data.reduce((sum, q) => sum + (q.totalAnswers || 0), 0) / data.length, 평균댓글수: data.reduce((sum, q) => sum + (q.totalComments || 0), 0) / data.length, } // 도메인별 통계 const domainStats = data.reduce((acc, q) => { const domain = q.authorDomain || "기타" acc[domain] = (acc[domain] || 0) + 1 return acc }, {} as Record) // 회사별 통계 (상위 10개) const companyStats = data.reduce((acc, q) => { const company = q.companyName || "미지정" acc[company] = (acc[company] || 0) + 1 return acc }, {} as Record) const topCompanies = Object.entries(companyStats) .sort(([,a], [,b]) => b - a) .slice(0, 10) const exportData = [ { 구분: "전체 통계", ...stats }, { 구분: "도메인별", ...domainStats }, ...topCompanies.map(([company, count]) => ({ 구분: "회사별", 회사명: company, 질문수: count })) ] return generateCSV(exportData) } catch (error) { return { success: false, error: "통계 내보내기 중 오류가 발생했습니다." } } }