From 14f61e24947fb92dd71ec0a7196a6e815f8e66da Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 21 Jul 2025 07:54:26 +0000 Subject: (최겸)기술영업 RFQ 담당자 초대, 요구사항 반영 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contact-table-toolbar-actions.tsx | 264 +++++++++++++-------- 1 file changed, 162 insertions(+), 102 deletions(-) (limited to 'lib/tech-vendors/contacts-table/contact-table-toolbar-actions.tsx') diff --git a/lib/tech-vendors/contacts-table/contact-table-toolbar-actions.tsx b/lib/tech-vendors/contacts-table/contact-table-toolbar-actions.tsx index 7622c6d6..84228a54 100644 --- a/lib/tech-vendors/contacts-table/contact-table-toolbar-actions.tsx +++ b/lib/tech-vendors/contacts-table/contact-table-toolbar-actions.tsx @@ -1,103 +1,163 @@ -"use client" - -import * as React from "react" -import { type Table } from "@tanstack/react-table" -import { Download, Upload } from "lucide-react" -import { toast } from "sonner" - -import { exportTableToExcel } from "@/lib/export" -import { Button } from "@/components/ui/button" -import { TechVendorContact } from "@/db/schema/techVendors" -import { AddContactDialog } from "./add-contact-dialog" -import { importTasksExcel } from "@/lib/tasks/service" - -interface TechVendorContactsTableToolbarActionsProps { - table: Table - vendorId: number -} - -export function TechVendorContactsTableToolbarActions({ table, vendorId }: TechVendorContactsTableToolbarActionsProps) { - // 파일 input을 숨기고, 버튼 클릭 시 참조해 클릭하는 방식 - const fileInputRef = React.useRef(null) - - // 파일이 선택되었을 때 처리 - async function onFileChange(event: React.ChangeEvent) { - const file = event.target.files?.[0] - if (!file) return - - // 파일 초기화 (동일 파일 재업로드 시에도 onChange가 트리거되도록) - event.target.value = "" - - // 서버 액션 or API 호출 - try { - // 예: 서버 액션 호출 - const { errorFile, errorMessage } = await importTasksExcel(file) - - if (errorMessage) { - toast.error(errorMessage) - } - if (errorFile) { - // 에러 엑셀을 다운로드 - const url = URL.createObjectURL(errorFile) - const link = document.createElement("a") - link.href = url - link.download = "errors.xlsx" - link.click() - URL.revokeObjectURL(url) - } else { - // 성공 - toast.success("Import success") - // 필요 시 revalidateTag("tasks") 등 - } - - } catch (error) { - toast.error("파일 업로드 중 오류가 발생했습니다.") - - } - } - - function handleImportClick() { - // 숨겨진 요소를 클릭 - fileInputRef.current?.click() - } - - return ( -
- - - - {/** 3) Import 버튼 (파일 업로드) */} - - {/* - 실제로는 숨겨진 input과 연결: - - accept=".xlsx,.xls" 등으로 Excel 파일만 업로드 허용 - */} - - - {/** 4) Export 버튼 */} - -
- ) +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { Download, Upload } from "lucide-react" +import { toast } from "sonner" +import ExcelJS from "exceljs" + +import { exportTableToExcel } from "@/lib/export" +import { Button } from "@/components/ui/button" +import { TechVendorContact } from "@/db/schema/techVendors" +import { AddContactDialog } from "./add-contact-dialog" +import { + importTechVendorContacts, + generateContactImportTemplate, + parseContactImportFile +} from "@/lib/tech-vendors/service" + +interface TechVendorContactsTableToolbarActionsProps { + table: Table + vendorId: number +} + +export function TechVendorContactsTableToolbarActions({ table, vendorId }: TechVendorContactsTableToolbarActionsProps) { + // 파일 input을 숨기고, 버튼 클릭 시 참조해 클릭하는 방식 + const fileInputRef = React.useRef(null) + + // 파일이 선택되었을 때 처리 + async function onFileChange(event: React.ChangeEvent) { + const file = event.target.files?.[0] + if (!file) return + + // 파일 초기화 (동일 파일 재업로드 시에도 onChange가 트리거되도록) + event.target.value = "" + + try { + // Excel 파일 파싱 + const contactData = await parseContactImportFile(file) + + if (contactData.length === 0) { + toast.error("유효한 데이터가 없습니다. 템플릿 형식을 확인해주세요.") + return + } + + // 서버로 데이터 전송 + const result = await importTechVendorContacts(contactData) + + if (result.successCount > 0) { + toast.success(`${result.successCount}개 연락처가 성공적으로 추가되었습니다.`) + } + + if (result.failedRows.length > 0) { + toast.error(`${result.failedRows.length}개 행에서 오류가 발생했습니다.`) + + // 에러 데이터를 Excel로 다운로드 + const errorWorkbook = new ExcelJS.Workbook() + const errorWorksheet = errorWorkbook.addWorksheet("오류내역") + + // 헤더 추가 + errorWorksheet.columns = [ + { header: "행번호", key: "row", width: 10 }, + { header: "벤더이메일", key: "vendorEmail", width: 25 }, + { header: "담당자명", key: "contactName", width: 20 }, + { header: "담당자이메일", key: "contactEmail", width: 25 }, + { header: "오류내용", key: "error", width: 80, style: { alignment: { wrapText: true } , font: { color: { argb: "FFFF0000" } } } }, + ] + + // 오류 데이터 추가 + result.failedRows.forEach(failedRow => { + errorWorksheet.addRow({ + row: failedRow.row, + error: failedRow.error, + vendorEmail: failedRow.vendorEmail, + contactName: failedRow.contactName, + contactEmail: failedRow.contactEmail, + }) + }) + + const buffer = await errorWorkbook.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 = "contact-import-errors.xlsx" + link.click() + URL.revokeObjectURL(url) + } + + } catch (error) { + toast.error("파일 업로드 중 오류가 발생했습니다.") + console.error("Import error:", error) + } + } + + function handleImportClick() { + // 숨겨진 요소를 클릭 + fileInputRef.current?.click() + } + + async function handleTemplateDownload() { + try { + const templateBlob = await generateContactImportTemplate() + const url = URL.createObjectURL(templateBlob) + const link = document.createElement("a") + link.href = url + link.download = "tech-vendor-contacts-template.xlsx" + link.click() + URL.revokeObjectURL(url) + toast.success("템플릿이 다운로드되었습니다.") + } catch (error) { + toast.error("템플릿 다운로드 중 오류가 발생했습니다.") + console.error("Template download error:", error) + } + } + + return ( +
+ + + + {/** 템플릿 다운로드 버튼 */} + + + {/** Import 버튼 (파일 업로드) */} + + {/* + 실제로는 숨겨진 input과 연결: + - accept=".xlsx,.xls" 등으로 Excel 파일만 업로드 허용 + */} + + + {/** Export 버튼 */} + +
+ ) } \ No newline at end of file -- cgit v1.2.3