diff options
Diffstat (limited to 'lib/rfq-last/vendor/rfq-vendor-table.tsx')
| -rw-r--r-- | lib/rfq-last/vendor/rfq-vendor-table.tsx | 208 |
1 files changed, 155 insertions, 53 deletions
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index b6d42804..7f7afe14 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { +import { Plus, Send, Eye, @@ -32,7 +32,7 @@ import { type ColumnDef } from "@tanstack/react-table"; import { Checkbox } from "@/components/ui/checkbox"; import { ClientDataTableColumnHeaderSimple } from "@/components/client-data-table/data-table-column-simple-header"; import { ClientDataTable } from "@/components/client-data-table/data-table"; -import { +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -50,7 +50,9 @@ import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { AddVendorDialog } from "./add-vendor-dialog"; import { BatchUpdateConditionsDialog } from "./batch-update-conditions-dialog"; +import { SendRfqDialog } from "./send-rfq-dialog"; // import { VendorDetailDialog } from "./vendor-detail-dialog"; +// import { sendRfqToVendors } from "@/app/actions/rfq/send-rfq.action"; // 타입 정의 interface RfqDetail { @@ -59,9 +61,10 @@ interface RfqDetail { vendorName: string | null; vendorCode: string | null; vendorCountry: string | null; - vendorCategory?: string | null; // 업체분류 - vendorGrade?: string | null; // AVL 등급 - basicContract?: string | null; // 기본계약 + vendorEmail?: string | null; + vendorCategory?: string | null; + vendorGrade?: string | null; + basicContract?: string | null; shortList: boolean; currency: string | null; paymentTermsCode: string | null; @@ -97,11 +100,42 @@ interface VendorResponse { attachmentCount?: number; } +// Props 타입 정의 (중복 제거하고 하나로 통합) interface RfqVendorTableProps { rfqId: number; rfqCode?: string; rfqDetails: RfqDetail[]; vendorResponses: VendorResponse[]; + // 추가 props + rfqInfo?: { + rfqTitle: string; + rfqType: string; + projectCode?: string; + projectName?: string; + picName?: string; + picCode?: string; + picTeam?: string; + packageNo?: string; + packageName?: string; + designPicName?: string; + designTeam?: string; + materialGroup?: string; + materialGroupDesc?: string; + dueDate: Date; + quotationType?: string; + evaluationApply?: boolean; + contractType?: string; + }; + attachments?: Array<{ + id: number; + attachmentType: string; + serialNo: string; + currentRevision: string; + description?: string; + fileName?: string; + fileSize?: number; + uploadedAt?: Date; + }>; } // 상태별 아이콘 반환 @@ -158,43 +192,94 @@ export function RfqVendorTable({ rfqCode, rfqDetails, vendorResponses, + rfqInfo, + attachments, }: RfqVendorTableProps) { const [isRefreshing, setIsRefreshing] = React.useState(false); const [selectedRows, setSelectedRows] = React.useState<any[]>([]); const [isAddDialogOpen, setIsAddDialogOpen] = React.useState(false); const [isBatchUpdateOpen, setIsBatchUpdateOpen] = React.useState(false); const [selectedVendor, setSelectedVendor] = React.useState<any | null>(null); + const [isSendDialogOpen, setIsSendDialogOpen] = React.useState(false); // 데이터 병합 const mergedData = React.useMemo( () => mergeVendorData(rfqDetails, vendorResponses, rfqCode), [rfqDetails, vendorResponses, rfqCode] ); - + + // 일괄 발송 핸들러 + const handleBulkSend = React.useCallback(async () => { + if (selectedRows.length === 0) { + toast.warning("발송할 벤더를 선택해주세요."); + return; + } + + // 다이얼로그 열기 + setIsSendDialogOpen(true); + }, [selectedRows]); + + // RFQ 발송 핸들러 + const handleSendRfq = React.useCallback(async (data: { + vendors: Array<{ + vendorId: number; + vendorName: string; + vendorCode?: string | null; + vendorCountry?: string | null; + vendorEmail?: string | null; + currency?: string | null; + additionalRecipients: string[]; + }>; + attachments: number[]; + message?: string; + }) => { + try { + // 서버 액션 호출 + // const result = await sendRfqToVendors({ + // rfqId, + // rfqCode, + // vendors: data.vendors, + // attachmentIds: data.attachments, + // message: data.message, + // }); + + // 임시 성공 처리 + console.log("RFQ 발송 데이터:", data); + + // 성공 후 처리 + setSelectedRows([]); + toast.success(`${data.vendors.length}개 업체에 RFQ를 발송했습니다.`); + } catch (error) { + console.error("RFQ 발송 실패:", error); + toast.error("RFQ 발송에 실패했습니다."); + throw error; + } + }, [rfqId, rfqCode]); + // 액션 처리 const handleAction = React.useCallback(async (action: string, vendor: any) => { switch (action) { case "view": setSelectedVendor(vendor); break; - + case "send": // RFQ 발송 로직 toast.info(`${vendor.vendorName}에게 RFQ를 발송합니다.`); break; - + case "edit": // 수정 로직 toast.info("수정 기능은 준비중입니다."); break; - + case "delete": // 삭제 로직 if (confirm(`${vendor.vendorName}을(를) 삭제하시겠습니까?`)) { toast.success(`${vendor.vendorName}이(가) 삭제되었습니다.`); } break; - + case "response-detail": // 회신 상세 보기 toast.info(`${vendor.vendorName}의 회신 상세를 확인합니다.`); @@ -202,21 +287,6 @@ export function RfqVendorTable({ } }, []); - // 선택된 벤더들에게 일괄 발송 - const handleBulkSend = React.useCallback(async () => { - if (selectedRows.length === 0) { - toast.warning("발송할 벤더를 선택해주세요."); - return; - } - - const vendorNames = selectedRows.map(r => r.vendorName).join(", "); - if (confirm(`선택한 ${selectedRows.length}개 벤더에게 RFQ를 발송하시겠습니까?\n\n${vendorNames}`)) { - toast.success(`${selectedRows.length}개 벤더에게 RFQ를 발송했습니다.`); - setSelectedRows([]); - } - }, [selectedRows]); - - // 컬럼 정의 (확장된 버전) const columns: ColumnDef<any>[] = React.useMemo(() => [ { @@ -251,19 +321,6 @@ export function RfqVendorTable({ }, size: 120, }, - // { - // accessorKey: "response.responseVersion", - // header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="Rev" />, - // cell: ({ row }) => { - // const version = row.original.response?.responseVersion; - // return version ? ( - // <Badge variant="outline" className="font-mono">v{version}</Badge> - // ) : ( - // <span className="text-muted-foreground">-</span> - // ); - // }, - // size: 60, - // }, { accessorKey: "vendorName", header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="협력업체정보" />, @@ -307,14 +364,14 @@ export function RfqVendorTable({ cell: ({ row }) => { const grade = row.original.vendorGrade; if (!grade) return <span className="text-muted-foreground">-</span>; - + const gradeColor = { "A": "text-green-600", - "B": "text-blue-600", + "B": "text-blue-600", "C": "text-yellow-600", "D": "text-red-600", }[grade] || "text-gray-600"; - + return <span className={cn("font-semibold", gradeColor)}>{grade}</span>; }, size: 100, @@ -373,15 +430,15 @@ export function RfqVendorTable({ cell: ({ row }) => { const deliveryDate = row.original.deliveryDate; const contractDuration = row.original.contractDuration; - + return ( <div className="flex flex-col gap-0.5"> - {deliveryDate && ( + {deliveryDate && !rfqCode?.startsWith("F") && ( <span className="text-xs"> {format(new Date(deliveryDate), "yyyy-MM-dd")} </span> )} - {contractDuration && ( + {contractDuration && rfqCode?.startsWith("F") && ( <span className="text-xs text-muted-foreground">{contractDuration}</span> )} {!deliveryDate && !contractDuration && ( @@ -398,7 +455,7 @@ export function RfqVendorTable({ cell: ({ row }) => { const code = row.original.incotermsCode; const detail = row.original.incotermsDetail; - + return ( <TooltipProvider> <Tooltip> @@ -459,7 +516,7 @@ export function RfqVendorTable({ if (conditions === "-") { return <span className="text-muted-foreground">-</span>; } - + const items = conditions.split(", "); return ( <div className="flex flex-wrap gap-1"> @@ -479,11 +536,11 @@ export function RfqVendorTable({ cell: ({ row }) => { const submittedAt = row.original.response?.submittedAt; const status = row.original.response?.status; - + if (!submittedAt) { return <Badge variant="outline">미참여</Badge>; } - + return ( <div className="flex flex-col gap-0.5"> <Badge variant="default" className="text-xs">참여</Badge> @@ -500,11 +557,11 @@ export function RfqVendorTable({ header: "회신상세", cell: ({ row }) => { const hasResponse = !!row.original.response?.submittedAt; - + if (!hasResponse) { return <span className="text-muted-foreground text-xs">-</span>; } - + return ( <Button variant="ghost" @@ -565,7 +622,7 @@ export function RfqVendorTable({ cell: ({ row }) => { const vendor = row.original; const hasResponse = !!vendor.response; - + return ( <DropdownMenu> <DropdownMenuTrigger asChild> @@ -592,7 +649,7 @@ export function RfqVendorTable({ 조건 수정 </DropdownMenuItem> <DropdownMenuSeparator /> - <DropdownMenuItem + <DropdownMenuItem onClick={() => handleAction("delete", vendor)} className="text-red-600" > @@ -605,7 +662,7 @@ export function RfqVendorTable({ }, size: 60, }, - ], [handleAction]); + ], [handleAction, rfqCode]); const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [ { id: "vendorName", label: "벤더명", type: "text" }, @@ -644,6 +701,41 @@ export function RfqVendorTable({ })); }, [selectedRows]); + // 선택된 벤더 정보 (Send용) + const selectedVendorsForSend = React.useMemo(() => { + return selectedRows.map(row => ({ + vendorId: row.vendorId, + vendorName: row.vendorName, + vendorCode: row.vendorCode, + vendorCountry: row.vendorCountry, + vendorEmail: row.vendorEmail || `vendor${row.vendorId}@example.com`, + currency: row.currency, + })); + }, [selectedRows]); + + // RFQ 정보 준비 (다이얼로그용) + const rfqInfoForDialog = React.useMemo(() => { + // props로 받은 rfqInfo 사용, 없으면 기본값 + return rfqInfo || { + rfqCode: rfqCode || '', + rfqTitle: '테스트 RFQ', + rfqType: '정기견적', + projectCode: 'PN003', + projectName: 'PETRONAS ZLNG nearshore project', + picName: '김*종', + picCode: '86D', + picTeam: '해양구매팀(해양구매1)', + packageNo: 'MM03', + packageName: 'Deck Machinery', + designPicName: '이*진', + designTeam: '전장설계팀 (전장기기시스템)', + materialGroup: 'BE2101', + materialGroupDesc: 'Combined Windlass & Mooring Wi', + dueDate: new Date('2025-07-05'), + evaluationApply: true, + }; + }, [rfqInfo, rfqCode]); + // 추가 액션 버튼들 const additionalActions = React.useMemo(() => ( <div className="flex items-center gap-2"> @@ -732,6 +824,16 @@ export function RfqVendorTable({ }} /> + {/* RFQ 발송 다이얼로그 */} + <SendRfqDialog + open={isSendDialogOpen} + onOpenChange={setIsSendDialogOpen} + selectedVendors={selectedVendorsForSend} + rfqInfo={rfqInfoForDialog} + attachments={attachments || []} + onSend={handleSendRfq} + /> + {/* 벤더 상세 다이얼로그 */} {/* {selectedVendor && ( <VendorDetailDialog |
