diff options
Diffstat (limited to 'lib/vendor-pool/table')
| -rw-r--r-- | lib/vendor-pool/table/vendor-pool-excel-import-button.tsx | 258 | ||||
| -rw-r--r-- | lib/vendor-pool/table/vendor-pool-table-columns.tsx | 2956 | ||||
| -rw-r--r-- | lib/vendor-pool/table/vendor-pool-table.tsx | 224 |
3 files changed, 1856 insertions, 1582 deletions
diff --git a/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx b/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx new file mode 100644 index 00000000..704d4aff --- /dev/null +++ b/lib/vendor-pool/table/vendor-pool-excel-import-button.tsx @@ -0,0 +1,258 @@ +/** + * 특정 컬럼들 복합키로 묶어 UPDATE 처리해야 함. + */ + +"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 { createVendorPool } from '../service' +import { Input } from '@/components/ui/input' +import { useSession } from "next-auth/react" +import { + getCellValueAsString, + parseBoolean, + getAccessorKeyByHeader, + vendorPoolExcelColumns +} from '../excel-utils' + +interface ImportExcelProps { + onSuccess?: () => void +} + +export function ImportVendorPoolButton({ onSuccess }: ImportExcelProps) { + const fileInputRef = useRef<HTMLInputElement>(null) + const [isImporting, setIsImporting] = React.useState(false) + const { data: session, status } = useSession() + + // 헬퍼 함수들은 excel-utils에서 import + + const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0] + if (!file) return + + setIsImporting(true) + + try { + // Read the Excel file using ExcelJS + const data = await file.arrayBuffer() + 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 firstRowText = getCellValueAsString(worksheet.getRow(1).getCell(1)); + const hasInstructionRow = firstRowText.includes('벤더풀 데이터 입력 템플릿') || + firstRowText.includes('입력 가이드 시트') || + firstRowText.includes('입력 가이드') || + (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 columnIndices: Record<string, number> = {}; + + headerRow.eachCell((cell, colNumber) => { + const header = getCellValueAsString(cell); + // Excel 헤더를 통해 accessorKey 찾기 + const accessorKey = getAccessorKeyByHeader(header); + if (accessorKey) { + columnIndices[accessorKey] = colNumber; + } + }); + + // Process data rows + const rows: any[] = []; + 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 empty template row (빈 템플릿 행 건너뛰기) + let hasAnyData = false; + for (let col = 1; col <= row.cellCount; col++) { + if (getCellValueAsString(row.getCell(col)).trim()) { + hasAnyData = true; + break; + } + } + if (!hasAnyData) continue; + + const rowData: Record<string, any> = {}; + let hasData = false; + + // Map the data using accessorKey indices + Object.entries(columnIndices).forEach(([accessorKey, colIndex]) => { + const value = getCellValueAsString(row.getCell(colIndex)); + if (value) { + rowData[accessorKey] = 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 pool creation operations + const promises = rows.map(async (row) => { + try { + // Excel 컬럼 설정을 기반으로 데이터 매핑 + const vendorPoolData: any = {}; + + vendorPoolExcelColumns.forEach(column => { + const { accessorKey, type } = column; + const value = row[accessorKey] || ''; + + if (type === 'boolean') { + vendorPoolData[accessorKey] = parseBoolean(String(value)); + } else if (value === '') { + // 빈 문자열은 null로 설정 (스키마에 맞게) + vendorPoolData[accessorKey] = null; + } else { + vendorPoolData[accessorKey] = String(value); + } + }); + + // 현재 사용자 정보 추가 + vendorPoolData.registrant = session?.user?.name || 'system'; + vendorPoolData.lastModifier = session?.user?.name || 'system'; + + // Validate required fields + if (!vendorPoolData.constructionSector || !vendorPoolData.htDivision || + !vendorPoolData.designCategory || !vendorPoolData.vendorName || + !vendorPoolData.designCategoryCode || !vendorPoolData.equipBulkDivision) { + console.error("Missing required fields", vendorPoolData); + errorCount++; + return null; + } + + // Validate field lengths and formats + const validationErrors: string[] = []; + + if (vendorPoolData.designCategoryCode && vendorPoolData.designCategoryCode.length > 2) { + validationErrors.push(`설계기능코드는 2자리 이하여야 합니다: ${vendorPoolData.designCategoryCode}`); + } + + if (vendorPoolData.equipBulkDivision && vendorPoolData.equipBulkDivision.length > 1) { + validationErrors.push(`Equip/Bulk 구분은 1자리여야 합니다: ${vendorPoolData.equipBulkDivision}`); + } + + if (vendorPoolData.constructionSector && !['조선', '해양'].includes(vendorPoolData.constructionSector)) { + validationErrors.push(`공사부문은 '조선' 또는 '해양'이어야 합니다: ${vendorPoolData.constructionSector}`); + } + + if (vendorPoolData.htDivision && !['H', 'T', '공통'].includes(vendorPoolData.htDivision)) { + validationErrors.push(`H/T구분은 'H', 'T' 또는 '공통'이어야 합니다: ${vendorPoolData.htDivision}`); + } + + if (validationErrors.length > 0) { + console.error("Validation errors:", validationErrors, vendorPoolData); + errorCount++; + return null; + } + + if (!session || !session.user || !session.user.id) { + toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.") + return + } + + // Create the vendor pool entry + const result = await createVendorPool(vendorPoolData as any) + + if (!result) { + console.error(`Failed to import row - createVendorPool returned null:`, vendorPoolData); + errorCount++; + return null; + } + + successCount++; + return result; + } 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(`${successCount}개 항목이 성공적으로 가져와졌습니다.`); + if (errorCount > 0) { + toast.warning(`${errorCount}개 항목 가져오기에 실패했습니다. 콘솔에서 자세한 오류를 확인하세요.`); + } + // Call the success callback to refresh data + onSuccess?.(); + } else if (errorCount > 0) { + toast.error(`모든 ${errorCount}개 항목 가져오기에 실패했습니다. 데이터 형식을 확인하세요.`); + } + + } 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> + </> + ) +} diff --git a/lib/vendor-pool/table/vendor-pool-table-columns.tsx b/lib/vendor-pool/table/vendor-pool-table-columns.tsx index 8f09e684..1f0c455e 100644 --- a/lib/vendor-pool/table/vendor-pool-table-columns.tsx +++ b/lib/vendor-pool/table/vendor-pool-table-columns.tsx @@ -1,7 +1,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { Button } from "@/components/ui/button" -import { MoreHorizontal, Eye, Edit, Trash2 } from "lucide-react" -import { type ColumnDef, TableMeta } from "@tanstack/react-table" +import { Trash2 } from "lucide-react" +import { type ColumnDef } from "@tanstack/react-table" import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { EditableCell } from "@/components/data-table/editable-cell" @@ -11,1566 +11,1530 @@ const getIsModified = (table: any, rowId: string, fieldName: string) => { return String(rowId) in pendingChanges && fieldName in pendingChanges[String(rowId)] } -// 테이블 메타 타입 확장 -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 - onTaxIdChange?: (id: string, taxId: string) => Promise<void> - onMaterialGroupCodeChange?: (id: string, materialGroupCode: string) => Promise<void> - } +// 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 + onTaxIdChange?: (id: string, taxId: string) => Promise<void> } -// Vendor Pool 데이터 타입 -export type VendorPoolItem = { - id: string - no: number - selected: boolean - constructionSector: string // 공사부문: 조선 또는 해양 - htDivision: string // H/T구분: H 또는 T - designCategoryCode: string // 설계기능(공종) 코드: 2자리 영문대문자 - designCategory: string // 설계기능(공종): 전장 등 - equipBulkDivision: string // Equip/Bulk 구분: E 또는 B - // 패키지 정보 (스키마: packageCode, packageName) - packageCode: string - packageName: string - // 자재그룹 (스키마: materialGroupCode, materialGroupName) - materialGroupCode: string - materialGroupName: string - smCode: string // SM Code - similarMaterialNamePurchase: string // 유사자재명 (구매) - similarMaterialNameOther: string // 유사자재명 (구매 외) - // 협력업체 정보 (스키마: vendorCode, vendorName) - vendorCode: string - vendorName: string - taxId: string // 사업자번호(Tax ID) - faTarget: boolean // FA대상 - faStatus: string // FA현황 - faRemark: string // FA상세(Remark) - tier: string // 등급(Tier) - isAgent: boolean // Agent 여부 - // 계약서명주체 (스키마: contractSignerCode, contractSignerName) - contractSignerCode: string - contractSignerName: string - headquarterLocation: string // 본사 위치(국가) - manufacturingLocation: string // 제작/선적지(국가) - avlVendorName: string // AVL 등재업체명 - similarVendorName: string // 유사업체명(기술영업) - hasAvl: boolean // AVL: 존재여부 - isBlacklist: boolean // Blacklist - isBcc: boolean // BCC - purchaseOpinion: string // 구매의견 - // AVL 적용 선종(조선) - shipTypeCommon: boolean // 공통 - shipTypeAmax: boolean // A-max - shipTypeSmax: boolean // S-max - shipTypeVlcc: boolean // VLCC - shipTypeLngc: boolean // LNGC - shipTypeCont: boolean // CONT - // AVL 적용 선종(해양) - offshoreTypeCommon: boolean // 공통 - offshoreTypeFpso: boolean // FPSO - offshoreTypeFlng: boolean // FLNG - offshoreTypeFpu: boolean // FPU - offshoreTypePlatform: boolean // Platform - offshoreTypeWtiv: boolean // WTIV - offshoreTypeGom: boolean // GOM - // eVCP 미등록 정보 - picName: string // PIC(담당자) - picEmail: string // PIC(E-mail) - picPhone: string // PIC(Phone) - agentName: string // Agent(담당자) - agentEmail: string // Agent(E-mail) - agentPhone: string // Agent(Phone) - // 업체 실적 현황 - recentQuoteDate: string // 최근견적일 - recentQuoteNumber: string // 최근견적번호 - recentOrderDate: string // 최근발주일 - recentOrderNumber: string // 최근발주번호 - // 업데이트 히스토리 - registrationDate: string // 등재일 - registrant: string // 등재자 - lastModifiedDate: string // 최종변경일 - lastModifier: string // 최종변경자 +// Vendor Pool 데이터 타입 - 스키마 기반 + 테이블용 추가 필드 +import type { VendorPool } from "@/db/schema/avl/vendor-pool" +import { DisciplineCode, EngineeringDisciplineSelector } from "@/components/common/discipline" +import { MaterialGroupSelectorDialogSingle } from "@/components/common/material/material-group-selector-dialog-single" +import type { MaterialSearchItem } from "@/lib/material/material-group-service" +import { VendorTierSelector } from "@/components/common/selectors/vendor-tier/vendor-tier-selector" +import { VendorSelectorDialogSingle } from "@/components/common/vendor/vendor-selector-dialog-single" +import type { VendorSearchItem } from "@/components/common/vendor/vendor-service" +import { PlaceOfShippingSelectorDialogSingle } from "@/components/common/selectors/place-of-shipping/place-of-shipping-selector" + +export type VendorPoolItem = Omit<VendorPool, 'registrationDate' | 'lastModifiedDate'> & { + id: string | number // temp-로 시작하는 경우 string, 실제 데이터는 number + no: number // 테이블 표시용 순번 + selected: boolean // 테이블 선택 상태 + registrationDate: string // 표시용 string으로 변환 + lastModifiedDate: string // 표시용 string으로 변환 } // 테이블 컬럼 정의 export const columns: ColumnDef<VendorPoolItem>[] = [ - { - id: "select", - header: ({ table }) => ( - <Checkbox - checked={ - table.getIsAllPageRowsSelected() || - (table.getIsSomePageRowsSelected() && "indeterminate") - } - onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - <Checkbox - checked={row.getIsSelected()} - onCheckedChange={(value) => row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - enableSorting: false, - enableHiding: false, - size: 40, - }, - { - accessorKey: "id", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="No." /> - ), - cell: ({ row }) => { - const id = String(row.original.id) - - // 빈 행의 경우 No. 표시하지 않음 - if (id.startsWith('temp-')) { - return <div className="text-sm text-muted-foreground italic">신규</div> - } + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + size: 40, + }, + { + accessorKey: "id", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="ID" /> + ), + cell: ({ row }) => { + const id = String(row.original.id) + + // 빈 행의 경우 신규 표시 + if (id.startsWith('temp-')) { + return <div className="text-sm text-muted-foreground italic">신규</div> + } - // vendor_pool 테이블의 실제 id 표시 - return <div className="text-sm font-mono">{id}</div> - }, - size: 60, - }, - { - accessorKey: "constructionSector", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">공사부문 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("constructionSector") - const isEmptyRow = String(row.original.id).startsWith('temp-') - - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "constructionSector", newValue) - } - } + // 실제 ID 표시 + return <div className="text-sm font-mono">{id}</div> + }, + size: 60, + }, + { + accessorKey: "constructionSector", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">공사부문 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("constructionSector") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + const meta = table.options.meta as VendorPoolTableMeta + if (meta?.onCellUpdate) { + await meta.onCellUpdate(row.original.id, "constructionSector", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "constructionSector") - - return ( - <EditableCell - value={value} - type="select" - onSave={onSave} - options={[ - { label: "조선", value: "조선" }, - { label: "해양", value: "해양" } - ]} - placeholder="공사부문 선택" - autoSave={true} - disabled={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 100, - }, - { - accessorKey: "htDivision", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">H/T구분 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("htDivision") - const isEmptyRow = String(row.original.id).startsWith('temp-') - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "htDivision", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "constructionSector") - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "htDivision") - - return ( - <EditableCell - value={value} - type="select" - onSave={onSave} - options={[ - { label: "H", value: "H" }, - { label: "T", value: "T" }, - { label: "공통", value: "공통" }, - ]} - placeholder="H/T 선택" - autoSave={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 80, - }, - { - accessorKey: "designCategoryCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="설계기능코드" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("designCategoryCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "designCategoryCode", newValue) - } - } + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "조선", value: "조선" }, + { label: "해양", value: "해양" } + ]} + placeholder="공사부문 선택" + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 100, + }, + { + accessorKey: "htDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">H/T *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("htDivision") + const isEmptyRow = String(row.original.id).startsWith('temp-') + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "htDivision", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "designCategoryCode") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="설계기능코드 입력" - maxLength={10} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "designCategory", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">설계기능(공종) *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("designCategory") - const isEmptyRow = String(row.original.id).startsWith('temp-') - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "designCategory", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "htDivision") - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "designCategory") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="설계기능(공종) 입력" - maxLength={50} - autoSave={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "equipBulkDivision", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Equip/Bulk 구분" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("equipBulkDivision") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "equipBulkDivision", newValue) - } - } + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "H", value: "H" }, + { label: "T", value: "T" }, + { label: "공통", value: "공통" }, + ]} + placeholder="H/T 선택" + autoSave={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 80, + }, + { + accessorKey: "designCategory", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">설계기능(공종) *</span>} /> + ), + cell: ({ row, table }) => { + const designCategoryCode = row.original.designCategoryCode as string + const designCategory = row.original.designCategory as string + + // 현재 선택된 discipline 구성 + const selectedDiscipline: DisciplineCode | undefined = designCategoryCode && designCategory ? { + CD: designCategoryCode, + USR_DF_CHAR_18: designCategory + } : undefined + + const onDisciplineSelect = async (discipline: DisciplineCode) => { + console.log('선택된 설계공종:', discipline) + console.log('행 ID:', row.original.id) + + // 설계기능코드와 설계기능(공종) 필드 모두 업데이트 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "designCategoryCode", discipline.CD) + await table.options.meta.onCellUpdate(row.original.id, "designCategory", discipline.USR_DF_CHAR_18) + } else { + console.error('onCellUpdate가 정의되지 않음') + } + } - return ( - <EditableCell - value={value} - type="select" - onSave={onSave} - options={[ - { label: "E (Equip)", value: "E" }, - { label: "B (Bulk)", value: "B" }, - { label: "S (강재)", value: "S" } - ]} - placeholder="구분 선택" - autoSave={false} - /> - ) - }, - size: 120, - }, - { - accessorKey: "packageCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="패키지 코드" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("packageCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "packageCode", newValue) - } - } + return ( + <EngineeringDisciplineSelector + selectedDiscipline={selectedDiscipline} + onDisciplineSelect={onDisciplineSelect} + disabled={false} + placeholder="설계공종을 선택하세요" + /> + ) + }, + size: 260, + }, + { + accessorKey: "equipBulkDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Equip/Bulk" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("equipBulkDivision") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "equipBulkDivision", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "packageCode") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="패키지 코드 입력" - maxLength={50} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "packageName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="패키지 명" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("packageName") - const isEmptyRow = String(row.original.id).startsWith('temp-') - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "packageName", newValue) - } - } + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "E (Equip)", value: "E" }, + { label: "B (Bulk)", value: "B" }, + { label: "S (강재)", value: "S" } + ]} + placeholder="구분 선택" + autoSave={false} + /> + ) + }, + size: 120, + }, + { + accessorKey: "packageCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="패키지 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("packageCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "packageCode", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "packageName") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="패키지 명 입력" - maxLength={100} - autoSave={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "materialGroupCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">자재그룹 코드 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("materialGroupCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "materialGroupCode", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "packageCode") - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "materialGroupCode") + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="패키지 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "packageName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="패키지 명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("packageName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "packageName", newValue) + } + } - const onChange = async (newValue: any) => { - if (table.options.meta?.onMaterialGroupCodeChange) { - await table.options.meta.onMaterialGroupCodeChange(row.original.id, newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "packageName") - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - onChange={onChange} - placeholder="자재그룹 코드 입력" - maxLength={50} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "materialGroupName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">자재그룹 명 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("materialGroupName") - const isEmptyRow = String(row.original.id).startsWith('temp-') - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "materialGroupName", newValue) - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="패키지 명 입력" + maxLength={100} + autoSave={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 200, + }, + { + accessorKey: "materialGroupName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">자재그룹 *</span>} /> + ), + cell: ({ row, table }) => { + const materialGroupCode = row.original.materialGroupCode as string + const materialGroupName = row.original.materialGroupName as string + + // 현재 선택된 material 구성 + const selectedMaterial: MaterialSearchItem | null = materialGroupCode && materialGroupName ? { + materialGroupCode, + materialGroupDescription: materialGroupName, + displayText: `${materialGroupCode} - ${materialGroupName}` + } : null + + const onMaterialSelect = async (material: MaterialSearchItem | null) => { + console.log('선택된 자재그룹:', material) + + if (material) { + // 자재그룹코드와 자재그룹명 필드 모두 업데이트 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "materialGroupCode", material.materialGroupCode) + await table.options.meta.onCellUpdate(row.original.id, "materialGroupName", material.materialGroupDescription) } - - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "materialGroupName") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="자재그룹 명 입력" - maxLength={100} - autoSave={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "smCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="SM Code" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("smCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "smCode", newValue) - } + } else { + // 선택 해제 시 빈 값으로 설정 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "materialGroupCode", "") + await table.options.meta.onCellUpdate(row.original.id, "materialGroupName", "") } + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="SM Code 입력" - maxLength={50} - /> - ) - }, - size: 100, - }, - { - accessorKey: "similarMaterialNamePurchase", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="유사자재명(구매)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("similarMaterialNamePurchase") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNamePurchase", newValue) - } - } + return ( + <MaterialGroupSelectorDialogSingle + selectedMaterial={selectedMaterial} + onMaterialSelect={onMaterialSelect} + disabled={false} + triggerLabel="자재그룹 선택" + placeholder="자재그룹을 검색하세요..." + title="자재그룹 선택" + description="필요한 자재그룹을 검색하고 선택해주세요." + /> + ) + }, + size: 400, + }, + { + accessorKey: "smCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="SM Code" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("smCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "smCode", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="유사자재명(구매) 입력" - maxLength={100} - autoSave={false} - /> - ) - }, - size: 140, - }, - { - accessorKey: "similarMaterialNameOther", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="유사자재명(구매외)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("similarMaterialNameOther") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNameOther", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="SM Code 입력" + maxLength={50} + /> + ) + }, + size: 200, + }, + { + accessorKey: "similarMaterialNamePurchase", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="자재명 (검색 키워드)" /> + // 이전에는 컬럼명이 '유사자재명(구매외)' 였음. + ), + cell: ({ row, table }) => { + const value = row.getValue("similarMaterialNamePurchase") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNamePurchase", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="유사자재명(구매외) 입력" - maxLength={100} - autoSave={false} - /> - ) - }, - size: 140, - }, - { - accessorKey: "vendorCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="협력업체 코드" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("vendorCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "vendorCode", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사자재명(구매) 입력" + maxLength={100} + autoSave={false} + /> + ) + }, + size: 250, + }, + { + accessorKey: "similarMaterialNameOther", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="유사자재명(구매외)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("similarMaterialNameOther") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNameOther", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "vendorCode") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="협력업체 코드 입력" - maxLength={50} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 130, - }, - { - accessorKey: "vendorName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">협력업체 명 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("vendorName") - const isEmptyRow = String(row.original.id).startsWith('temp-') - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "vendorName", newValue) - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사자재명(구매외) 입력" + maxLength={100} + autoSave={false} + /> + ) + }, + size: 140, + }, + { + accessorKey: "vendorSelector", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체 선택" /> + ), + cell: ({ row, table }) => { + const vendorCode = row.original.vendorCode as string + const vendorName = row.original.vendorName as string + + // 현재 선택된 vendor 구성 + const selectedVendor: VendorSearchItem | null = vendorCode && vendorName ? { + id: 0, // 실제로는 vendorId가 있어야 하지만 여기서는 임시로 0 사용 + vendorName, + vendorCode: vendorCode || null, + status: "ACTIVE", // 임시 값 + displayText: vendorName + (vendorCode ? ` (${vendorCode})` : "") + } : null + + const onVendorSelect = async (vendor: VendorSearchItem | null) => { + console.log('선택된 협력업체:', vendor) + + if (vendor) { + // 협력업체코드, 협력업체명, 사업자번호 필드 모두 업데이트 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorCode", vendor.vendorCode || "") + await table.options.meta.onCellUpdate(row.original.id, "vendorName", vendor.vendorName) + await table.options.meta.onCellUpdate(row.original.id, "taxId", vendor.taxId || "") } - - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "vendorName") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="협력업체 명 입력" - maxLength={100} - autoSave={false} - initialEditMode={isEmptyRow} - isModified={isModified} - /> - ) - }, - size: 130, - }, - { - accessorKey: "taxId", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">사업자번호 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("taxId") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "taxId", newValue) - } + } else { + // 선택 해제 시 빈 값으로 설정 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorCode", "") + await table.options.meta.onCellUpdate(row.original.id, "vendorName", "") + await table.options.meta.onCellUpdate(row.original.id, "taxId", "") } + } + } - const onChange = async (newValue: any) => { - if (table.options.meta?.onTaxIdChange) { - await table.options.meta.onTaxIdChange(row.original.id, newValue) - } - } + return ( + <VendorSelectorDialogSingle + selectedVendor={selectedVendor} + onVendorSelect={onVendorSelect} + disabled={false} + triggerLabel="협력업체 선택" + placeholder="협력업체를 검색하세요..." + title="협력업체 선택" + description="협력업체를 검색하고 선택해주세요." + statusFilter="ACTIVE" + /> + ) + }, + size: 150, + }, + { + accessorKey: "vendorCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("vendorCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorCode", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - onChange={onChange} - placeholder="사업자번호 입력" - maxLength={50} - /> - ) - }, - size: 120, - }, - { - accessorKey: "faTarget", - header: "FA대상", - cell: ({ row, table }) => { - const value = row.getValue("faTarget") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "faTarget", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "vendorCode") - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "faTarget") - - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - isModified={isModified} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "faStatus", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="FA현황" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("faStatus") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "faStatus", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="협력업체 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">협력업체 명 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("vendorName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorName", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="FA현황 입력" - maxLength={50} - /> - ) - }, - size: 100, - }, - { - accessorKey: "faRemark", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="FA상세" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("faRemark") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "faRemark", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "vendorName") - return ( - <EditableCell - value={value} - type="textarea" - onSave={onSave} - placeholder="FA상세 입력" - maxLength={500} - autoSave={false} - /> - ) - }, - size: 120, - }, - { - accessorKey: "tier", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">등급 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("tier") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "tier", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="협력업체 명 입력" + maxLength={100} + autoSave={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "taxId", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">사업자번호 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("taxId") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "taxId", newValue) + } + } - return ( - <EditableCell - value={value} - type="select" - onSave={onSave} - options={[ - { label: "Tier 1", value: "Tier 1" }, - { label: "Tier 2", value: "Tier 2" }, - { label: "Tier 3", value: "Tier 3" }, - { label: "Tier 4", value: "Tier 4" }, - ]} - placeholder="등급 선택" - /> - ) - }, - size: 80, - }, - { - accessorKey: "isAgent", - header: "Agent 여부", - cell: ({ row, table }) => { - const value = row.getValue("isAgent") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "isAgent", newValue) - } - } + const onChange = async (newValue: any) => { + if (table.options.meta?.onTaxIdChange) { + await table.options.meta.onTaxIdChange(row.original.id, newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 100, - }, - { - accessorKey: "contractSignerCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="계약서명주체 코드" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("contractSignerCode") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "contractSignerCode", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + onChange={onChange} + placeholder="사업자번호 입력" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "faTarget", + header: "FA대상", + cell: ({ row, table }) => { + const value = row.getValue("faTarget") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "faTarget", newValue) + } + } - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "contractSignerCode") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="계약서명주체 코드 입력" - maxLength={50} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "contractSignerName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">계약서명주체 명 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("contractSignerName") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "contractSignerName", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "faTarget") - // 수정 여부 확인 - const isModified = getIsModified(table, row.original.id, "contractSignerName") - - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="계약서명주체 명 입력" - maxLength={100} - autoSave={false} - isModified={isModified} - /> - ) - }, - size: 120, - }, - { - accessorKey: "headquarterLocation", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">본사 위치 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("headquarterLocation") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "headquarterLocation", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + isModified={isModified} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "faStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="FA현황" /> + ), + cell: ({ row }) => { + const value = row.original.faStatus as string - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="본사 위치 입력" - maxLength={50} - /> - ) - }, - size: 100, - }, - { - accessorKey: "manufacturingLocation", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">제작/선적지 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("manufacturingLocation") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "manufacturingLocation", newValue) - } - } + // 'O'인 경우에만 'O'를 표시, 그 외에는 빈 셀 + const displayValue = value === "O" ? "O" : "" - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="제작/선적지 입력" - maxLength={50} - /> - ) - }, - size: 110, - }, - { - accessorKey: "avlVendorName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">AVL 등재업체명 *</span>} /> - ), - cell: ({ row, table }) => { - const value = row.getValue("avlVendorName") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "avlVendorName", newValue) - } - } + return ( + <div className="px-2 py-1 text-sm text-center"> + {displayValue} + </div> + ) + }, + size: 120, + }, + // { + // accessorKey: "faRemark", + // header: ({ column }) => ( + // <DataTableColumnHeaderSimple column={column} title="FA상세" /> + // ), + // cell: ({ row, table }) => { + // const value = row.getValue("faRemark") + // const onSave = async (newValue: any) => { + // if (table.options.meta?.onCellUpdate) { + // await table.options.meta.onCellUpdate(row.original.id, "faRemark", newValue) + // } + // } + + // return ( + // <EditableCell + // value={value} + // type="textarea" + // onSave={onSave} + // placeholder="FA상세 입력" + // maxLength={500} + // autoSave={false} + // /> + // ) + // }, + // size: 120, + // }, + { + accessorKey: "tier", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">등급 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.original.tier as string - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="AVL 등재업체명 입력" - maxLength={100} - /> - ) - }, - size: 140, - }, - { - accessorKey: "similarVendorName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="유사업체명" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("similarVendorName") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "similarVendorName", newValue) - } - } + const onValueChange = async (newValue: string) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "tier", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="유사업체명 입력" - maxLength={100} - /> - ) - }, - size: 130, - }, - { - accessorKey: "hasAvl", - header: "AVL", - cell: ({ row, table }) => { - const value = row.getValue("hasAvl") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "hasAvl", newValue) - } - } + return ( + <VendorTierSelector + value={value} + onValueChange={onValueChange} + disabled={false} + placeholder="등급 선택" + /> + ) + }, + size: 200, + }, + { + accessorKey: "isAgent", + header: "Agent 여부", + cell: ({ row, table }) => { + const value = row.getValue("isAgent") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isAgent", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 60, - }, - { - accessorKey: "isBlacklist", - header: "Blacklist", - cell: ({ row, table }) => { - const value = row.getValue("isBlacklist") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "isBlacklist", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 100, + }, + { + accessorKey: "contractSignerCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="계약서명주체 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("contractSignerCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerCode", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 100, - }, - { - accessorKey: "isBcc", - header: "BCC", - cell: ({ row, table }) => { - const value = row.getValue("isBcc") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "isBcc", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "contractSignerCode") - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "purchaseOpinion", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="구매의견" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("purchaseOpinion") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "purchaseOpinion", newValue) - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="계약서명주체 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "contractSignerSelector", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="계약서명주체 선택" /> + ), + cell: ({ row, table }) => { + const contractSignerCode = row.original.contractSignerCode as string + const contractSignerName = row.original.contractSignerName as string + + // 현재 선택된 contract signer 구성 + const selectedVendor: VendorSearchItem | null = contractSignerCode && contractSignerName ? { + id: 0, // 실제로는 vendorId가 있어야 하지만 여기서는 임시로 0 사용 + vendorName: contractSignerName, + vendorCode: contractSignerCode || null, + status: "ACTIVE", // 임시 값 + displayText: contractSignerName + (contractSignerCode ? ` (${contractSignerCode})` : "") + } : null + + const onVendorSelect = async (vendor: VendorSearchItem | null) => { + console.log('선택된 계약서명주체:', vendor) + + if (vendor) { + // 계약서명주체코드와 계약서명주체명 필드 업데이트 + // 사업자번호는 협력업체 선택 시에만 업데이트됨 (taxId 필드가 하나만 존재) + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerCode", vendor.vendorCode || "") + await table.options.meta.onCellUpdate(row.original.id, "contractSignerName", vendor.vendorName) } - - return ( - <EditableCell - value={value} - type="textarea" - onSave={onSave} - placeholder="구매의견 입력" - maxLength={500} - /> - ) - }, - size: 120, - }, - { - accessorKey: "shipTypeCommon", - header: "공통", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeCommon") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeCommon", newValue) - } + } else { + // 선택 해제 시 빈 값으로 설정 + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerCode", "") + await table.options.meta.onCellUpdate(row.original.id, "contractSignerName", "") } + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "shipTypeAmax", - header: "A-max", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeAmax") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeAmax", newValue) - } - } + return ( + <VendorSelectorDialogSingle + selectedVendor={selectedVendor} + onVendorSelect={onVendorSelect} + disabled={false} + triggerLabel="계약서명주체 선택" + placeholder="계약서명주체를 검색하세요..." + title="계약서명주체 선택" + description="계약서명주체를 검색하고 선택해주세요." + statusFilter="ACTIVE" + /> + ) + }, + size: 150, + }, + { + accessorKey: "contractSignerName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">계약서명주체 명 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("contractSignerName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerName", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "shipTypeSmax", - header: "S-max", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeSmax") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeSmax", newValue) - } - } + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "contractSignerName") - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "shipTypeVlcc", - header: "VLCC", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeVlcc") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeVlcc", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="계약서명주체 명 입력" + maxLength={100} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "headquarterLocation", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">본사 위치 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("headquarterLocation") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "headquarterLocation", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - enableSorting: false, - size: 80, - }, - { - accessorKey: "shipTypeLngc", - header: "LNGC", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeLngc") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeLngc", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="본사 위치 입력" + maxLength={50} + /> + ) + }, + size: 100, + }, + { + accessorKey: "manufacturingLocation", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">제작/선적지 *</span>} /> + ), + cell: ({ row, table }) => { + const manufacturingLocation = row.original.manufacturingLocation as string - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "shipTypeCont", - header: "CONT", - cell: ({ row, table }) => { - const value = row.getValue("shipTypeCont") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "shipTypeCont", newValue) - } - } + // 현재 선택된 장소 구성 (description은 알 수 없으므로 null로 설정) + const selectedPlace = null // 선택된 장소 표시를 위해 null로 설정 - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypeCommon", - header: "공통", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeCommon") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeCommon", newValue) - } - } + const onPlaceSelect = async (place: { code: string; description: string } | null) => { + console.log('선택된 제작/선적지:', place) - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypeFpso", - header: "FPSO", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeFpso") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpso", newValue) - } - } + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "manufacturingLocation", place?.code || "") + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypeFlng", - header: "FLNG", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeFlng") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFlng", newValue) - } - } + return ( + <PlaceOfShippingSelectorDialogSingle + selectedPlace={selectedPlace} + onPlaceSelect={onPlaceSelect} + disabled={false} + triggerLabel={manufacturingLocation || "제작/선적지 선택"} + placeholder="제작/선적지를 검색하세요..." + title="제작/선적지 선택" + description="제작/선적지를 검색하고 선택해주세요." + /> + ) + }, + size: 200, + }, + { + accessorKey: "avlVendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">AVL 등재업체명 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("avlVendorName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "avlVendorName", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypeFpu", - header: "FPU", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeFpu") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpu", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="AVL 등재업체명 입력" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "similarVendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="업체명 (검색 키워드)" /> + // 이전에는 컬럼명이 '유사업체명' 였음. + ), + cell: ({ row, table }) => { + const value = row.getValue("similarVendorName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarVendorName", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypePlatform", - header: "Platform", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypePlatform") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypePlatform", newValue) - } - } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사업체명 입력" + maxLength={100} + /> + ) + }, + size: 130, + }, + { + accessorKey: "hasAvl", + header: "AVL", + cell: ({ row, table }) => { + const value = row.getValue("hasAvl") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "hasAvl", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 100, - }, - { - accessorKey: "offshoreTypeWtiv", - header: "WTIV", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeWtiv") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeWtiv", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "isBlacklist", + header: "Blacklist", + cell: ({ row, table }) => { + const value = row.getValue("isBlacklist") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isBlacklist", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "offshoreTypeGom", - header: "GOM", - cell: ({ row, table }) => { - const value = row.getValue("offshoreTypeGom") as boolean - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeGom", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "isBcc", + header: "BCC", + cell: ({ row, table }) => { + const value = row.getValue("isBcc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isBcc", newValue) + } + } - return ( - <EditableCell - value={value} - type="checkbox" - onSave={onSave} - autoSave={false} - /> - ) - }, - size: 80, - }, - { - accessorKey: "picName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="PIC(담당자)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("picName") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "picName", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "purchaseOpinion", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="구매의견" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("purchaseOpinion") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "purchaseOpinion", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="PIC 담당자명 입력" - maxLength={50} - /> - ) - }, - size: 120, - }, - { - accessorKey: "picEmail", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="PIC(E-mail)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("picEmail") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "picEmail", newValue) - } - } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="PIC 이메일 입력" - maxLength={100} - /> - ) - }, - size: 140, - }, - { - accessorKey: "picPhone", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="PIC(Phone)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("picPhone") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "picPhone", newValue) - } - } + return ( + <EditableCell + value={value} + type="textarea" + onSave={onSave} + placeholder="구매의견 입력" + maxLength={500} + /> + ) + }, + size: 300, + }, + { + accessorKey: "shipTypeCommon", + header: "공통", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeCommon") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeCommon", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="PIC 전화번호 입력" - maxLength={20} - /> - ) - }, - size: 120, - }, - { - accessorKey: "agentName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Agent(담당자)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("agentName") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "agentName", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "shipTypeAmax", + header: "A-max", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeAmax") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeAmax", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="Agent 담당자명 입력" - maxLength={50} - /> - ) - }, - size: 120, - }, - { - accessorKey: "agentEmail", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Agent(E-mail)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("agentEmail") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "agentEmail", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "shipTypeSmax", + header: "S-max", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeSmax") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeSmax", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="Agent 이메일 입력" - maxLength={100} - /> - ) - }, - size: 140, - }, - { - accessorKey: "agentPhone", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Agent(Phone)" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("agentPhone") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "agentPhone", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "shipTypeVlcc", + header: "VLCC", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeVlcc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeVlcc", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="Agent 전화번호 입력" - maxLength={20} - /> - ) - }, - size: 120, - }, - { - accessorKey: "recentQuoteDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최근견적일" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("recentQuoteDate") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "recentQuoteDate", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "shipTypeLngc", + header: "LNGC", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeLngc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeLngc", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="최근견적일 입력 (YYYY-MM-DD)" - maxLength={20} - autoSave={false} - /> - ) - }, - size: 120, - }, - { - accessorKey: "recentQuoteNumber", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최근견적번호" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("recentQuoteNumber") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "recentQuoteNumber", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "shipTypeCont", + header: "CONT", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeCont") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeCont", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="최근견적번호 입력" - maxLength={50} - /> - ) - }, - size: 130, - }, - { - accessorKey: "recentOrderDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최근발주일" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("recentOrderDate") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "recentOrderDate", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeCommon", + header: "공통", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeCommon") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeCommon", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="최근발주일 입력 (YYYY-MM-DD)" - maxLength={20} - autoSave={false} - /> - ) - }, - size: 120, - }, - { - accessorKey: "recentOrderNumber", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최근발주번호" /> - ), - cell: ({ row, table }) => { - const value = row.getValue("recentOrderNumber") - const onSave = async (newValue: any) => { - if (table.options.meta?.onCellUpdate) { - await table.options.meta.onCellUpdate(row.original.id, "recentOrderNumber", newValue) - } - } + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeFpso", + header: "FPSO", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFpso") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpso", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeFlng", + header: "FLNG", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFlng") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFlng", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeFpu", + header: "FPU", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFpu") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpu", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypePlatform", + header: "Platform", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypePlatform") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypePlatform", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeWtiv", + header: "WTIV", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeWtiv") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeWtiv", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "offshoreTypeGom", + header: "GOM", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeGom") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeGom", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 60, + }, + { + accessorKey: "picName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체담당자" /> + // 이전에는 컬럼명이 PIC(담당자) 였음. + ), + cell: ({ row, table }) => { + const value = row.getValue("picName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "picEmail", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체담당자(E-mail)" /> + // 이전에는 컬럼명이 PIC(E-mail) 였음. + ), + cell: ({ row, table }) => { + const value = row.getValue("picEmail") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picEmail", newValue) + } + } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "picPhone", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체담당자(Phone)" /> + // 이전에는 컬럼명이 PIC(Phone) 였음. + ), + cell: ({ row, table }) => { + const value = row.getValue("picPhone") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picPhone", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={20} + /> + ) + }, + size: 120, + }, + { + accessorKey: "agentName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(담당자)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "agentEmail", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(E-mail)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentEmail") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentEmail", newValue) + } + } - return ( - <EditableCell - value={value} - type="text" - onSave={onSave} - placeholder="최근발주번호 입력" - maxLength={50} - /> - ) - }, - size: 130, - }, - { - accessorKey: "registrationDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="등재일" /> - ), - size: 120, - }, - { - accessorKey: "registrant", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="등재자" /> - ), - cell: ({ row }) => { - const value = row.getValue("registrant") as string - return <div className="text-sm">{value || ""}</div> - }, - size: 100, - }, - { - accessorKey: "lastModifiedDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종변경일" /> - ), - size: 120, - }, - { - accessorKey: "lastModifier", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종변경자" /> - ), - cell: ({ row }) => { - const value = row.getValue("lastModifier") as string - return <div className="text-sm">{value || ""}</div> - }, - size: 120, - }, + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "agentPhone", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(Phone)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentPhone") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentPhone", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="입력가능" + maxLength={20} + /> + ) + }, + size: 120, + }, + { + accessorKey: "recentQuoteDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근견적일" /> + ), + cell: ({ row }) => { + const value = row.getValue("recentQuoteDate") as string + return ( + <div className="px-2 py-1 text-sm"> + {value || "-"} + </div> + ) + }, + size: 120, + }, + { + accessorKey: "recentQuoteNumber", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근견적번호" /> + ), + cell: ({ row }) => { + const value = row.getValue("recentQuoteNumber") as string + return ( + <div className="px-2 py-1 text-sm"> + {value || "-"} + </div> + ) + }, + size: 130, + }, + { + accessorKey: "recentOrderDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근발주일" /> + ), + cell: ({ row }) => { + const value = row.getValue("recentOrderDate") as string + return ( + <div className="px-2 py-1 text-sm"> + {value || "-"} + </div> + ) + }, + size: 120, + }, + { + accessorKey: "recentOrderNumber", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근발주번호" /> + ), + cell: ({ row }) => { + const value = row.getValue("recentOrderNumber") as string + return ( + <div className="px-2 py-1 text-sm"> + {value || "-"} + </div> + ) + }, + size: 130, + }, + { + accessorKey: "registrationDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등록일" /> + ), + size: 120, + }, + { + accessorKey: "registrant", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등록자" /> + ), + cell: ({ row }) => { + const value = row.getValue("registrant") as string + return <div className="text-sm">{value || ""}</div> + }, + size: 100, + }, + { + accessorKey: "lastModifiedDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종변경일" /> + ), + size: 120, + }, + { + accessorKey: "lastModifier", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종변경자" /> + ), + cell: ({ row }) => { + const value = row.getValue("lastModifier") as string + return <div className="text-sm">{value || ""}</div> + }, + size: 120, + }, // 액션 그룹 { id: "actions", 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 |
