From bf2db28586569499e44b58999f2e0f33ed4cdeb5 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Wed, 24 Sep 2025 17:36:08 +0900
Subject: (김준회) 구매 요청사항 반영 - vendor-pool 및 avl detail (이진용 프로)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/vendor-pool/table/vendor-pool-table.tsx | 224 +++++++++++++++++-----------
 1 file changed, 138 insertions(+), 86 deletions(-)

(limited to 'lib/vendor-pool/table/vendor-pool-table.tsx')

diff --git a/lib/vendor-pool/table/vendor-pool-table.tsx b/lib/vendor-pool/table/vendor-pool-table.tsx
index 43dd64c1..46a0588d 100644
--- a/lib/vendor-pool/table/vendor-pool-table.tsx
+++ b/lib/vendor-pool/table/vendor-pool-table.tsx
@@ -16,19 +16,20 @@ import { BulkImportDialog } from "./bulk-import-dialog"
 
 import { columns, type VendorPoolItem } from "./vendor-pool-table-columns"
 import { createVendorPool, updateVendorPool, deleteVendorPool } from "../service"
-import type { VendorPool } from "../types"
-
-// 테이블 메타 타입 확장
-declare module "@tanstack/react-table" {
-  interface TableMeta<TData> {
-    onCellUpdate?: (id: string, field: keyof TData, newValue: any) => Promise<void>
-    onCellCancel?: (id: string, field: keyof TData) => void
-    onAction?: (action: string, data?: any) => void
-    onSaveEmptyRow?: (tempId: string) => Promise<void>
-    onCancelEmptyRow?: (tempId: string) => void
-    isEmptyRow?: (id: string) => boolean
-    getPendingChanges?: () => Record<string, Partial<VendorPoolItem>>
-  }
+import type { VendorPool } from "@/db/schema/avl/vendor-pool"
+import { Download, FileSpreadsheet, Upload } from "lucide-react"
+import { ImportVendorPoolButton } from "./vendor-pool-excel-import-button"
+import { exportVendorPoolToExcel, createVendorPoolTemplate } from "../excel-utils"
+
+// vendor-pool 테이블 메타 타입
+interface VendorPoolTableMeta {
+  onCellUpdate?: (id: string | number, field: string, newValue: any) => Promise<void>
+  onCellCancel?: (id: string | number, field: string) => void
+  onAction?: (action: string, data?: any) => void
+  onSaveEmptyRow?: (tempId: string) => Promise<void>
+  onCancelEmptyRow?: (tempId: string) => void
+  isEmptyRow?: (id: string) => boolean
+  getPendingChanges?: () => Record<string, Partial<VendorPoolItem>>
 }
 
 interface VendorPoolTableProps {
@@ -37,6 +38,67 @@ interface VendorPoolTableProps {
   onRefresh?: () => void // 데이터 새로고침 콜백
 }
 
+// 빈 행 기본값 객체
+const createEmptyVendorPoolBase = (): Omit<VendorPool, 'id'> & { id?: string | number } => ({
+  constructionSector: "",
+  htDivision: "",
+  designCategoryCode: "",
+  designCategory: "",
+  equipBulkDivision: "",
+  packageCode: null,
+  packageName: null,
+  materialGroupCode: null,
+  materialGroupName: null,
+  smCode: null,
+  similarMaterialNamePurchase: null,
+  similarMaterialNameOther: null,
+  vendorCode: null,
+  vendorName: "",
+  taxId: null,
+  faTarget: false,
+  faStatus: null,
+  faRemark: null,
+  tier: null,
+  isAgent: false,
+  contractSignerCode: null,
+  contractSignerName: "",
+  headquarterLocation: "",
+  manufacturingLocation: "",
+  avlVendorName: null,
+  similarVendorName: null,
+  hasAvl: false,
+  isBlacklist: false,
+  isBcc: false,
+  purchaseOpinion: null,
+  shipTypeCommon: false,
+  shipTypeAmax: false,
+  shipTypeSmax: false,
+  shipTypeVlcc: false,
+  shipTypeLngc: false,
+  shipTypeCont: false,
+  offshoreTypeCommon: false,
+  offshoreTypeFpso: false,
+  offshoreTypeFlng: false,
+  offshoreTypeFpu: false,
+  offshoreTypePlatform: false,
+  offshoreTypeWtiv: false,
+  offshoreTypeGom: false,
+  picName: null,
+  picEmail: null,
+  picPhone: null,
+  agentName: null,
+  agentEmail: null,
+  agentPhone: null,
+  recentQuoteDate: null,
+  recentQuoteNumber: null,
+  recentOrderDate: null,
+  recentOrderNumber: null,
+  registrationDate: null,
+  registrant: null,
+  lastModifiedDate: null,
+  lastModifier: null,
+})
+
 export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableProps) {
   const { data: session } = useSession()
 
@@ -54,7 +116,7 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
 
 
   // 인라인 편집 핸들러 (일괄 저장용)
-  const handleCellUpdate = React.useCallback(async (id: string, field: keyof VendorPoolItem, newValue: any) => {
+  const handleCellUpdate = React.useCallback(async (id: string | number, field: string, newValue: any) => {
     const isEmptyRow = String(id).startsWith('temp-')
 
     if (isEmptyRow) {
@@ -81,7 +143,7 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
 
 
   // 편집 취소 핸들러
-  const handleCellCancel = React.useCallback((id: string, field: keyof VendorPoolItem) => {
+  const handleCellCancel = React.useCallback((id: string | number, field: string) => {
     const isEmptyRow = String(id).startsWith('temp-')
 
     if (isEmptyRow) {
@@ -142,13 +204,13 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
       for (const [id, changes] of Object.entries(pendingChanges)) {
         try {
           // changes에서 id 필드 제거 (서버에서 자동 생성)
-          const { id: _, ...updateData } = changes as any
+          const { id: _, no: __, selected: ___, ...updateData } = changes
           // 최종변경자를 현재 세션 사용자 정보로 설정
-          const updateDataWithModifier = {
+          const updateDataWithModifier: any = {
             ...updateData,
-            lastModifier: session?.user?.name || ""
+            lastModifier: session?.user?.name || null
           }
-          const result = await updateVendorPool(Number(id), updateDataWithModifier as Partial<VendorPool>)
+          const result = await updateVendorPool(Number(id), updateDataWithModifier)
           if (result) {
             successCount++
           } else {
@@ -190,68 +252,18 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
     if (isCreating) return // 이미 생성 중이면 중복 생성 방지
 
     const tempId = `temp-${Date.now()}`
+    const userName = session?.user?.name || null
+
     const emptyRow: VendorPoolItem = {
-      id: tempId,
+      ...createEmptyVendorPoolBase(),
+      id: tempId, // string 타입으로 설정
       no: 0, // 나중에 계산
       selected: false,
-      constructionSector: "",
-      htDivision: "",
-      designCategoryCode: "",
-      designCategory: "",
-      equipBulkDivision: "",
-      packageCode: "",
-      packageName: "",
-      materialGroupCode: "",
-      materialGroupName: "",
-      smCode: "",
-      similarMaterialNamePurchase: "",
-      similarMaterialNameOther: "",
-      vendorCode: "",
-      vendorName: "",
-      taxId: "",
-      faTarget: false,
-      faStatus: "",
-      faRemark: "",
-      tier: "",
-      isAgent: false,
-      contractSignerCode: "",
-      contractSignerName: "",
-      headquarterLocation: "",
-      manufacturingLocation: "",
-      avlVendorName: "",
-      similarVendorName: "",
-      hasAvl: false,
-      isBlacklist: false,
-      isBcc: false,
-      purchaseOpinion: "",
-      shipTypeCommon: false,
-      shipTypeAmax: false,
-      shipTypeSmax: false,
-      shipTypeVlcc: false,
-      shipTypeLngc: false,
-      shipTypeCont: false,
-      offshoreTypeCommon: false,
-      offshoreTypeFpso: false,
-      offshoreTypeFlng: false,
-      offshoreTypeFpu: false,
-      offshoreTypePlatform: false,
-      offshoreTypeWtiv: false,
-      offshoreTypeGom: false,
-      picName: "",
-      picEmail: "",
-      picPhone: "",
-      agentName: "",
-      agentEmail: "",
-      agentPhone: "",
-      recentQuoteDate: "",
-      recentQuoteNumber: "",
-      recentOrderDate: "",
-      recentOrderNumber: "",
-      registrationDate: "",
-      registrant: session?.user?.name || "",
+      registrationDate: "", // 빈 행에서는 string으로 표시
+      registrant: userName,
       lastModifiedDate: "",
-      lastModifier: session?.user?.name || "",
-    }
+      lastModifier: userName,
+    } as unknown as VendorPoolItem
 
     setEmptyRows(prev => ({ ...prev, [tempId]: emptyRow }))
     setIsCreating(true)
@@ -312,10 +324,10 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
     try {
       setIsSaving(true)
 
-      // id 필드 제거 (서버에서 자동 생성)
-      const { id: _, no: __, selected: ___, ...createData } = finalData
+      // id, no, selected 필드 제거 및 타입 변환
+      const { id: _, no: __, selected: ___, registrationDate: ____, lastModifiedDate: _____, ...createData } = finalData
 
-      const result = await createVendorPool(createData as Omit<VendorPool, 'id' | 'registrationDate' | 'lastModifiedDate'>)
+      const result = await createVendorPool(createData as any)
 
       if (result) {
         toast.success("새 항목이 추가되었습니다.")
@@ -591,8 +603,34 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
           toast.info('저장 기능은 개발 중입니다.')
           break
 
-        case 'excel-import':
-          toast.info('Excel Import 기능은 개발 중입니다.')
+
+        case 'excel-export':
+          try {
+            // 현재 테이블 데이터를 Excel로 내보내기 (ID 포함)
+            const currentData = table.getFilteredRowModel().rows.map(row => row.original)
+            await exportVendorPoolToExcel(
+              currentData, 
+              `vendor-pool-${new Date().toISOString().split('T')[0]}.xlsx`,
+              true // ID 포함
+            )
+            toast.success('Excel 파일이 다운로드되었습니다.')
+          } catch (error) {
+            console.error('Excel export 실패:', error)
+            toast.error('Excel 내보내기에 실패했습니다.')
+          }
+          break
+
+        case 'excel-template':
+          try {
+            // 템플릿 파일 다운로드 (데이터 없음, ID 컬럼 제외)
+            await createVendorPoolTemplate(
+              `vendor-pool-template-${new Date().toISOString().split('T')[0]}.xlsx`
+            )
+            toast.success('Excel 템플릿이 다운로드되었습니다.')
+          } catch (error) {
+            console.error('Excel template export 실패:', error)
+            toast.error('Excel 템플릿 다운로드에 실패했습니다.')
+          }
           break
 
         case 'delete':
@@ -634,7 +672,7 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
         // 제공된 값들만 적용 (빈 값이나 undefined는 건너뜀)
         Object.entries(bulkData).forEach(([field, value]) => {
           if (value !== undefined && value !== null && value !== '') {
-            handleCellUpdate(rowId, field as keyof VendorPoolItem, value)
+            handleCellUpdate(rowId, field as keyof VendorPool, value)
           }
         })
       }
@@ -648,7 +686,7 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
   }, [table, handleCellUpdate])
 
   // 테이블 메타에 핸들러 설정
-  table.options.meta = {
+  const tableMeta: VendorPoolTableMeta = {
     onAction: handleAction,
     onCellUpdate: handleCellUpdate,
     onCellCancel: handleCellCancel,
@@ -657,6 +695,8 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
     isEmptyRow: (id: string) => String(id).startsWith('temp-'),
     getPendingChanges: () => pendingChanges
   }
+  
+  table.options.meta = tableMeta as any
 
 
   // 툴바 액션 핸들러들
@@ -699,12 +739,24 @@ export function VendorPoolTable({ data, pageCount, onRefresh }: VendorPoolTableP
               일괄입력
             </Button>
 
+            <ImportVendorPoolButton onSuccess={onRefresh} />
+
+            <Button
+              onClick={() => handleToolbarAction('excel-export')}
+              variant="outline"
+              size="sm"
+            >
+              <Download className="mr-2 h-4 w-4" />
+              Excel Export
+            </Button>
+
             <Button
-              onClick={() => handleToolbarAction('excel-import')}
+              onClick={() => handleToolbarAction('excel-template')}
               variant="outline"
               size="sm"
             >
-              Excel Import
+              <FileSpreadsheet className="mr-2 h-4 w-4" />
+              Template
             </Button>
 
             <Button
-- 
cgit v1.2.3

