summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor/rfq-vendor-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/vendor/rfq-vendor-table.tsx')
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx155
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