diff options
Diffstat (limited to 'lib/rfq-last/vendor/rfq-vendor-table.tsx')
| -rw-r--r-- | lib/rfq-last/vendor/rfq-vendor-table.tsx | 155 |
1 files changed, 132 insertions, 23 deletions
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx index c0f80aca..29aa5f09 100644 --- a/lib/rfq-last/vendor/rfq-vendor-table.tsx +++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx @@ -57,6 +57,7 @@ import { toast } from "sonner"; import { AddVendorDialog } from "./add-vendor-dialog"; import { BatchUpdateConditionsDialog } from "./batch-update-conditions-dialog"; import { SendRfqDialog } from "./send-rfq-dialog"; +import { CancelVendorResponseDialog } from "./cancel-vendor-response-dialog"; import { getRfqSendData, @@ -72,6 +73,7 @@ import { useRouter } from "next/navigation" import { EditContractDialog } from "./edit-contract-dialog"; import { createFilterFn } from "@/components/client-data-table/table-filters"; import { AvlVendorDialog } from "./avl-vendor-dialog"; +import { PriceAdjustmentDialog } from "./price-adjustment-dialog"; // 타입 정의 interface RfqDetail { @@ -286,6 +288,11 @@ export function RfqVendorTable({ const [editContractVendor, setEditContractVendor] = React.useState<any | null>(null); const [isUpdatingShortList, setIsUpdatingShortList] = React.useState(false); const [isAvlDialogOpen, setIsAvlDialogOpen] = React.useState(false); + const [priceAdjustmentData, setPriceAdjustmentData] = React.useState<{ + data: any; + vendorName: string; + } | null>(null); + const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false); // AVL 연동 핸들러 const handleAvlIntegration = React.useCallback(() => { @@ -340,10 +347,20 @@ export function RfqVendorTable({ // 견적 비교 핸들러 const handleQuotationCompare = React.useCallback(() => { - const vendorsWithQuotation = selectedRows.filter(row => + // 취소되지 않은 벤더만 필터링 + const nonCancelledRows = selectedRows.filter(row => { + const isCancelled = row.response?.status === "취소" || row.cancelReason; + return !isCancelled; + }); + + const vendorsWithQuotation = nonCancelledRows.filter(row => row.response?.submission?.submittedAt ); + if (vendorsWithQuotation.length === 0) { + toast.warning("비교할 견적이 있는 벤더를 선택해주세요."); + return; + } // 견적 비교 페이지로 이동 또는 모달 열기 const vendorIds = vendorsWithQuotation @@ -356,20 +373,26 @@ export function RfqVendorTable({ // 일괄 발송 핸들러 const handleBulkSend = React.useCallback(async () => { - if (selectedRows.length === 0) { - toast.warning("발송할 벤더를 선택해주세요."); + // 취소되지 않은 벤더만 필터링 + const nonCancelledRows = selectedRows.filter(row => { + const isCancelled = row.response?.status === "취소" || row.cancelReason; + return !isCancelled; + }); + + if (nonCancelledRows.length === 0) { + toast.warning("발송할 벤더를 선택해주세요. (취소된 벤더는 제외됩니다)"); return; } try { setIsLoadingSendData(true); - // 선택된 벤더 ID들 추출 - const selectedVendorIds = rfqCode?.startsWith("I") ? selectedRows + // 선택된 벤더 ID들 추출 (취소되지 않은 벤더만) + const selectedVendorIds = rfqCode?.startsWith("I") ? nonCancelledRows // .filter(v => v.shortList) .map(row => row.vendorId) .filter(id => id != null) : - selectedRows + nonCancelledRows .map(row => row.vendorId) .filter(id => id != null) @@ -629,6 +652,20 @@ export function RfqVendorTable({ case "response-detail": toast.info(`${vendor.vendorName}의 회신 상세를 확인합니다.`); break; + + case "price-adjustment": + // 연동제 정보 다이얼로그 열기 + const priceAdjustmentForm = vendor.response?.priceAdjustmentForm || + vendor.response?.additionalRequirements?.materialPriceRelated?.priceAdjustmentForm; + if (!priceAdjustmentForm) { + toast.warning("연동제 정보가 없습니다."); + return; + } + setPriceAdjustmentData({ + data: priceAdjustmentForm, + vendorName: vendor.vendorName, + }); + break; } }, [rfqId]); @@ -1300,6 +1337,11 @@ export function RfqVendorTable({ const emailResentCount = vendor.response?.email?.emailResentCount || 0; const hasQuotation = !!vendor.quotationStatus; const isKorean = vendor.vendorCountry === "KR" || vendor.vendorCountry === "한국"; + // 연동제 정보는 최상위 레벨 또는 additionalRequirements에서 확인 + const hasPriceAdjustment = !!( + vendor.response?.priceAdjustmentForm || + vendor.response?.additionalRequirements?.materialPriceRelated?.priceAdjustmentForm + ); return ( <DropdownMenu> @@ -1317,6 +1359,14 @@ export function RfqVendorTable({ 상세보기 </DropdownMenuItem> + {/* 연동제 정보 메뉴 (연동제 정보가 있을 때만 표시) */} + {hasPriceAdjustment && ( + <DropdownMenuItem onClick={() => handleAction("price-adjustment", vendor)}> + <FileText className="mr-2 h-4 w-4" /> + 연동제 정보 + </DropdownMenuItem> + )} + {/* 기본계약 수정 메뉴 추가 */} <DropdownMenuItem onClick={() => handleAction("edit-contract", vendor)}> <FileText className="mr-2 h-4 w-4" /> @@ -1341,7 +1391,7 @@ export function RfqVendorTable({ </> )} - {!emailSentAt && ( + {/* {!emailSentAt && ( <DropdownMenuItem onClick={() => handleAction("send", vendor)} disabled={isLoadingSendData} @@ -1349,7 +1399,7 @@ export function RfqVendorTable({ <Send className="mr-2 h-4 w-4" /> RFQ 발송 </DropdownMenuItem> - )} + )} */} <DropdownMenuSeparator /> <DropdownMenuItem @@ -1545,23 +1595,35 @@ export function RfqVendorTable({ // 선택된 벤더 정보 (BatchUpdate용) const selectedVendorsForBatch = React.useMemo(() => { - return selectedRows.map(row => ({ - id: row.vendorId, - vendorName: row.vendorName, - vendorCode: row.vendorCode, - })); + // 취소되지 않은 벤더만 필터링 + return selectedRows + .filter(row => { + const isCancelled = row.response?.status === "취소" || row.cancelReason; + return !isCancelled; + }) + .map(row => ({ + id: row.vendorId, + vendorName: row.vendorName, + vendorCode: row.vendorCode, + })); }, [selectedRows]); // 추가 액션 버튼들 const additionalActions = React.useMemo(() => { + // 취소되지 않은 벤더만 필터링 (취소된 벤더는 제외) + const nonCancelledRows = selectedRows.filter(row => { + const isCancelled = row.response?.status === "취소" || row.cancelReason; + return !isCancelled; + }); + // 참여 의사가 있는 선택된 벤더 수 계산 const participatingCount = selectedRows.length; const shortListCount = selectedRows.filter(v => v.shortList).length; const vendorsWithResponseCount = selectedRows.filter(v => v.response && v.response.vendor && v.response.isDocumentConfirmed).length; - // 견적서가 있는 선택된 벤더 수 계산 - const quotationCount = selectedRows.filter(row => + // 견적서가 있는 선택된 벤더 수 계산 (취소되지 않은 벤더만) + const quotationCount = nonCancelledRows.filter(row => row.response?.submission?.submittedAt ).length; @@ -1591,23 +1653,23 @@ export function RfqVendorTable({ {selectedRows.length > 0 && ( <> - {/* 정보 일괄 입력 버튼 */} + {/* 정보 일괄 입력 버튼 - 취소되지 않은 벤더만 */} <Button variant="outline" size="sm" onClick={() => setIsBatchUpdateOpen(true)} - disabled={isLoadingSendData} + disabled={isLoadingSendData || nonCancelledRows.length === 0} > <Settings2 className="h-4 w-4 mr-2" /> - 협력업체 조건 설정 ({selectedRows.length}) + 협력업체 조건 설정 ({nonCancelledRows.length}) </Button> - {/* RFQ 발송 버튼 */} + {/* RFQ 발송 버튼 - 취소되지 않은 벤더만 */} <Button variant="outline" size="sm" onClick={handleBulkSend} - disabled={isLoadingSendData || selectedRows.length === 0} + disabled={isLoadingSendData || nonCancelledRows.length === 0} > {isLoadingSendData ? ( <> @@ -1617,11 +1679,24 @@ export function RfqVendorTable({ ) : ( <> <Send className="h-4 w-4 mr-2" /> - RFQ 발송 ({selectedRows.length}) + RFQ 발송 ({nonCancelledRows.length}) </> )} </Button> + {/* RFQ 취소 버튼 - RFQ 발송 후에만 표시 (emailSentAt이 있는 경우) 및 취소되지 않은 벤더만 */} + {rfqDetails.some(detail => detail.emailSentAt) && nonCancelledRows.length > 0 && ( + <Button + variant="destructive" + size="sm" + onClick={() => setIsCancelDialogOpen(true)} + disabled={nonCancelledRows.length === 0} + > + <XCircle className="h-4 w-4 mr-2" /> + RFQ 취소 ({nonCancelledRows.length}) + </Button> + )} + {/* Short List 확정 버튼 */} {!rfqCode?.startsWith("F") && <Button @@ -1646,7 +1721,7 @@ export function RfqVendorTable({ </Button> } - {/* 견적 비교 버튼 */} + {/* 견적 비교 버튼 - 취소되지 않은 벤더만 */} <Button variant="outline" size="sm" @@ -1678,7 +1753,7 @@ export function RfqVendorTable({ </Button> </div> ); - }, [selectedRows, isRefreshing, isLoadingSendData, handleBulkSend, handleShortListConfirm, handleQuotationCompare, isUpdatingShortList]); + }, [selectedRows, isRefreshing, isLoadingSendData, handleBulkSend, handleShortListConfirm, handleQuotationCompare, isUpdatingShortList, rfqInfo, rfqCode, handleAvlIntegration, rfqDetails]); return ( <> @@ -1779,6 +1854,40 @@ export function RfqVendorTable({ router.refresh(); }} /> + + {/* 연동제 정보 다이얼로그 */} + {priceAdjustmentData && ( + <PriceAdjustmentDialog + open={!!priceAdjustmentData} + onOpenChange={(open) => !open && setPriceAdjustmentData(null)} + data={priceAdjustmentData.data} + vendorName={priceAdjustmentData.vendorName} + /> + )} + + {/* RFQ 취소 다이얼로그 - 취소되지 않은 벤더만 전달 */} + <CancelVendorResponseDialog + open={isCancelDialogOpen} + onOpenChange={setIsCancelDialogOpen} + rfqId={rfqId} + selectedVendors={selectedRows + .filter(row => { + const isCancelled = row.response?.status === "취소" || row.cancelReason; + return !isCancelled; + }) + .map(row => ({ + detailId: row.detailId, + vendorId: row.vendorId, + vendorName: row.vendorName || "", + vendorCode: row.vendorCode, + }))} + onSuccess={() => { + setIsCancelDialogOpen(false); + setSelectedRows([]); + router.refresh(); + toast.success("RFQ 취소가 완료되었습니다."); + }} + /> </> ); }
\ No newline at end of file |
