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.tsx205
1 files changed, 147 insertions, 58 deletions
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx
index 7f7afe14..b2ea7588 100644
--- a/lib/rfq-last/vendor/rfq-vendor-table.tsx
+++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx
@@ -24,7 +24,8 @@ import {
Globe,
Package,
MapPin,
- Info
+ Info,
+ Loader2
} from "lucide-react";
import { format } from "date-fns";
import { ko } from "date-fns/locale";
@@ -53,6 +54,12 @@ 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";
+import {
+ getRfqSendData,
+ getSelectedVendorsWithEmails,
+ type RfqSendData,
+ type VendorEmailInfo
+} from "../service"
// 타입 정의
interface RfqDetail {
@@ -100,13 +107,12 @@ interface VendorResponse {
attachmentCount?: number;
}
-// Props 타입 정의 (중복 제거하고 하나로 통합)
+// Props 타입 정의
interface RfqVendorTableProps {
rfqId: number;
rfqCode?: string;
rfqDetails: RfqDetail[];
vendorResponses: VendorResponse[];
- // 추가 props
rfqInfo?: {
rfqTitle: string;
rfqType: string;
@@ -201,6 +207,17 @@ export function RfqVendorTable({
const [isBatchUpdateOpen, setIsBatchUpdateOpen] = React.useState(false);
const [selectedVendor, setSelectedVendor] = React.useState<any | null>(null);
const [isSendDialogOpen, setIsSendDialogOpen] = React.useState(false);
+ const [isLoadingSendData, setIsLoadingSendData] = React.useState(false);
+
+ const [sendDialogData, setSendDialogData] = React.useState<{
+ rfqInfo: RfqSendData['rfqInfo'] | null;
+ attachments: RfqSendData['attachments'];
+ selectedVendors: VendorEmailInfo[];
+ }>({
+ rfqInfo: null,
+ attachments: [],
+ selectedVendors: [],
+ });
// 데이터 병합
const mergedData = React.useMemo(
@@ -215,9 +232,63 @@ export function RfqVendorTable({
return;
}
- // 다이얼로그 열기
- setIsSendDialogOpen(true);
- }, [selectedRows]);
+ try {
+ setIsLoadingSendData(true);
+
+ // 선택된 벤더 ID들 추출
+ const selectedVendorIds = selectedRows
+ .map(row => row.vendorId)
+ .filter(id => id != null);
+
+ if (selectedVendorIds.length === 0) {
+ toast.error("유효한 벤더가 선택되지 않았습니다.");
+ return;
+ }
+
+ // 병렬로 데이터 가져오기 (에러 처리 포함)
+ const [rfqSendData, vendorEmailInfos] = await Promise.all([
+ getRfqSendData(rfqId),
+ getSelectedVendorsWithEmails(rfqId, selectedVendorIds)
+ ]);
+
+ // 데이터 검증
+ if (!rfqSendData?.rfqInfo) {
+ toast.error("RFQ 정보를 불러올 수 없습니다.");
+ return;
+ }
+
+ if (!vendorEmailInfos || vendorEmailInfos.length === 0) {
+ toast.error("선택된 벤더의 이메일 정보를 찾을 수 없습니다.");
+ return;
+ }
+
+ // 다이얼로그 데이터 설정
+ setSendDialogData({
+ rfqInfo: rfqSendData.rfqInfo,
+ attachments: rfqSendData.attachments || [],
+ selectedVendors: vendorEmailInfos.map(v => ({
+ vendorId: v.vendorId,
+ vendorName: v.vendorName,
+ vendorCode: v.vendorCode,
+ vendorCountry: v.vendorCountry,
+ vendorEmail: v.vendorEmail,
+ representativeEmail: v.representativeEmail,
+ contacts: v.contacts || [],
+ contactsByPosition: v.contactsByPosition || {},
+ primaryEmail: v.primaryEmail,
+ currency: v.currency,
+ })),
+ });
+
+ // 다이얼로그 열기
+ setIsSendDialogOpen(true);
+ } catch (error) {
+ console.error("RFQ 발송 데이터 로드 실패:", error);
+ toast.error("데이터를 불러오는데 실패했습니다. 다시 시도해주세요.");
+ } finally {
+ setIsLoadingSendData(false);
+ }
+ }, [selectedRows, rfqId]);
// RFQ 발송 핸들러
const handleSendRfq = React.useCallback(async (data: {
@@ -248,6 +319,12 @@ export function RfqVendorTable({
// 성공 후 처리
setSelectedRows([]);
+ setSendDialogData({
+ rfqInfo: null,
+ attachments: [],
+ selectedVendors: [],
+ });
+
toast.success(`${data.vendors.length}개 업체에 RFQ를 발송했습니다.`);
} catch (error) {
console.error("RFQ 발송 실패:", error);
@@ -264,30 +341,63 @@ export function RfqVendorTable({
break;
case "send":
- // RFQ 발송 로직
- toast.info(`${vendor.vendorName}에게 RFQ를 발송합니다.`);
+ // 개별 RFQ 발송
+ try {
+ setIsLoadingSendData(true);
+
+ const [rfqSendData, vendorEmailInfos] = await Promise.all([
+ getRfqSendData(rfqId),
+ getSelectedVendorsWithEmails(rfqId, [vendor.vendorId])
+ ]);
+
+ if (!rfqSendData?.rfqInfo || !vendorEmailInfos || vendorEmailInfos.length === 0) {
+ toast.error("벤더 정보를 불러올 수 없습니다.");
+ return;
+ }
+
+ setSendDialogData({
+ rfqInfo: rfqSendData.rfqInfo,
+ attachments: rfqSendData.attachments || [],
+ selectedVendors: vendorEmailInfos.map(v => ({
+ vendorId: v.vendorId,
+ vendorName: v.vendorName,
+ vendorCode: v.vendorCode,
+ vendorCountry: v.vendorCountry,
+ vendorEmail: v.vendorEmail,
+ representativeEmail: v.representativeEmail,
+ contacts: v.contacts || [],
+ contactsByPosition: v.contactsByPosition || {},
+ primaryEmail: v.primaryEmail,
+ currency: v.currency,
+ })),
+ });
+
+ setIsSendDialogOpen(true);
+ } catch (error) {
+ console.error("개별 발송 데이터 로드 실패:", error);
+ toast.error("데이터를 불러오는데 실패했습니다.");
+ } finally {
+ setIsLoadingSendData(false);
+ }
break;
case "edit":
- // 수정 로직
toast.info("수정 기능은 준비중입니다.");
break;
case "delete":
- // 삭제 로직
if (confirm(`${vendor.vendorName}을(를) 삭제하시겠습니까?`)) {
toast.success(`${vendor.vendorName}이(가) 삭제되었습니다.`);
}
break;
case "response-detail":
- // 회신 상세 보기
toast.info(`${vendor.vendorName}의 회신 상세를 확인합니다.`);
break;
}
- }, []);
+ }, [rfqId]);
- // 컬럼 정의 (확장된 버전)
+ // 컬럼 정의
const columns: ColumnDef<any>[] = React.useMemo(() => [
{
id: "select",
@@ -535,7 +645,6 @@ export function RfqVendorTable({
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="참여여부 (회신일)" />,
cell: ({ row }) => {
const submittedAt = row.original.response?.submittedAt;
- const status = row.original.response?.status;
if (!submittedAt) {
return <Badge variant="outline">미참여</Badge>;
@@ -639,7 +748,10 @@ export function RfqVendorTable({
상세보기
</DropdownMenuItem>
{!hasResponse && (
- <DropdownMenuItem onClick={() => handleAction("send", vendor)}>
+ <DropdownMenuItem
+ onClick={() => handleAction("send", vendor)}
+ disabled={isLoadingSendData}
+ >
<Send className="mr-2 h-4 w-4" />
RFQ 발송
</DropdownMenuItem>
@@ -662,7 +774,7 @@ export function RfqVendorTable({
},
size: 60,
},
- ], [handleAction, rfqCode]);
+ ], [handleAction, rfqCode, isLoadingSendData]);
const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [
{ id: "vendorName", label: "벤더명", type: "text" },
@@ -701,41 +813,6 @@ 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">
@@ -743,6 +820,7 @@ export function RfqVendorTable({
variant="outline"
size="sm"
onClick={() => setIsAddDialogOpen(true)}
+ disabled={isLoadingSendData}
>
<Plus className="h-4 w-4 mr-2" />
벤더 추가
@@ -753,6 +831,7 @@ export function RfqVendorTable({
variant="outline"
size="sm"
onClick={() => setIsBatchUpdateOpen(true)}
+ disabled={isLoadingSendData}
>
<Settings2 className="h-4 w-4 mr-2" />
정보 일괄 입력 ({selectedRows.length})
@@ -761,9 +840,19 @@ export function RfqVendorTable({
variant="outline"
size="sm"
onClick={handleBulkSend}
+ disabled={isLoadingSendData || selectedRows.length === 0}
>
- <Send className="h-4 w-4 mr-2" />
- 선택 발송 ({selectedRows.length})
+ {isLoadingSendData ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ 데이터 준비중...
+ </>
+ ) : (
+ <>
+ <Send className="h-4 w-4 mr-2" />
+ 선택 발송 ({selectedRows.length})
+ </>
+ )}
</Button>
</>
)}
@@ -777,13 +866,13 @@ export function RfqVendorTable({
toast.success("데이터를 새로고침했습니다.");
}, 1000);
}}
- disabled={isRefreshing}
+ disabled={isRefreshing || isLoadingSendData}
>
<RefreshCw className={cn("h-4 w-4 mr-2", isRefreshing && "animate-spin")} />
새로고침
</Button>
</div>
- ), [selectedRows, isRefreshing, handleBulkSend]);
+ ), [selectedRows, isRefreshing, isLoadingSendData, handleBulkSend]);
return (
<>
@@ -828,9 +917,9 @@ export function RfqVendorTable({
<SendRfqDialog
open={isSendDialogOpen}
onOpenChange={setIsSendDialogOpen}
- selectedVendors={selectedVendorsForSend}
- rfqInfo={rfqInfoForDialog}
- attachments={attachments || []}
+ selectedVendors={sendDialogData.selectedVendors}
+ rfqInfo={sendDialogData.rfqInfo}
+ attachments={sendDialogData.attachments || []}
onSend={handleSendRfq}
/>