From 30683aee60eedd39893c79773fbae913349a0417 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 26 Aug 2025 08:23:00 +0000 Subject: (최겸) gtc 레코드 생성 및 기술영업 아이템 import 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../possible-items-toolbar-actions.tsx | 156 ++++++++++++++++++++- 1 file changed, 153 insertions(+), 3 deletions(-) (limited to 'lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx') diff --git a/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx b/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx index 192bf614..371f88f9 100644 --- a/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx +++ b/lib/tech-vendors/possible-items/possible-items-toolbar-actions.tsx @@ -2,7 +2,7 @@ import * as React from "react" import type { Table } from "@tanstack/react-table" -import { Plus, Trash2 } from "lucide-react" +import { Plus, Trash2, Upload, Download } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -19,21 +19,33 @@ import { } from "@/components/ui/alert-dialog" import type { TechVendorPossibleItem } from "../validations" -import { deleteTechVendorPossibleItemsNew } from "../service" +import { + deleteTechVendorPossibleItemsNew, + parsePossibleItemsImportFile, + importPossibleItemsFromExcel, + generatePossibleItemsImportTemplate, + generatePossibleItemsErrorExcel, + type PossibleItemImportData, + type PossibleItemErrorData +} from "../service" interface PossibleItemsTableToolbarActionsProps { table: Table vendorId: number onAdd: () => void // 주석처리 + onRefresh?: () => void // 데이터 새로고침 콜백 } export function PossibleItemsTableToolbarActions({ table, vendorId, onAdd, // 주석처리 + onRefresh, }: PossibleItemsTableToolbarActionsProps) { const [showDeleteAlert, setShowDeleteAlert] = React.useState(false) const [isDeleting, setIsDeleting] = React.useState(false) + const [isImporting, setIsImporting] = React.useState(false) + const fileInputRef = React.useRef(null) const selectedRows = table.getFilteredSelectedRowModel().rows @@ -42,7 +54,7 @@ export function PossibleItemsTableToolbarActions({ try { const ids = selectedRows.map((row) => row.original.id) const { error } = await deleteTechVendorPossibleItemsNew(ids, vendorId) - + if (error) { throw new Error(error) } @@ -50,6 +62,7 @@ export function PossibleItemsTableToolbarActions({ toast.success(`${ids.length}개의 아이템이 삭제되었습니다`) table.resetRowSelection() setShowDeleteAlert(false) + onRefresh?.() // 데이터 새로고침 } catch { toast.error("아이템 삭제 중 오류가 발생했습니다") } finally { @@ -57,9 +70,146 @@ export function PossibleItemsTableToolbarActions({ } } + // 템플릿 다운로드 핸들러 + async function handleTemplateDownload() { + try { + const templateBlob = await generatePossibleItemsImportTemplate() + const url = window.URL.createObjectURL(templateBlob) + const link = document.createElement("a") + link.href = url + link.download = "벤더_possible_items_템플릿.xlsx" + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + toast.success("템플릿 파일이 다운로드되었습니다") + } catch (error) { + toast.error("템플릿 다운로드 중 오류가 발생했습니다") + } + } + + // 파일 선택 핸들러 + function handleFileSelect() { + fileInputRef.current?.click() + } + + // Excel 파일 import 핸들러 + async function handleFileImport(event: React.ChangeEvent) { + const file = event.target.files?.[0] + if (!file) return + + // 파일 타입 검증 + if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) { + toast.error("Excel 파일(.xlsx 또는 .xls)만 업로드 가능합니다") + return + } + + setIsImporting(true) + try { + // Excel 파일 파싱 + const importData: PossibleItemImportData[] = await parsePossibleItemsImportFile(file) + + if (importData.length === 0) { + toast.error("업로드할 데이터가 없습니다") + return + } + + // 데이터 import 실행 + const result = await importPossibleItemsFromExcel(importData) + + // 결과 메시지 생성 + const successMessage = `${result.successCount}개의 아이템이 성공적으로 등록되었습니다` + const failMessage = result.failedRows.length > 0 + ? `, ${result.failedRows.length}개의 아이템 등록 실패` + : "" + + toast.success(successMessage + failMessage) + + // 실패한 행이 있는 경우 에러 파일 다운로드 + if (result.failedRows.length > 0) { + const errorData: PossibleItemErrorData[] = result.failedRows.map(failedRow => ({ + vendorEmail: failedRow.vendorEmail, + itemCode: failedRow.itemCode, + itemType: failedRow.itemType, + error: failedRow.error, + })) + + const errorBlob = await generatePossibleItemsErrorExcel(errorData) + const url = window.URL.createObjectURL(errorBlob) + const link = document.createElement("a") + link.href = url + link.download = "possible_items_import_에러.xlsx" + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + + toast.error("에러 내역 파일이 다운로드되었습니다") + } + + // 파일 입력 초기화 + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + + // 데이터 새로고침 + onRefresh?.() + + } catch (error) { + console.error("Import error:", error) + toast.error(error instanceof Error ? error.message : "데이터 등록 중 오류가 발생했습니다") + } finally { + setIsImporting(false) + } + } + return ( <>
+ {/* 템플릿 다운로드 버튼 */} + + + + + + Excel 템플릿 파일 다운로드 + + + + {/* Excel Import 버튼 */} + + + + + + Excel 파일로 아이템 일괄 등록 + + + + {/* 숨겨진 파일 입력 */} + + {/* 아이템 추가 버튼 주석처리 */}