diff options
Diffstat (limited to 'lib/vendor-pool/table/vendor-pool-table.tsx')
| -rw-r--r-- | lib/vendor-pool/table/vendor-pool-table.tsx | 224 |
1 files changed, 138 insertions, 86 deletions
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 |
