import { type Table } from "@tanstack/react-table" import ExcelJS from "exceljs" import { BiddingListItem } from "@/db/schema" import { biddingStatusLabels, contractTypeLabels, biddingTypeLabels, } from "@/db/schema" import { formatDate } from "@/lib/utils" // BiddingListItem 확장 타입 (manager 정보 포함) type BiddingListItemWithManagerCode = BiddingListItem & { bidPicName?: string | null supplyPicName?: string | null } /** * 입찰 목록을 Excel로 내보내기 * - 계약구분, 진행상태, 입찰유형은 라벨(명칭)로 변환 * - 입찰서 제출기간은 submissionStartDate, submissionEndDate 기준 * - 등록일시는 년, 월, 일 형식 */ export async function exportBiddingsToExcel( table: Table, { filename = "입찰목록", onlySelected = false, }: { filename?: string onlySelected?: boolean } = {} ): Promise { // 테이블에서 실제 사용 중인 leaf columns 가져오기 const allColumns = table.getAllLeafColumns() // select, actions 컬럼 제외 const columns = allColumns.filter( (col) => !["select", "actions"].includes(col.id) ) // 헤더 행 생성 (excelHeader 사용) const headerRow = columns.map((col) => { const excelHeader = (col.columnDef.meta as any)?.excelHeader return typeof excelHeader === "string" ? excelHeader : col.id }) // 데이터 행 생성 const rowModel = onlySelected ? table.getFilteredSelectedRowModel() : table.getRowModel() const dataRows = rowModel.rows.map((row) => { const original = row.original return columns.map((col) => { const colId = col.id let value: any // 특별 처리 필요한 컬럼들 switch (colId) { case "contractType": // 계약구분: 라벨로 변환 value = contractTypeLabels[original.contractType as keyof typeof contractTypeLabels] || original.contractType break case "status": // 진행상태: 라벨로 변환 value = biddingStatusLabels[original.status as keyof typeof biddingStatusLabels] || original.status break case "biddingType": // 입찰유형: 라벨로 변환 value = biddingTypeLabels[original.biddingType as keyof typeof biddingTypeLabels] || original.biddingType break case "submissionPeriod": // 입찰서 제출기간: submissionStartDate, submissionEndDate 기준 const startDate = original.submissionStartDate const endDate = original.submissionEndDate if (!startDate || !endDate) { value = "-" } else { const startObj = new Date(startDate) const endObj = new Date(endDate) // KST 변환 (UTC+9) const formatKst = (d: Date) => { const kstDate = new Date(d.getTime() + 9 * 60 * 60 * 1000) return kstDate.toISOString().slice(0, 16).replace('T', ' ') } value = `${formatKst(startObj)} ~ ${formatKst(endObj)}` } break case "updatedAt": // 등록일시: 년, 월, 일 형식만 if (original.updatedAt) { value = formatDate(original.updatedAt, "KR") } else { value = "-" } break case "biddingRegistrationDate": // 입찰등록일: 년, 월, 일 형식만 if (original.biddingRegistrationDate) { value = formatDate(original.biddingRegistrationDate, "KR") } else { value = "-" } break case "projectName": // 프로젝트: 코드와 이름 조합 const code = original.projectCode const name = original.projectName value = code && name ? `${code} (${name})` : (code || name || "-") break case "hasSpecificationMeeting": // 사양설명회: Yes/No value = original.hasSpecificationMeeting ? "Yes" : "No" break default: // 기본값: row.getValue 사용 value = row.getValue(colId) // null/undefined 처리 if (value == null) { value = "" } // 객체인 경우 JSON 문자열로 변환 if (typeof value === "object") { value = JSON.stringify(value) } break } return value }) }) // 최종 sheetData const sheetData = [headerRow, ...dataRows] // ExcelJS로 파일 생성 및 다운로드 await createAndDownloadExcel(sheetData, columns.length, filename) } /** * Excel 파일 생성 및 다운로드 */ async function createAndDownloadExcel( sheetData: any[][], columnCount: number, filename: string ): Promise { // ExcelJS 워크북/시트 생성 const workbook = new ExcelJS.Workbook() const worksheet = workbook.addWorksheet("Sheet1") // 칼럼별 최대 길이 추적 const maxColumnLengths = Array(columnCount).fill(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 (idx === 0) { row.font = { bold: true } row.alignment = { horizontal: "center" } row.eachCell((cell) => { cell.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFCCCCCC" }, } }) } }) // 칼럼 너비 자동 조정 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) }