summaryrefslogtreecommitdiff
path: root/lib/vendor-pool
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-24 17:36:08 +0900
committerjoonhoekim <26rote@gmail.com>2025-09-24 17:36:08 +0900
commitbf2db28586569499e44b58999f2e0f33ed4cdeb5 (patch)
tree9ef9305829fdec30ec7a442f2ba0547a62dba7a9 /lib/vendor-pool
parent1bda7f20f113737f4af32495e7ff24f6022dc283 (diff)
(김준회) 구매 요청사항 반영 - vendor-pool 및 avl detail (이진용 프로)
Diffstat (limited to 'lib/vendor-pool')
-rw-r--r--lib/vendor-pool/excel-utils.ts310
-rw-r--r--lib/vendor-pool/service.ts187
-rw-r--r--lib/vendor-pool/table/vendor-pool-excel-import-button.tsx258
-rw-r--r--lib/vendor-pool/table/vendor-pool-table-columns.tsx2956
-rw-r--r--lib/vendor-pool/table/vendor-pool-table.tsx224
5 files changed, 2352 insertions, 1583 deletions
diff --git a/lib/vendor-pool/excel-utils.ts b/lib/vendor-pool/excel-utils.ts
new file mode 100644
index 00000000..8dd8743c
--- /dev/null
+++ b/lib/vendor-pool/excel-utils.ts
@@ -0,0 +1,310 @@
+"use client"
+
+import ExcelJS from 'exceljs'
+import { saveAs } from 'file-saver'
+import type { VendorPoolItem } from './table/vendor-pool-table-columns'
+
+// Excel 컬럼 정의
+export interface ExcelColumnConfig {
+ accessorKey: string
+ header: string
+ width?: number
+ type?: 'text' | 'number' | 'boolean' | 'date'
+}
+
+// vendor-pool Excel 컬럼 매핑
+export const vendorPoolExcelColumns: ExcelColumnConfig[] = [
+ { accessorKey: 'constructionSector', header: '조선/해양', width: 15 },
+ { accessorKey: 'htDivision', header: 'H/T구분', width: 15 },
+ { accessorKey: 'designCategoryCode', header: '설계기능코드', width: 20 },
+ { accessorKey: 'designCategory', header: '설계기능(공종)', width: 25 },
+ { accessorKey: 'equipBulkDivision', header: 'Equip/Bulk', width: 15 },
+ { accessorKey: 'packageCode', header: '패키지코드', width: 20 },
+ { accessorKey: 'packageName', header: '패키지명', width: 25 },
+ { accessorKey: 'materialGroupCode', header: '자재그룹코드', width: 20 },
+ { accessorKey: 'materialGroupName', header: '자재그룹명', width: 30 },
+ { accessorKey: 'smCode', header: 'SM Code', width: 15 },
+ { accessorKey: 'similarMaterialNamePurchase', header: '유사자재명(구매)', width: 25 },
+ { accessorKey: 'similarMaterialNameOther', header: '유사자재명(기타)', width: 25 },
+ { accessorKey: 'vendorCode', header: '협력업체코드', width: 20 },
+ { accessorKey: 'vendorName', header: '협력업체명', width: 25 },
+ { accessorKey: 'taxId', header: '사업자번호', width: 20 },
+ { accessorKey: 'faTarget', header: 'FA대상', width: 15, type: 'boolean' },
+ { accessorKey: 'faStatus', header: 'FA현황', width: 15 },
+ { accessorKey: 'tier', header: '등급', width: 15 },
+ { accessorKey: 'isAgent', header: 'Agent여부', width: 15, type: 'boolean' },
+ { accessorKey: 'contractSignerCode', header: '계약서명주체코드', width: 20 },
+ { accessorKey: 'contractSignerName', header: '계약서명주체명', width: 25 },
+ { accessorKey: 'headquarterLocation', header: '본사위치', width: 20 },
+ { accessorKey: 'manufacturingLocation', header: '제작/선적지', width: 20 },
+ { accessorKey: 'avlVendorName', header: 'AVL등재업체명', width: 25 },
+ { accessorKey: 'similarVendorName', header: '유사업체명', width: 25 },
+ { accessorKey: 'hasAvl', header: 'AVL보유', width: 15, type: 'boolean' },
+ { accessorKey: 'isBlacklist', header: '블랙리스트', width: 15, type: 'boolean' },
+ { accessorKey: 'isBcc', header: 'BCC', width: 15, type: 'boolean' },
+ { accessorKey: 'purchaseOpinion', header: '구매의견', width: 30 },
+ // 선종
+ { accessorKey: 'shipTypeCommon', header: '선종공통', width: 15, type: 'boolean' },
+ { accessorKey: 'shipTypeAmax', header: 'A-MAX', width: 15, type: 'boolean' },
+ { accessorKey: 'shipTypeSmax', header: 'S-MAX', width: 15, type: 'boolean' },
+ { accessorKey: 'shipTypeVlcc', header: 'VLCC', width: 15, type: 'boolean' },
+ { accessorKey: 'shipTypeLngc', header: 'LNGC', width: 15, type: 'boolean' },
+ { accessorKey: 'shipTypeCont', header: '컨테이너선', width: 15, type: 'boolean' },
+ // 해양플랜트
+ { accessorKey: 'offshoreTypeCommon', header: '해양플랜트공통', width: 20, type: 'boolean' },
+ { accessorKey: 'offshoreTypeFpso', header: 'FPSO', width: 15, type: 'boolean' },
+ { accessorKey: 'offshoreTypeFlng', header: 'FLNG', width: 15, type: 'boolean' },
+ { accessorKey: 'offshoreTypeFpu', header: 'FPU', width: 15, type: 'boolean' },
+ { accessorKey: 'offshoreTypePlatform', header: '플랫폼', width: 15, type: 'boolean' },
+ { accessorKey: 'offshoreTypeWtiv', header: 'WTIV', width: 15, type: 'boolean' },
+ { accessorKey: 'offshoreTypeGom', header: 'GOM', width: 15, type: 'boolean' },
+ // 담당자 정보
+ { accessorKey: 'picName', header: '담당자명', width: 20 },
+ { accessorKey: 'picEmail', header: '담당자이메일', width: 30 },
+ { accessorKey: 'picPhone', header: '담당자연락처', width: 20 },
+ // 대행사 정보
+ { accessorKey: 'agentName', header: '대행사명', width: 20 },
+ { accessorKey: 'agentEmail', header: '대행사이메일', width: 30 },
+ { accessorKey: 'agentPhone', header: '대행사연락처', width: 20 },
+ // 최근 거래 정보
+ { accessorKey: 'recentQuoteDate', header: '최근견적일', width: 20 },
+ { accessorKey: 'recentQuoteNumber', header: '최근견적번호', width: 25 },
+ { accessorKey: 'recentOrderDate', header: '최근발주일', width: 20 },
+ { accessorKey: 'recentOrderNumber', header: '최근발주번호', width: 25 },
+]
+
+// 값 변환 헬퍼 함수
+const formatCellValue = (value: unknown, type?: string): string => {
+ if (value === null || value === undefined) return ''
+
+ switch (type) {
+ case 'boolean':
+ return value ? 'TRUE' : 'FALSE'
+ case 'date':
+ if (value instanceof Date) {
+ return value.toISOString().split('T')[0]
+ }
+ return String(value)
+ default:
+ return String(value)
+ }
+}
+
+// Excel 템플릿 생성
+export async function createVendorPoolTemplate(filename?: string) {
+ const workbook = new ExcelJS.Workbook()
+ const worksheet = workbook.addWorksheet('VendorPool Template')
+
+ // 1. 안내 텍스트 추가 (첫 번째 행)
+ const instructionText = '벤더풀 데이터 입력 템플릿 - 입력 방법은 "입력 가이드" 시트를 참조하세요'
+ worksheet.getCell(1, 1).value = instructionText
+ worksheet.getCell(1, 1).font = { bold: true, color: { argb: 'FF0066CC' } }
+ worksheet.getCell(1, 1).fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFFFCC00' }
+ }
+ // 첫 번째 행을 여러 컬럼에 걸쳐 병합
+ worksheet.mergeCells(1, 1, 1, Math.min(vendorPoolExcelColumns.length, 10))
+
+ // 2. 헤더 행 추가 (두 번째 행)
+ const headerRow = worksheet.addRow(vendorPoolExcelColumns.map(col => col.header))
+ headerRow.font = { bold: true }
+ headerRow.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFE0E0E0' }
+ }
+
+ // 3. 컬럼 너비 설정
+ vendorPoolExcelColumns.forEach((col, index) => {
+ const column = worksheet.getColumn(index + 1)
+ column.width = col.width || 15
+
+ // 헤더 셀 스타일 설정 (두 번째 행)
+ const headerCell = worksheet.getCell(2, index + 1)
+ headerCell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ }
+ headerCell.alignment = { vertical: 'middle', horizontal: 'center' }
+ })
+
+ // 3. 데이터 입력을 위한 빈 행 한 개 추가 (사용자가 바로 입력할 수 있도록)
+ worksheet.addRow(vendorPoolExcelColumns.map(() => ''))
+
+ // 5. 가이드 워크시트 추가
+ const guideWorksheet = workbook.addWorksheet('입력 가이드')
+
+ // 가이드 제목
+ guideWorksheet.getCell(1, 1).value = '벤더풀 데이터 입력 가이드'
+ guideWorksheet.getCell(1, 1).font = { bold: true, size: 16, color: { argb: 'FF0066CC' } }
+
+ // 가이드 내용
+ const guideContent = [
+ '',
+ '■ 필수 입력 필드 (빨간색 * 표시)',
+ ' - 조선/해양: "조선" 또는 "해양"',
+ ' - H/T구분: "H", "T", 또는 "공통"',
+ ' - 설계기능코드: 2자리 이하 영문코드 (예: EL, ME)',
+ ' - 설계기능(공종): 설계기능 한글명 (예: 전장, 기관)',
+ ' - Equip/Bulk: "E" 또는 "B" (1자리)',
+ ' - 협력업체명: 업체 한글명',
+ ' - 자재그룹코드/명: 해당 자재그룹 정보',
+ ' - 등급: "Tier 1", "Tier 2", "Tier 3" 등',
+ ' - 계약서명주체명: 계약 주체 업체명',
+ ' - 본사위치/제작선적지: 국가명',
+ ' - AVL등재업체명: AVL에 등재된 업체명',
+ '',
+ '■ Boolean (참/거짓) 필드 입력법',
+ ' - TRUE, true, 1, Y, y, O, o → 참',
+ ' - FALSE, false, 0, N, n → 거짓',
+ ' - 빈 값은 기본적으로 거짓(false)으로 처리',
+ '',
+ '■ 주의사항',
+ ' - 첫 번째 행의 안내 텍스트는 삭제하지 마세요',
+ ' - 헤더 행(2번째 행)은 수정하지 마세요',
+ ' - 데이터는 3번째 행부터 입력하세요',
+ '',
+ '■ 문제 해결',
+ ' - 필드 길이 초과 오류: 해당 필드의 글자 수를 확인하세요',
+ ' - 필수 필드 누락: 빨간색 * 표시 필드를 모두 입력했는지 확인하세요',
+ ' - Boolean 값 오류: TRUE/FALSE 형태로 입력했는지 확인하세요'
+ ]
+
+ guideContent.forEach((content, index) => {
+ const cell = guideWorksheet.getCell(index + 2, 1)
+ cell.value = content
+ if (content.startsWith('■')) {
+ cell.font = { bold: true, color: { argb: 'FF333333' } }
+ } else if (content.startsWith(' -')) {
+ cell.font = { color: { argb: 'FF666666' } }
+ }
+ })
+
+ // 가이드 워크시트 컬럼 너비 설정
+ guideWorksheet.getColumn(1).width = 80
+
+ // 파일 저장
+ const buffer = await workbook.xlsx.writeBuffer()
+ const blob = new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ })
+
+ const defaultFilename = `vendor-pool-template-${new Date().toISOString().split('T')[0]}.xlsx`
+ saveAs(blob, filename || defaultFilename)
+}
+
+// Excel 데이터 내보내기
+export async function exportVendorPoolToExcel(
+ data: VendorPoolItem[],
+ filename?: string,
+ includeIds: boolean = true
+) {
+ const workbook = new ExcelJS.Workbook()
+ const worksheet = workbook.addWorksheet('VendorPool Data')
+
+ // ID 제외할 컬럼들 필터링
+ const columnsToExport = includeIds
+ ? vendorPoolExcelColumns
+ : vendorPoolExcelColumns.filter(col => col.accessorKey !== 'id')
+
+ // 헤더 행 추가
+ const headerRow = worksheet.addRow(columnsToExport.map(col => col.header))
+ headerRow.font = { bold: true }
+ headerRow.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFE0E0E0' }
+ }
+
+ // 데이터 행 추가
+ data.forEach(item => {
+ const rowData = columnsToExport.map(col => {
+ const value = item[col.accessorKey as keyof VendorPoolItem]
+ return formatCellValue(value, col.type)
+ })
+ worksheet.addRow(rowData)
+ })
+
+ // 컬럼 너비 및 스타일 설정
+ columnsToExport.forEach((col, index) => {
+ const column = worksheet.getColumn(index + 1)
+ column.width = col.width || 15
+
+ // 헤더 셀 스타일 설정
+ const headerCell = worksheet.getCell(1, index + 1)
+ headerCell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ }
+ headerCell.alignment = { vertical: 'middle', horizontal: 'center' }
+ })
+
+ // 데이터 셀 테두리 추가
+ for (let row = 2; row <= worksheet.rowCount; row++) {
+ for (let col = 1; col <= columnsToExport.length; col++) {
+ const cell = worksheet.getCell(row, col)
+ cell.border = {
+ top: { style: 'thin' },
+ left: { style: 'thin' },
+ bottom: { style: 'thin' },
+ right: { style: 'thin' }
+ }
+ }
+ }
+
+ // 파일 저장
+ const buffer = await workbook.xlsx.writeBuffer()
+ const blob = new Blob([buffer], {
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ })
+
+ const defaultFilename = `vendor-pool-${new Date().toISOString().split('T')[0]}.xlsx`
+ saveAs(blob, filename || defaultFilename)
+}
+
+// Excel 컬럼 헤더로 accessorKey 찾기
+export function getAccessorKeyByHeader(header: string): string | undefined {
+ const column = vendorPoolExcelColumns.find(col => col.header === header)
+ return column?.accessorKey
+}
+
+// accessorKey로 Excel 헤더 찾기
+export function getHeaderByAccessorKey(accessorKey: string): string | undefined {
+ const column = vendorPoolExcelColumns.find(col => col.accessorKey === accessorKey)
+ return column?.header
+}
+
+// Boolean 값 파싱
+export function parseBoolean(value: string): boolean {
+ const lowerValue = value.toLowerCase().trim()
+ return lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes' ||
+ lowerValue === 'o' || lowerValue === 'y' || lowerValue === '참'
+}
+
+// Excel 셀 값을 문자열로 변환
+export function getCellValueAsString(cell: ExcelJS.Cell): string {
+ if (!cell || cell.value === undefined || cell.value === null) return '';
+
+ if (typeof cell.value === 'string') return cell.value.trim();
+ if (typeof cell.value === 'number') return cell.value.toString();
+
+ // Handle rich text
+ if (typeof cell.value === 'object' && cell.value && 'richText' in cell.value) {
+ const richTextValue = cell.value as { richText: Array<{ text: string }> };
+ return richTextValue.richText.map((rt) => rt.text).join('');
+ }
+
+ // Handle dates
+ if (cell.value instanceof Date) {
+ return cell.value.toISOString().split('T')[0];
+ }
+
+ // Fallback
+ return String(cell.value);
+}
diff --git a/lib/vendor-pool/service.ts b/lib/vendor-pool/service.ts
index 1933c199..3d83e3aa 100644
--- a/lib/vendor-pool/service.ts
+++ b/lib/vendor-pool/service.ts
@@ -27,8 +27,10 @@ const _getVendorPools = async (input: GetVendorPoolSchema) => {
whereConditions.push(
or(
ilike(vendorPool.constructionSector, searchTerm),
+ ilike(vendorPool.designCategoryCode, searchTerm),
ilike(vendorPool.designCategory, searchTerm),
ilike(vendorPool.vendorName, searchTerm),
+ ilike(vendorPool.materialGroupCode, searchTerm),
ilike(vendorPool.materialGroupName, searchTerm),
ilike(vendorPool.packageName, searchTerm),
ilike(vendorPool.avlVendorName, searchTerm),
@@ -37,7 +39,190 @@ const _getVendorPools = async (input: GetVendorPoolSchema) => {
);
}
- // 필터 조건 추가
+ // Advanced filters 처리 (DataTableFilterList에서 생성된 필터들)
+ if (input.filters && input.filters.length > 0) {
+ const filterConditions: any[] = [];
+
+ for (const filter of input.filters) {
+ let condition: any = null;
+
+ switch (filter.id) {
+ case 'constructionSector':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.constructionSector, filter.value as string);
+ } else if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.constructionSector, `%${filter.value}%`);
+ }
+ break;
+ case 'htDivision':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.htDivision, filter.value as string);
+ } else if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.htDivision, `%${filter.value}%`);
+ }
+ break;
+ case 'designCategoryCode':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.designCategoryCode, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.designCategoryCode, filter.value as string);
+ }
+ break;
+ case 'designCategory':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.designCategory, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.designCategory, filter.value as string);
+ }
+ break;
+ case 'packageCode':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.packageCode, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.packageCode, filter.value as string);
+ }
+ break;
+ case 'packageName':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.packageName, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.packageName, filter.value as string);
+ }
+ break;
+ case 'materialGroupCode':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.materialGroupCode, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.materialGroupCode, filter.value as string);
+ }
+ break;
+ case 'materialGroupName':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.materialGroupName, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.materialGroupName, filter.value as string);
+ }
+ break;
+ case 'vendorCode':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.vendorCode, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.vendorCode, filter.value as string);
+ }
+ break;
+ case 'vendorName':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.vendorName, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.vendorName, filter.value as string);
+ }
+ break;
+ case 'taxId':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.taxId, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.taxId, filter.value as string);
+ }
+ break;
+ case 'faStatus':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.faStatus, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.faStatus, filter.value as string);
+ }
+ break;
+ case 'tier':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.tier, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.tier, filter.value as string);
+ }
+ break;
+ case 'headquarterLocation':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.headquarterLocation, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.headquarterLocation, filter.value as string);
+ }
+ break;
+ case 'manufacturingLocation':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.manufacturingLocation, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.manufacturingLocation, filter.value as string);
+ }
+ break;
+ case 'avlVendorName':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.avlVendorName, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.avlVendorName, filter.value as string);
+ }
+ break;
+ case 'registrant':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.registrant, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.registrant, filter.value as string);
+ }
+ break;
+ case 'lastModifier':
+ if (filter.operator === 'iLike') {
+ condition = ilike(vendorPool.lastModifier, `%${filter.value}%`);
+ } else if (filter.operator === 'eq') {
+ condition = eq(vendorPool.lastModifier, filter.value as string);
+ }
+ break;
+ case 'hasAvl':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.hasAvl, filter.value === 'true');
+ }
+ break;
+ case 'isAgent':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.isAgent, filter.value === 'true');
+ }
+ break;
+ case 'isBlacklist':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.isBlacklist, filter.value === 'true');
+ }
+ break;
+ case 'isBcc':
+ if (filter.operator === 'eq') {
+ condition = eq(vendorPool.isBcc, filter.value === 'true');
+ }
+ break;
+ case 'registrationDate':
+ case 'lastModifiedDate':
+ // 날짜 필터 처리 (단순화된 버전)
+ if (filter.operator === 'eq' && filter.value) {
+ const dateValue = new Date(filter.value as string);
+ if (filter.id === 'registrationDate') {
+ condition = eq(vendorPool.registrationDate, dateValue);
+ } else {
+ condition = eq(vendorPool.lastModifiedDate, dateValue);
+ }
+ }
+ break;
+ }
+
+ if (condition) {
+ filterConditions.push(condition);
+ }
+ }
+
+ // 필터 조건들을 AND 또는 OR로 결합
+ if (filterConditions.length > 0) {
+ if (input.joinOperator === 'or') {
+ whereConditions.push(or(...filterConditions));
+ } else {
+ whereConditions.push(and(...filterConditions));
+ }
+ }
+ }
+
+ // 기존 필터 조건 추가 (하위 호환성 유지)
if (input.constructionSector) {
whereConditions.push(eq(vendorPool.constructionSector, input.constructionSector));
}
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