"use client" import * as React from "react" import { useRouter } from "next/navigation" import type { DataTableAdvancedFilterField, DataTableFilterField, DataTableRowAction, } from "@/types/table" import { useDataTable } from "@/hooks/use-data-table" import { DataTable } from "@/components/data-table/data-table" import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" import { getColumns } from "./cbe-table-columns" import { fetchRfqAttachmentsbyCommentId, getCBEbyVendorId, getFileFromRfqAttachmentsbyid, fetchCbeFiles } from "../../rfqs/service" import { useSession } from "next-auth/react" import { CbeComment, CommentSheet } from "./comments-sheet" import { VendorWithCbeFields } from "@/config/vendorCbeColumnsConfig" import { toast } from "sonner" import { RfqDeailDialog } from "./rfq-detail-dialog" import { CommercialResponseSheet } from "./respond-cbe-sheet" interface VendorsTableProps { promises: Promise< [ Awaited>, ] > } export function CbeVendorTable({ promises }: VendorsTableProps) { const { data: session } = useSession() const userVendorId = session?.user?.companyId const userId = Number(session?.user?.id) // Suspense로 받아온 데이터 const [{ data, pageCount }] = React.use(promises) const [rowAction, setRowAction] = React.useState | null>(null) const [selectedCbeId, setSelectedCbeId] = React.useState(null) // 개별 협력업체별 로딩 상태를 관리하는 맵 const [loadingVendors, setLoadingVendors] = React.useState>({}) const router = useRouter() // 코멘트 관련 상태 const [initialComments, setInitialComments] = React.useState([]) const [commentSheetOpen, setCommentSheetOpen] = React.useState(false) const [selectedRfqIdForComments, setSelectedRfqIdForComments] = React.useState(null) // 상업 응답 관련 상태 const [commercialResponseSheetOpen, setCommercialResponseSheetOpen] = React.useState(false) const [selectedResponseId, setSelectedResponseId] = React.useState(null) const [selectedRfq, setSelectedRfq] = React.useState(null) // RFQ 상세 관련 상태 const [rfqDetailDialogOpen, setRfqDetailDialogOpen] = React.useState(false) const [selectedRfqId, setSelectedRfqId] = React.useState(null) const [selectedRfqDetail, setSelectedRfqDetail] = React.useState(null) React.useEffect(() => { if (rowAction?.type === "comments") { // rowAction가 새로 세팅된 뒤 여기서 openCommentSheet 실행 openCommentSheet(Number(rowAction.row.original.responseId)) } }, [rowAction]) async function openCommentSheet(responseId: number) { setInitialComments([]) const comments = rowAction?.row.original.comments const rfqId = rowAction?.row.original.rfqId if (comments && comments.length > 0) { const commentWithAttachments: CbeComment[] = await Promise.all( comments.map(async (c) => { // 서버 액션을 사용하여 코멘트 첨부 파일 가져오기 const attachments = await fetchRfqAttachmentsbyCommentId(c.id) return { ...c, commentedBy: userId, // DB나 API 응답에 있다고 가정 attachments, } }) ) setInitialComments(commentWithAttachments) } if(rfqId) { setSelectedRfqIdForComments(rfqId) } setSelectedCbeId(responseId) setCommentSheetOpen(true) } // 상업 응답 시트 열기 function openCommercialResponseSheet(responseId: number, rfq: VendorWithCbeFields) { setSelectedResponseId(responseId) setSelectedRfq(rfq) setCommercialResponseSheetOpen(true) } // RFQ 상세 대화상자 열기 function openRfqDetailDialog(rfqId: number, rfq: VendorWithCbeFields) { setSelectedRfqId(rfqId) setSelectedRfqDetail(rfq) setRfqDetailDialogOpen(true) } const handleDownloadCbeFiles = React.useCallback( async (vendorId: number, rfqId: number) => { // 고유 키 생성: vendorId_rfqId const rowKey = `${vendorId}_${rfqId}` // 해당 협력업체의 로딩 상태만 true로 설정 setLoadingVendors(prev => ({ ...prev, [rowKey]: true })) try { const { files, error } = await fetchCbeFiles(vendorId, rfqId); if (error) { toast.error(error); return; } if (files.length === 0) { toast.warning("다운로드할 CBE 파일이 없습니다"); return; } // 순차적으로 파일 다운로드 for (const file of files) { await downloadFile(file.id); } toast.success(`${files.length}개의 CBE 파일이 다운로드되었습니다`); } catch (error) { toast.error("CBE 파일을 다운로드하는 데 실패했습니다"); console.error(error); } finally { // 해당 협력업체의 로딩 상태만 false로 되돌림 setLoadingVendors(prev => ({ ...prev, [rowKey]: false })) } }, [] ); const downloadFile = React.useCallback(async (fileId: number) => { try { const { file, error } = await getFileFromRfqAttachmentsbyid(fileId); if (error || !file) { throw new Error(error || "파일 정보를 가져오는 데 실패했습니다"); } const link = document.createElement("a"); link.href = `/api/rfq-download?path=${encodeURIComponent(file.filePath)}`; link.download = file.fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); return true; } catch (error) { console.error(error); return false; } }, []); // 응답 성공 후 데이터 갱신 const handleResponseSuccess = React.useCallback(() => { // 필요한 경우 데이터 다시 가져오기 router.refresh() }, [router]); // getColumns() 호출 시 필요한 핸들러들 주입 const columns = React.useMemo( () => getColumns({ setRowAction, router, openCommentSheet, handleDownloadCbeFiles, loadingVendors, openVendorContactsDialog: openRfqDetailDialog, openCommercialResponseSheet, }), [ setRowAction, router, openCommentSheet, handleDownloadCbeFiles, loadingVendors, openRfqDetailDialog, openCommercialResponseSheet ] ); // 필터 필드 정의 const filterFields: DataTableFilterField[] = [] const advancedFilterFields: DataTableAdvancedFilterField[] = [ ] const { table } = useDataTable({ data, columns, pageCount, filterFields, enablePinning: true, enableAdvancedFilter: true, initialState: { sorting: [{ id: "respondedAt", desc: true }], columnPinning: { right: ["respond", "comments"] }, // respond 컬럼을 오른쪽에 고정 }, getRowId: (originalRow) => String(originalRow.responseId), shallow: false, clearOnDefault: true, }) return ( <> {/* 코멘트 시트 */} {commentSheetOpen && selectedRfqIdForComments && selectedCbeId !== null && ( )} {/* 상업 응답 시트 */} {commercialResponseSheetOpen && selectedResponseId !== null && selectedRfq && ( )} {/* RFQ 상세 대화상자 */} {rfqDetailDialogOpen && selectedRfqId !== null && ( )} ) }