diff options
Diffstat (limited to 'lib/rfq-last/vendor/rfq-vendor-table.tsx')
| -rw-r--r-- | lib/rfq-last/vendor/rfq-vendor-table.tsx | 341 |
1 files changed, 268 insertions, 73 deletions
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index 72539113..0ebcecbd 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -29,7 +29,8 @@ import { Router, Shield, CheckSquare, - GitCompare + GitCompare, + Link } from "lucide-react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; @@ -69,6 +70,7 @@ import { VendorResponseDetailDialog } from "./vendor-detail-dialog"; import { DeleteVendorDialog } from "./delete-vendor-dialog"; import { useRouter } from "next/navigation" import { EditContractDialog } from "./edit-contract-dialog"; +import { createFilterFn } from "@/components/client-data-table/table-filters"; // 타입 정의 interface RfqDetail { @@ -292,13 +294,14 @@ export function RfqVendorTable({ ); console.log(mergedData, "mergedData") + console.log(rfqId, "rfqId") // Short List 확정 핸들러 const handleShortListConfirm = React.useCallback(async () => { 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="ITB/RFQ/견적 No." />, + filterFn: createFilterFn("text"), + cell: ({ row }) => { return ( <span className="font-mono text-xs">{row.original.rfqCode || "-"}</span> @@ -603,6 +608,8 @@ export function RfqVendorTable({ { accessorKey: "vendorName", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="협력업체정보" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => { const vendor = row.original; return ( @@ -620,12 +627,16 @@ export function RfqVendorTable({ { accessorKey: "vendorCategory", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="업체분류" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => row.original.vendorCategory || "-", size: 100, }, { accessorKey: "vendorCountry", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="내외자 (위치)" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="AVL 정보 (등급)" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => { const grade = row.original.vendorGrade; if (!grade) return <span className="text-muted-foreground">-</span>; @@ -661,9 +674,11 @@ export function RfqVendorTable({ header: ({ column }) => ( <ClientDataTableColumnHeaderSimple column={column} title="TBE 상태" /> ), + filterFn: createFilterFn("text"), + cell: ({ row }) => { const status = row.original.tbeStatus; - + if (!status || status === "준비중") { return ( <Badge variant="outline" className="text-gray-500"> @@ -672,7 +687,7 @@ export function RfqVendorTable({ </Badge> ); } - + const statusConfig = { "진행중": { variant: "default", icon: <Clock className="h-3 w-3 mr-1" />, color: "text-blue-600" }, "검토중": { variant: "secondary", icon: <Eye className="h-3 w-3 mr-1" />, color: "text-orange-600" }, @@ -680,7 +695,7 @@ export function RfqVendorTable({ "완료": { variant: "success", icon: <CheckCircle className="h-3 w-3 mr-1" />, color: "text-green-600" }, "취소": { variant: "destructive", icon: <XCircle className="h-3 w-3 mr-1" />, color: "text-red-600" }, }[status] || { variant: "outline", icon: null, color: "text-gray-600" }; - + return ( <Badge variant={statusConfig.variant as any} className={statusConfig.color}> {statusConfig.icon} @@ -690,42 +705,44 @@ export function RfqVendorTable({ }, size: 100, }, - + { accessorKey: "tbeEvaluationResult", header: ({ column }) => ( <ClientDataTableColumnHeaderSimple column={column} title="TBE 평가" /> ), + filterFn: createFilterFn("text"), + cell: ({ row }) => { const result = row.original.tbeEvaluationResult; const status = row.original.tbeStatus; - + // TBE가 완료되지 않았으면 표시하지 않음 if (status !== "완료" || !result) { return <span className="text-xs text-muted-foreground">-</span>; } - + const resultConfig = { - "Acceptable": { - variant: "success", - icon: <CheckCircle className="h-3 w-3" />, + "Acceptable": { + variant: "success", + icon: <CheckCircle className="h-3 w-3" />, text: "적합", color: "bg-green-50 text-green-700 border-green-200" }, - "Acceptable with Comment": { - variant: "warning", - icon: <AlertCircle className="h-3 w-3" />, + "Acceptable with Comment": { + variant: "warning", + icon: <AlertCircle className="h-3 w-3" />, text: "조건부 적합", color: "bg-yellow-50 text-yellow-700 border-yellow-200" }, - "Not Acceptable": { - variant: "destructive", - icon: <XCircle className="h-3 w-3" />, + "Not Acceptable": { + variant: "destructive", + icon: <XCircle className="h-3 w-3" />, text: "부적합", color: "bg-red-50 text-red-700 border-red-200" }, }[result]; - + return ( <TooltipProvider> <Tooltip> @@ -755,6 +772,8 @@ export function RfqVendorTable({ { accessorKey: "contractRequirements", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="기본계약 요청" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="발송 회차" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="요청 통화" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => { const currency = row.original.currency; return currency ? ( @@ -949,6 +974,8 @@ export function RfqVendorTable({ { accessorKey: "paymentTermsCode", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="지급조건" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="Tax" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => row.original.taxCode || "-", size: 60, }, { accessorKey: "deliveryDate", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="계약납기일/기간" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="Incoterms" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="선적지" />, + filterFn: createFilterFn("text"), + cell: ({ row }) => { const place = row.original.placeOfShipping; return place ? ( @@ -1046,6 +1081,7 @@ export function RfqVendorTable({ { accessorKey: "placeOfDestination", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="도착지" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="참여여부 (회신일)" />, + 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 }) => <ClientDataTableColumnHeaderSimple column={column} title="Short List" />, cell: ({ row }) => ( row.original.shortList ? ( @@ -1143,6 +1184,7 @@ export function RfqVendorTable({ }] : []), { accessorKey: "updatedByUserName", + filterFn: createFilterFn("text"), // 추가 header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="최신수정자" />, cell: ({ row }) => { const name = row.original.updatedByUserName; @@ -1238,24 +1280,160 @@ export function RfqVendorTable({ } ], [handleAction, rfqCode, isLoadingSendData]); + // advancedFilterFields 정의 - columns와 매칭되도록 정리 const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [ - { 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 ( <div className="flex items-center gap-2"> + {(rfqCode?.startsWith("I") || rfqCode?.startsWith("R")) && + + <Button + variant="outline" + size="sm" + > + <Link className="h-4 w-4 mr-2" /> + AVL 연동 + </Button> + } + + <Button variant="outline" size="sm" @@ -1298,32 +1493,32 @@ export function RfqVendorTable({ <Plus className="h-4 w-4 mr-2" /> 벤더 추가 </Button> - + {selectedRows.length > 0 && ( <> {/* Short List 확정 버튼 */} - {rfqCode?.startsWith("I")&& - <Button - variant="outline" - size="sm" - onClick={handleShortListConfirm} - disabled={isUpdatingShortList } + {rfqCode?.startsWith("I") && + <Button + variant="outline" + size="sm" + onClick={handleShortListConfirm} + disabled={isUpdatingShortList} // className={ "border-green-500 text-green-600 hover:bg-green-50" } - > - {isUpdatingShortList ? ( - <> - <Loader2 className="mr-2 h-4 w-4 animate-spin" /> - 처리중... - </> - ) : ( - <> - <CheckSquare className="h-4 w-4 mr-2" /> - Short List 확정 - {participatingCount > 0 && ` (${participatingCount})`} - </> - )} - </Button> - } + > + {isUpdatingShortList ? ( + <> + <Loader2 className="mr-2 h-4 w-4 animate-spin" /> + 처리중... + </> + ) : ( + <> + <CheckSquare className="h-4 w-4 mr-2" /> + Short List 확정 + {participatingCount > 0 && ` (${participatingCount})`} + </> + )} + </Button> + } {/* 견적 비교 버튼 */} <Button @@ -1334,7 +1529,7 @@ export function RfqVendorTable({ className={quotationCount >= 2 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""} > <GitCompare className="h-4 w-4 mr-2" /> - 견적 비교 + 견적 비교 {quotationCount > 0 && ` (${quotationCount})`} </Button> @@ -1370,7 +1565,7 @@ export function RfqVendorTable({ </Button> </> )} - + <Button variant="outline" size="sm" @@ -1464,19 +1659,19 @@ export function RfqVendorTable({ /> )} - {/* 기본계약 수정 다이얼로그 - 새로 추가 */} - {editContractVendor && ( - <EditContractDialog - open={!!editContractVendor} - onOpenChange={(open) => !open && setEditContractVendor(null)} - rfqId={rfqId} - vendor={editContractVendor} - onSuccess={() => { - setEditContractVendor(null); - router.refresh(); - }} - /> - )} + {/* 기본계약 수정 다이얼로그 - 새로 추가 */} + {editContractVendor && ( + <EditContractDialog + open={!!editContractVendor} + onOpenChange={(open) => !open && setEditContractVendor(null)} + rfqId={rfqId} + vendor={editContractVendor} + onSuccess={() => { + setEditContractVendor(null); + router.refresh(); + }} + /> + )} </> ); }
\ No newline at end of file |
