"use client" import * as React from "react" import { useEffect, useState } from "react" import { DataTableRowAction, getRfqDetailColumns, RfqDetailView } from "./rfq-detail-column" import { toast } from "sonner" import { Skeleton } from "@/components/ui/skeleton" import { Card, CardContent } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { ProcurementRfqsView } from "@/db/schema" import { fetchCurrencies, fetchIncoterms, fetchPaymentTerms, fetchRfqDetails, fetchVendors, fetchUnreadMessages } from "@/lib/procurement-rfqs/services" import { ClientDataTable } from "@/components/client-data-table/data-table" import { AddVendorDialog } from "./add-vendor-dialog" import { Button } from "@/components/ui/button" import { Loader2, UserPlus, BarChart2 } from "lucide-react" // 아이콘 추가 import { DeleteRfqDetailDialog } from "./delete-vendor-dialog" import { UpdateRfqDetailSheet } from "./update-vendor-sheet" import { VendorCommunicationDrawer } from "./vendor-communication-drawer" import { VendorQuotationComparisonDialog } from "./vendor-quotation-comparison-dialog" // 새로운 컴포넌트 임포트 // 프로퍼티 정의 interface RfqDetailTablesProps { selectedRfq: ProcurementRfqsView | null maxHeight?: string | number } // 데이터 타입 정의 interface Vendor { id: number; vendorName: string; vendorCode: string | null; // Update this to allow null // 기타 필요한 벤더 속성들 } interface Currency { code: string; name: string; } interface PaymentTerm { code: string; description: string; } interface Incoterm { code: string; description: string; } export function RfqDetailTables({ selectedRfq , maxHeight}: RfqDetailTablesProps) { console.log("selectedRfq", selectedRfq) // 상태 관리 const [isLoading, setIsLoading] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false) const [details, setDetails] = useState([]) const [vendorDialogOpen, setVendorDialogOpen] = React.useState(false) const [updateSheetOpen, setUpdateSheetOpen] = React.useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false) const [selectedDetail, setSelectedDetail] = React.useState(null) const [vendors, setVendors] = React.useState([]) const [currencies, setCurrencies] = React.useState([]) const [paymentTerms, setPaymentTerms] = React.useState([]) const [incoterms, setIncoterms] = React.useState([]) const [isAdddialogLoading, setIsAdddialogLoading] = useState(false) const [rowAction, setRowAction] = React.useState | null>(null) // 벤더 커뮤니케이션 상태 관리 const [communicationDrawerOpen, setCommunicationDrawerOpen] = useState(false) const [selectedVendor, setSelectedVendor] = useState(null) // 읽지 않은 메시지 개수 const [unreadMessages, setUnreadMessages] = useState>({}) const [isUnreadLoading, setIsUnreadLoading] = useState(false) // 견적 비교 다이얼로그 상태 관리 (추가) const [comparisonDialogOpen, setComparisonDialogOpen] = useState(false) const existingVendorIds = React.useMemo(() => { return details.map(detail => Number(detail.vendorId)).filter(Boolean); }, [details]); const handleAddVendor = async () => { try { setIsAdddialogLoading(true) // 필요한 데이터 로드 (벤더, 통화, 지불조건, 인코텀즈) const [vendorsData, currenciesData, paymentTermsData, incotermsData] = await Promise.all([ fetchVendors(), fetchCurrencies(), fetchPaymentTerms(), fetchIncoterms() ]) setVendors(vendorsData.data || []) setCurrencies(currenciesData.data || []) setPaymentTerms(paymentTermsData.data || []) setIncoterms(incotermsData.data || []) setVendorDialogOpen(true) } catch (error) { console.error("데이터 로드 오류:", error) toast.error("벤더 정보를 불러오는 중 오류가 발생했습니다") } finally { setIsAdddialogLoading(false) } } // 견적 비교 다이얼로그 열기 핸들러 (추가) const handleOpenComparisonDialog = () => { // 제출된 견적이 있는 벤더가 최소 1개 이상 있는지 확인 const hasSubmittedQuotations = details.some(detail => detail.hasQuotation && detail.quotationStatus === "Submitted" ); if (!hasSubmittedQuotations) { toast.warning("제출된 견적이 없습니다."); return; } setComparisonDialogOpen(true); } // 읽지 않은 메시지 로드 const loadUnreadMessages = async () => { if (!selectedRfq || !selectedRfq.id) return; try { setIsUnreadLoading(true); // 읽지 않은 메시지 수 가져오기 const unreadData = await fetchUnreadMessages(selectedRfq.id); setUnreadMessages(unreadData); } catch (error) { console.error("읽지 않은 메시지 로드 오류:", error); // 조용히 실패 - 사용자에게 알림 표시하지 않음 } finally { setIsUnreadLoading(false); } }; // 칼럼 정의 - unreadMessages 상태 전달 const columns = React.useMemo(() => getRfqDetailColumns({ setRowAction, unreadMessages }), [unreadMessages]) // 필터 필드 정의 (필터 사용 시) const advancedFilterFields = React.useMemo( () => [ { id: "vendorName", label: "벤더명", type: "text", }, { id: "vendorCode", label: "벤더 코드", type: "text", }, { id: "currency", label: "통화", type: "text", }, ], [] ) // RFQ ID가 변경될 때 데이터 로드 useEffect(() => { async function loadRfqDetails() { if (!selectedRfq || !selectedRfq.id) { setDetails([]) return } try { setIsLoading(true) const transformRfqDetails = (data: any[]): RfqDetailView[] => { return data.map(item => ({ ...item, // Convert vendorId from string|null to number|undefined vendorId: item.vendorId ? Number(item.vendorId) : undefined, // Transform any other fields that need type conversion })); }; // Then in your useEffect: const result = await fetchRfqDetails(selectedRfq.id); setDetails(transformRfqDetails(result.data)); // 읽지 않은 메시지 개수 로드 await loadUnreadMessages(); } catch (error) { console.error("RFQ 디테일 로드 오류:", error) setDetails([]) toast.error("RFQ 세부정보를 불러오는 중 오류가 발생했습니다") } finally { setIsLoading(false) } } loadRfqDetails() }, [selectedRfq]) // 주기적으로 읽지 않은 메시지 갱신 (60초마다) useEffect(() => { if (!selectedRfq || !selectedRfq.id) return; const intervalId = setInterval(() => { loadUnreadMessages(); }, 60000); // 60초마다 갱신 return () => clearInterval(intervalId); }, [selectedRfq]); // rowAction 처리 useEffect(() => { if (!rowAction) return const handleRowAction = async () => { try { // 통신 액션인 경우 드로어 열기 if (rowAction.type === "communicate") { setSelectedVendor(rowAction.row.original); setCommunicationDrawerOpen(true); // 해당 벤더의 읽지 않은 메시지를 0으로 설정 (메시지를 읽은 것으로 간주) const vendorId = rowAction.row.original.vendorId; if (vendorId) { setUnreadMessages(prev => ({ ...prev, [vendorId]: 0 })); } // rowAction 초기화 setRowAction(null); return; } // 다른 액션들은 기존과 동일하게 처리 setIsAdddialogLoading(true); // 필요한 데이터 로드 (벤더, 통화, 지불조건, 인코텀즈) const [vendorsData, currenciesData, paymentTermsData, incotermsData] = await Promise.all([ fetchVendors(), fetchCurrencies(), fetchPaymentTerms(), fetchIncoterms() ]); setVendors(vendorsData.data || []); setCurrencies(currenciesData.data || []); setPaymentTerms(paymentTermsData.data || []); setIncoterms(incotermsData.data || []); // 이제 데이터가 로드되었으므로 필요한 작업 수행 if (rowAction.type === "update") { setSelectedDetail(rowAction.row.original); setUpdateSheetOpen(true); } else if (rowAction.type === "delete") { setSelectedDetail(rowAction.row.original); setDeleteDialogOpen(true); } } catch (error) { console.error("데이터 로드 오류:", error); toast.error("데이터를 불러오는 중 오류가 발생했습니다"); } finally { // communicate 타입이 아닌 경우에만 로딩 상태 변경 if (rowAction && rowAction.type !== "communicate") { setIsAdddialogLoading(false); } } }; handleRowAction(); }, [rowAction]) // RFQ가 선택되지 않은 경우 if (!selectedRfq) { return (
RFQ를 선택하세요
) } // 로딩 중인 경우 if (isLoading) { return (
) } const handleRefreshData = async () => { if (!selectedRfq || !selectedRfq.id) return try { setIsRefreshing(true) const transformRfqDetails = (data: any[]): RfqDetailView[] => { return data.map(item => ({ ...item, // Convert vendorId from string|null to number|undefined vendorId: item.vendorId ? Number(item.vendorId) : undefined, // Transform any other fields that need type conversion })); }; // Then in your useEffect: const result = await fetchRfqDetails(selectedRfq.id); setDetails(transformRfqDetails(result.data)); // 읽지 않은 메시지 개수 업데이트 await loadUnreadMessages(); toast.success("데이터가 새로고침되었습니다") } catch (error) { console.error("RFQ 디테일 로드 오류:", error) toast.error("데이터 새로고침 중 오류가 발생했습니다") } finally { setIsRefreshing(false) } } // 전체 읽지 않은 메시지 수 계산 const totalUnreadMessages = Object.values(unreadMessages).reduce((sum, count) => sum + count, 0); // 견적이 있는 벤더 수 계산 const vendorsWithQuotations = details.filter(detail => detail.hasQuotation && detail.quotationStatus === "Submitted").length; return (
{/* 메시지 및 새로고침 영역 */} {/* 테이블 또는 빈 상태 표시 */} {details.length > 0 ? (
{totalUnreadMessages > 0 && ( 읽지 않은 메시지: {totalUnreadMessages}건 )} {vendorsWithQuotations > 0 && ( 견적 제출: {vendorsWithQuotations}개 벤더 )}
{/* 견적 비교 버튼 추가 */}
) : (

해당 RFQ에 대한 협력업체가 정해지지 않았습니다. 아래 버튼을 이용하여 추가하시기 바랍니다.

)} {/* 벤더 추가 다이얼로그 */} { setVendorDialogOpen(open); if (!open) setIsAdddialogLoading(false); }} selectedRfq={selectedRfq} vendors={vendors} currencies={currencies} paymentTerms={paymentTerms} incoterms={incoterms} onSuccess={handleRefreshData} existingVendorIds={existingVendorIds} /> {/* 벤더 정보 수정 시트 */} {/* 벤더 정보 삭제 다이얼로그 */} {/* 벤더 커뮤니케이션 드로어 */} { setCommunicationDrawerOpen(open); // 드로어가 닫힐 때 읽지 않은 메시지 개수 갱신 if (!open) loadUnreadMessages(); }} selectedRfq={selectedRfq} selectedVendor={selectedVendor} onSuccess={handleRefreshData} /> {/* 견적 비교 다이얼로그 추가 */}
) }