summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-candidates/table/import-button.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-23 09:08:03 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-23 09:08:03 +0000
commita50bc9baea332f996e6bc3a5d70c69f6d2d0f194 (patch)
tree7493b8a4d9cc7cc3375068f1aa10b0067e85988f /lib/tech-vendor-candidates/table/import-button.tsx
parent7402e759857d511add0d3eb19f1fa13cb957c1df (diff)
(대표님, 최겸) 기본계약 템플릿 및 에디터, 기술영업 벤더정보, 파일 보안다운로드, 벤더 document sync 상태 서비스, 메뉴 Config, 기술영업 미사용 제거
Diffstat (limited to 'lib/tech-vendor-candidates/table/import-button.tsx')
-rw-r--r--lib/tech-vendor-candidates/table/import-button.tsx233
1 files changed, 0 insertions, 233 deletions
diff --git a/lib/tech-vendor-candidates/table/import-button.tsx b/lib/tech-vendor-candidates/table/import-button.tsx
deleted file mode 100644
index ad1e6862..00000000
--- a/lib/tech-vendor-candidates/table/import-button.tsx
+++ /dev/null
@@ -1,233 +0,0 @@
-"use client"
-
-import React, { useRef } from 'react'
-import ExcelJS from 'exceljs'
-import { toast } from 'sonner'
-import { Button } from '@/components/ui/button'
-import { Upload, Loader } from 'lucide-react'
-import { createVendorCandidate } from '../service'
-import { Input } from '@/components/ui/input'
-import { useSession } from "next-auth/react" // next-auth 세션 훅 추가
-import { decryptWithServerAction } from '@/components/drm/drmUtils' // DRM 복호화 함수 import
-
-interface ImportExcelProps {
- onSuccess?: () => void
-}
-
-export function ImportVendorCandidatesButton({ onSuccess }: ImportExcelProps) {
- const fileInputRef = useRef<HTMLInputElement>(null)
- const [isImporting, setIsImporting] = React.useState(false)
- const { data: session } = useSession() // status 제거 (사용하지 않음)
-
- // Helper function to get cell value as string
- const getCellValueAsString = (cell: ExcelJS.Cell): string => {
- if (!cell || cell.value === undefined || cell.value === null) return '';
-
- if (typeof cell.value === 'string') return cell.value.trim();
- if (typeof cell.value === 'number') return cell.value.toString();
-
- // Handle rich text
- if (typeof cell.value === 'object' && 'richText' in cell.value) {
- return cell.value.richText.map((rt: { text: string }) => rt.text).join('');
- }
-
- // Handle dates
- if (cell.value instanceof Date) {
- return cell.value.toISOString().split('T')[0];
- }
-
- // Fallback
- return String(cell.value);
- }
-
- const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
- const file = event.target.files?.[0]
- if (!file) return
-
- setIsImporting(true)
-
- try {
- // DRM 복호화 시도 (실패시 원본 파일 사용)
- let data: ArrayBuffer;
- try {
- data = await decryptWithServerAction(file);
- console.log('[Import] DRM 복호화 성공');
- } catch (decryptError) {
- console.warn('[Import] DRM 복호화 실패, 원본 파일 사용:', decryptError);
- data = await file.arrayBuffer();
- }
-
- // Read the Excel file using ExcelJS
- const workbook = new ExcelJS.Workbook()
- await workbook.xlsx.load(data)
-
- // Get the first worksheet
- const worksheet = workbook.getWorksheet(1)
- if (!worksheet) {
- toast.error("No worksheet found in the spreadsheet")
- return
- }
-
- // Check if there's an instruction row
- const hasInstructionRow = worksheet.getRow(1).getCell(1).value !== null &&
- worksheet.getRow(1).getCell(2).value === null;
-
- // Get header row index (row 2 if there's an instruction row, otherwise row 1)
- const headerRowIndex = hasInstructionRow ? 2 : 1;
-
- // Get column headers and their indices
- const headerRow = worksheet.getRow(headerRowIndex);
- const headers: Record<number, string> = {};
- const columnIndices: Record<string, number> = {};
-
- headerRow.eachCell((cell, colNumber) => {
- const header = getCellValueAsString(cell);
- headers[colNumber] = header;
- columnIndices[header] = colNumber;
- });
-
- // Process data rows
- const rows: Record<string, string>[] = [];
- const startRow = headerRowIndex + 1;
-
- for (let i = startRow; i <= worksheet.rowCount; i++) {
- const row = worksheet.getRow(i);
-
- // Skip empty rows
- if (row.cellCount === 0) continue;
-
- // Check if this is likely an example row
- const isExample = i === startRow && worksheet.getRow(i + 1).values?.length === 0;
- if (isExample) continue;
-
- const rowData: Record<string, string> = {};
- let hasData = false;
-
- // Map the data using header indices
- Object.entries(columnIndices).forEach(([header, colIndex]) => {
- const value = getCellValueAsString(row.getCell(colIndex));
- if (value) {
- rowData[header] = value;
- hasData = true;
- }
- });
-
- if (hasData) {
- rows.push(rowData);
- }
- }
-
- if (rows.length === 0) {
- toast.error("No data found in the spreadsheet")
- setIsImporting(false)
- return
- }
-
- // Process each row
- let successCount = 0;
- let errorCount = 0;
-
- // Create promises for all vendor candidate creation operations
- const promises = rows.map(async (row) => {
- try {
- // Map Excel columns to our data model
- const candidateData = {
- companyName: String(row['Company Name'] || ''),
- contactEmail: String(row['Contact Email'] || ''),
- contactPhone: String(row['Contact Phone'] || ''),
- taxId: String(row['Tax ID'] || ''),
- address: String(row['Address'] || ''),
- country: String(row['Country'] || ''),
- source: String(row['Source'] || ''),
- items: String(row['Items'] || ''),
- remark: String(row['Remark'] || row['Remarks'] || ''),
- // Default to COLLECTED if not specified
- status: (row['Status'] || 'COLLECTED') as "COLLECTED" | "INVITED" | "DISCARDED"
- };
-
- // Validate required fields
- if (!candidateData.companyName || !candidateData.source ||
- !candidateData.items) {
- console.error("Missing required fields", candidateData);
- errorCount++;
- return null;
- }
-
- if (!session || !session.user || !session.user.id) {
- toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.")
- return
- }
-
- // Create the vendor candidate (userId는 이미 number 타입이므로 변환 불필요)
- const result = await createVendorCandidate(candidateData)
-
- if (result.error) {
- console.error(`Failed to import row: ${result.error}`, candidateData);
- errorCount++;
- return null;
- }
-
- successCount++;
- return result.data;
- } catch (error) {
- console.error("Error processing row:", error, row);
- errorCount++;
- return null;
- }
- });
-
- // Wait for all operations to complete
- await Promise.all(promises);
-
- // Show results
- if (successCount > 0) {
- toast.success(`Successfully imported ${successCount} vendor candidates`);
- if (errorCount > 0) {
- toast.warning(`Failed to import ${errorCount} rows due to errors`);
- }
- // Call the success callback to refresh data
- onSuccess?.();
- } else if (errorCount > 0) {
- toast.error(`Failed to import all ${errorCount} rows due to errors`);
- }
-
- } catch (error) {
- console.error("Import error:", error);
- toast.error("Error importing data. Please check file format.");
- } finally {
- setIsImporting(false);
- // Reset the file input
- if (fileInputRef.current) {
- fileInputRef.current.value = '';
- }
- }
- }
-
- return (
- <>
- <Input
- type="file"
- ref={fileInputRef}
- onChange={handleImport}
- accept=".xlsx,.xls"
- className="hidden"
- />
- <Button
- variant="outline"
- size="sm"
- onClick={() => fileInputRef.current?.click()}
- disabled={isImporting}
- className="gap-2"
- >
- {isImporting ? (
- <Loader className="size-4 animate-spin" aria-hidden="true" />
- ) : (
- <Upload className="size-4" aria-hidden="true" />
- )}
- <span className="hidden sm:inline">
- {isImporting ? "Importing..." : "Import"}
- </span>
- </Button>
- </>
- )
-} \ No newline at end of file