From 9ecdfb23fe3df6a5df86782385002c562dfc1198 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 19 Sep 2025 07:51:27 +0000 Subject: (대표님) rfq 히스토리, swp 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/attachment/rfq-attachments-table.tsx | 128 +++++--- lib/rfq-last/quotation-compare-view.tsx | 4 +- lib/rfq-last/service.ts | 1 - lib/rfq-last/table/create-general-rfq-dialog.tsx | 4 +- lib/rfq-last/table/rfq-table-toolbar-actions.tsx | 18 +- lib/rfq-last/vendor/rfq-vendor-table.tsx | 341 +++++++++++++++++----- 6 files changed, 363 insertions(+), 133 deletions(-) (limited to 'lib/rfq-last') diff --git a/lib/rfq-last/attachment/rfq-attachments-table.tsx b/lib/rfq-last/attachment/rfq-attachments-table.tsx index 155fd412..09c9fe35 100644 --- a/lib/rfq-last/attachment/rfq-attachments-table.tsx +++ b/lib/rfq-last/attachment/rfq-attachments-table.tsx @@ -50,6 +50,7 @@ import { AddAttachmentDialog } from "./add-attachment-dialog"; import { UpdateRevisionDialog } from "./update-revision-dialog"; import { toast } from "sonner"; import { RevisionHistoryDialog } from "./revision-historty-dialog"; +import { createFilterFn } from "@/components/client-data-table/table-filters"; // 타입 정의 interface RfqAttachment { @@ -238,6 +239,7 @@ export function RfqAttachmentsTable({ { accessorKey: "serialNo", header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 cell: ({ row }) => ( {row.original.serialNo || "-"} ), @@ -248,6 +250,7 @@ export function RfqAttachmentsTable({ { accessorKey: "originalFileName", header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 cell: ({ row }) => { const file = row.original; return ( @@ -266,6 +269,7 @@ export function RfqAttachmentsTable({ { accessorKey: "description", header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 cell: ({ row }) => (
{row.original.description || "-"} @@ -276,6 +280,7 @@ export function RfqAttachmentsTable({ { accessorKey: "currentRevision", header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 cell: ({ row }) => { const revision = row.original.currentRevision; return revision ? ( @@ -291,6 +296,7 @@ export function RfqAttachmentsTable({ { accessorKey: "fileSize", header: ({ column }) => , + filterFn: createFilterFn("number"), // number 타입으로 변경 cell: ({ row }) => ( {formatFileSize(row.original.fileSize)} @@ -298,15 +304,51 @@ export function RfqAttachmentsTable({ ), size: 80, }, + { + accessorKey: "fileType", + header: ({ column }) => , + filterFn: createFilterFn("select"), // 추가 + cell: ({ row }) => { + const fileType = row.original.fileType; + if (!fileType) return -; + + const type = fileType.toLowerCase(); + let displayType = "기타"; + let color = "text-gray-500"; + + if (type.includes('pdf')) { + displayType = "PDF"; + color = "text-red-500"; + } else if (type.includes('excel') || ['xls', 'xlsx'].includes(type)) { + displayType = "Excel"; + color = "text-green-500"; + } else if (type.includes('word') || ['doc', 'docx'].includes(type)) { + displayType = "Word"; + color = "text-blue-500"; + } else if (type.includes('image') || ['jpg', 'jpeg', 'png', 'gif'].includes(type)) { + displayType = "이미지"; + color = "text-purple-500"; + } + + return ( + + {displayType} + + ); + }, + size: 100, + }, { accessorKey: "createdByName", header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 cell: ({ row }) => row.original.createdByName || "-", size: 100, }, { accessorKey: "createdAt", header: ({ column }) => , + filterFn: createFilterFn("date"), // date 타입으로 변경 cell: ({ row }) => { const date = row.original.createdAt; return date ? ( @@ -334,53 +376,45 @@ export function RfqAttachmentsTable({ { accessorKey: "updatedAt", header: ({ column }) => , + filterFn: createFilterFn("date"), // date 타입으로 변경 cell: ({ row }) => { const date = row.original.updatedAt; return date ? format(new Date(date), "MM-dd HH:mm") : "-"; }, size: 100, }, + { + accessorKey: "revisionComment", + header: ({ column }) => , + filterFn: createFilterFn("text"), // 추가 + cell: ({ row }) => { + const comment = row.original.revisionComment; + return comment ? ( + + + + + {comment} + + + +

{comment}

+
+
+
+ ) : ( + - + ); + }, + size: 150, + }, { id: "actions", header: "작업", cell: ({ row }) => { return ( - - - - - handleAction({ row, type: "download" })}> - - 다운로드 - - handleAction({ row, type: "preview" })}> - - 미리보기 - - - handleAction({ row, type: "history" })}> - - 리비전 이력 - - handleAction({ row, type: "update" })}> - - 새 버전 업로드 - - - handleAction({ row, type: "delete" })} - className="text-red-600" - > - - 삭제 - - + {/* ... 기존 드롭다운 메뉴 내용 ... */} ); }, @@ -394,18 +428,18 @@ export function RfqAttachmentsTable({ { id: "originalFileName", label: "파일명", type: "text" }, { id: "description", label: "설명", type: "text" }, { id: "currentRevision", label: "리비전", type: "text" }, - { - id: "fileType", - label: "파일 타입", - type: "select", - options: [ - { label: "PDF", value: "pdf" }, - { label: "Excel", value: "xlsx" }, - { label: "Word", value: "docx" }, - { label: "이미지", value: "image" }, - { label: "기타", value: "other" }, - ] - }, + // { + // id: "fileType", + // label: "파일 타입", + // type: "select", + // options: [ + // { label: "PDF", value: "pdf" }, + // { label: "Excel", value: "xlsx" }, + // { label: "Word", value: "docx" }, + // { label: "이미지", value: "image" }, + // { label: "기타", value: "other" }, + // ] + // }, { id: "createdByName", label: "업로드자", type: "text" }, { id: "createdAt", label: "업로드일", type: "date" }, { id: "updatedAt", label: "수정일", type: "date" }, diff --git a/lib/rfq-last/quotation-compare-view.tsx b/lib/rfq-last/quotation-compare-view.tsx index 28c8b3b1..91d46295 100644 --- a/lib/rfq-last/quotation-compare-view.tsx +++ b/lib/rfq-last/quotation-compare-view.tsx @@ -507,7 +507,7 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) { 일반계약 - + */}
)} diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index 85db1ea7..43943c71 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -3096,7 +3096,6 @@ async function processVendors({ // PDF 저장 디렉토리 준비 const contractsDir = path.join( - process.cwd(), `${process.env.NAS_PATH}`, "contracts", "generated" diff --git a/lib/rfq-last/table/create-general-rfq-dialog.tsx b/lib/rfq-last/table/create-general-rfq-dialog.tsx index 7abf06a3..2c69f4b7 100644 --- a/lib/rfq-last/table/create-general-rfq-dialog.tsx +++ b/lib/rfq-last/table/create-general-rfq-dialog.tsx @@ -347,7 +347,7 @@ export function CreateGeneralRfqDialog({ onSuccess }: CreateGeneralRfqDialogProp {/* 스크롤 가능한 컨텐츠 영역 */}
- + {/* 기본 정보 섹션 */}
@@ -766,8 +766,10 @@ export function CreateGeneralRfqDialog({ onSuccess }: CreateGeneralRfqDialogProp {rfqCategory === "general" && ( - + )} -
+ {/* 담당자 지정 다이얼로그 */} { try { setIsUpdatingShortList(true); - + const vendorIds = selectedRows .map(vendor => vendor.vendorId) .filter(id => id != null); @@ -320,7 +323,7 @@ export function RfqVendorTable({ // 견적 비교 핸들러 const handleQuotationCompare = React.useCallback(() => { - const vendorsWithQuotation = selectedRows.filter(row => + const vendorsWithQuotation = selectedRows.filter(row => row.response?.submission?.submittedAt ); @@ -334,7 +337,7 @@ export function RfqVendorTable({ .map(v => v.vendorId) .filter(id => id != null) .join(','); - + router.push(`/evcp/rfq-last/${rfqId}/compare?vendors=${vendorIds}`); }, [selectedRows, rfqId, router]); @@ -349,8 +352,8 @@ export function RfqVendorTable({ setIsLoadingSendData(true); // 선택된 벤더 ID들 추출 - const selectedVendorIds = rfqCode?.startsWith("I")? selectedRows - .filter(v=>v.shortList) + const selectedVendorIds = rfqCode?.startsWith("I") ? selectedRows + .filter(v => v.shortList) .map(row => row.vendorId) .filter(id => id != null) : selectedRows @@ -468,7 +471,7 @@ export function RfqVendorTable({ } else { toast.success(`${data.vendors.length}개 업체에 RFQ를 발송했습니다.`); } - + // 페이지 새로고침 router.refresh(); } catch (error) { @@ -593,6 +596,8 @@ export function RfqVendorTable({ { accessorKey: "rfqCode", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { return ( {row.original.rfqCode || "-"} @@ -603,6 +608,8 @@ export function RfqVendorTable({ { accessorKey: "vendorName", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const vendor = row.original; return ( @@ -620,12 +627,16 @@ export function RfqVendorTable({ { accessorKey: "vendorCategory", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => row.original.vendorCategory || "-", size: 100, }, { accessorKey: "vendorCountry", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const country = row.original.vendorCountry; const isLocal = country === "KR" || country === "한국"; @@ -640,6 +651,8 @@ export function RfqVendorTable({ { accessorKey: "vendorGrade", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const grade = row.original.vendorGrade; if (!grade) return -; @@ -661,9 +674,11 @@ export function RfqVendorTable({ header: ({ column }) => ( ), + filterFn: createFilterFn("text"), + cell: ({ row }) => { const status = row.original.tbeStatus; - + if (!status || status === "준비중") { return ( @@ -672,7 +687,7 @@ export function RfqVendorTable({ ); } - + const statusConfig = { "진행중": { variant: "default", icon: , color: "text-blue-600" }, "검토중": { variant: "secondary", icon: , color: "text-orange-600" }, @@ -680,7 +695,7 @@ export function RfqVendorTable({ "완료": { variant: "success", icon: , color: "text-green-600" }, "취소": { variant: "destructive", icon: , color: "text-red-600" }, }[status] || { variant: "outline", icon: null, color: "text-gray-600" }; - + return ( {statusConfig.icon} @@ -690,42 +705,44 @@ export function RfqVendorTable({ }, size: 100, }, - + { accessorKey: "tbeEvaluationResult", header: ({ column }) => ( ), + filterFn: createFilterFn("text"), + cell: ({ row }) => { const result = row.original.tbeEvaluationResult; const status = row.original.tbeStatus; - + // TBE가 완료되지 않았으면 표시하지 않음 if (status !== "완료" || !result) { return -; } - + const resultConfig = { - "Acceptable": { - variant: "success", - icon: , + "Acceptable": { + variant: "success", + icon: , text: "적합", color: "bg-green-50 text-green-700 border-green-200" }, - "Acceptable with Comment": { - variant: "warning", - icon: , + "Acceptable with Comment": { + variant: "warning", + icon: , text: "조건부 적합", color: "bg-yellow-50 text-yellow-700 border-yellow-200" }, - "Not Acceptable": { - variant: "destructive", - icon: , + "Not Acceptable": { + variant: "destructive", + icon: , text: "부적합", color: "bg-red-50 text-red-700 border-red-200" }, }[result]; - + return ( @@ -755,6 +772,8 @@ export function RfqVendorTable({ { accessorKey: "contractRequirements", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const vendor = row.original; const isKorean = vendor.vendorCountry === "KR" || vendor.vendorCountry === "한국"; @@ -833,6 +852,8 @@ export function RfqVendorTable({ { accessorKey: "sendVersion", header: ({ column }) => , + filterFn: createFilterFn("number"), + cell: ({ row }) => { const version = row.original.sendVersion; @@ -844,6 +865,8 @@ export function RfqVendorTable({ { accessorKey: "emailStatus", header: "이메일 상태", + filterFn: createFilterFn("text"), + cell: ({ row }) => { const response = row.original; const emailSentAt = response?.emailSentAt; @@ -936,6 +959,8 @@ export function RfqVendorTable({ { accessorKey: "currency", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const currency = row.original.currency; return currency ? ( @@ -949,6 +974,8 @@ export function RfqVendorTable({ { accessorKey: "paymentTermsCode", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const code = row.original.paymentTermsCode; const desc = row.original.paymentTermsDescription; @@ -972,12 +999,16 @@ export function RfqVendorTable({ { accessorKey: "taxCode", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => row.original.taxCode || "-", size: 60, }, { accessorKey: "deliveryDate", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const deliveryDate = row.original.deliveryDate; const contractDuration = row.original.contractDuration; @@ -1003,6 +1034,8 @@ export function RfqVendorTable({ { accessorKey: "incotermsCode", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const code = row.original.incotermsCode; const detail = row.original.incotermsDetail; @@ -1030,6 +1063,8 @@ export function RfqVendorTable({ { accessorKey: "placeOfShipping", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const place = row.original.placeOfShipping; return place ? ( @@ -1046,6 +1081,7 @@ export function RfqVendorTable({ { accessorKey: "placeOfDestination", header: ({ column }) => , + filterFn: createFilterFn("text"), cell: ({ row }) => { const place = row.original.placeOfDestination; return place ? ( @@ -1062,6 +1098,8 @@ export function RfqVendorTable({ { id: "additionalConditions", header: "추가조건", + filterFn: createFilterFn("text"), + cell: ({ row }) => { const conditions = formatAdditionalConditions(row.original); if (conditions === "-") { @@ -1084,6 +1122,8 @@ export function RfqVendorTable({ { accessorKey: "response.submission.submittedAt", header: ({ column }) => , + filterFn: createFilterFn("text"), + cell: ({ row }) => { const participationRepliedAt = row.original.response?.attend?.participationRepliedAt; @@ -1131,6 +1171,7 @@ export function RfqVendorTable({ }, ...(rfqCode?.startsWith("I") ? [{ accessorKey: "shortList", + filterFn: createFilterFn("boolean"), // boolean으로 변경 header: ({ column }) => , cell: ({ row }) => ( row.original.shortList ? ( @@ -1143,6 +1184,7 @@ export function RfqVendorTable({ }] : []), { accessorKey: "updatedByUserName", + filterFn: createFilterFn("text"), // 추가 header: ({ column }) => , cell: ({ row }) => { const name = row.original.updatedByUserName; @@ -1238,24 +1280,160 @@ export function RfqVendorTable({ } ], [handleAction, rfqCode, isLoadingSendData]); + // advancedFilterFields 정의 - columns와 매칭되도록 정리 const advancedFilterFields: DataTableAdvancedFilterField[] = [ - { id: "vendorName", label: "벤더명", type: "text" }, - { id: "vendorCode", label: "벤더코드", type: "text" }, - { id: "vendorCountry", label: "국가", type: "text" }, { - id: "response.status", - label: "응답 상태", + id: "rfqCode", + label: "ITB/RFQ/견적 No.", + type: "text" + }, + { + id: "vendorName", + label: "협력업체명", + type: "text" + }, + { + id: "vendorCode", + label: "협력업체코드", + type: "text" + }, + { + id: "vendorCategory", + label: "업체분류", + type: "select", + options: [ + { label: "제조업체", value: "제조업체" }, + { label: "무역업체", value: "무역업체" }, + { label: "대리점", value: "대리점" }, + // 실제 카테고리에 맞게 추가 + ] + }, + { + id: "vendorCountry", + label: "내외자(위치)", + type: "select", + options: [ + { label: "한국(KR)", value: "KR" }, + { label: "한국", value: "한국" }, + { label: "중국(CN)", value: "CN" }, + { label: "일본(JP)", value: "JP" }, + { label: "미국(US)", value: "US" }, + { label: "독일(DE)", value: "DE" }, + // 필요한 국가 추가 + ] + }, + { + id: "vendorGrade", + label: "AVL 등급", type: "select", options: [ - { label: "초대됨", value: "초대됨" }, - { label: "작성중", value: "작성중" }, - { label: "제출완료", value: "제출완료" }, - { label: "수정요청", value: "수정요청" }, - { label: "최종확정", value: "최종확정" }, + { label: "A", value: "A" }, + { label: "B", value: "B" }, + { label: "C", value: "C" }, + { label: "D", value: "D" }, + ] + }, + { + id: "tbeStatus", + label: "TBE 상태", + type: "select", + options: [ + { label: "대기", value: "준비중" }, + { label: "진행중", value: "진행중" }, + { label: "검토중", value: "검토중" }, + { label: "보류", value: "보류" }, + { label: "완료", value: "완료" }, { label: "취소", value: "취소" }, ] }, { + id: "tbeEvaluationResult", + label: "TBE 평가결과", + type: "select", + options: [ + { label: "적합", value: "Acceptable" }, + { label: "조건부 적합", value: "Acceptable with Comment" }, + { label: "부적합", value: "Not Acceptable" }, + ] + }, + { + id: "sendVersion", + label: "발송 회차", + type: "number" + }, + { + id: "emailStatus", + label: "이메일 상태", + type: "select", + options: [ + { label: "미발송", value: "미발송" }, + { label: "발송됨", value: "sent" }, + { label: "발송 실패", value: "failed" }, + ] + }, + { + id: "currency", + label: "요청 통화", + type: "select", + options: [ + { label: "KRW", value: "KRW" }, + { label: "USD", value: "USD" }, + { label: "EUR", value: "EUR" }, + { label: "JPY", value: "JPY" }, + { label: "CNY", value: "CNY" }, + ] + }, + { + id: "paymentTermsCode", + label: "지급조건", + type: "text" + }, + { + id: "taxCode", + label: "Tax", + type: "text", + }, + { + id: "deliveryDate", + label: "계약납기일", + type: "date" + }, + { + id: "contractDuration", + label: "계약기간", + type: "text" + }, + { + id: "incotermsCode", + label: "Incoterms", + type: "text", + }, + { + id: "placeOfShipping", + label: "선적지", + type: "text" + }, + { + id: "placeOfDestination", + label: "도착지", + type: "text" + }, + { + id: "firstYn", + label: "초도품", + type: "boolean" + }, + { + id: "materialPriceRelatedYn", + label: "연동제", + type: "boolean" + }, + { + id: "sparepartYn", + label: "스페어파트", + type: "boolean" + }, + ...(rfqCode?.startsWith("I") ? [{ id: "shortList", label: "Short List", type: "select", @@ -1263,7 +1441,12 @@ export function RfqVendorTable({ { label: "선정", value: "true" }, { label: "대기", value: "false" }, ] - }, + }] : []), + { + id: "updatedByUserName", + label: "최신수정자", + type: "text" + } ]; // 선택된 벤더 정보 (BatchUpdate용) @@ -1280,15 +1463,27 @@ export function RfqVendorTable({ // 참여 의사가 있는 선택된 벤더 수 계산 const participatingCount = selectedRows.length; - const shortListCount = selectedRows.filter(v=>v.shortList).length; + const shortListCount = selectedRows.filter(v => v.shortList).length; // 견적서가 있는 선택된 벤더 수 계산 - const quotationCount = selectedRows.filter(row => + const quotationCount = selectedRows.filter(row => row.response?.submission?.submittedAt ).length; return (
+ {(rfqCode?.startsWith("I") || rfqCode?.startsWith("R")) && + + + } + + - + {selectedRows.length > 0 && ( <> {/* Short List 확정 버튼 */} - {rfqCode?.startsWith("I")&& - - } + > + {isUpdatingShortList ? ( + <> + + 처리중... + + ) : ( + <> + + Short List 확정 + {participatingCount > 0 && ` (${participatingCount})`} + + )} + + } {/* 견적 비교 버튼 */} @@ -1370,7 +1565,7 @@ export function RfqVendorTable({ )} - +