"use client" import * as React from "react" import { useRouter } from "next/navigation" import { useSession } from "next-auth/react" import type { DataTableAdvancedFilterField, DataTableFilterField, DataTableRowAction, } from "@/types/table" import { Button } from "@/components/ui/button" import { Loader2 } from "lucide-react" import { toast } from "sonner" 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 { getBiddingsReceiveColumns } from "./biddings-receive-columns" import { getBiddingsForReceive } from "@/lib/bidding/service" import { biddingStatusLabels, contractTypeLabels, } from "@/db/schema" // import { SpecificationMeetingDialog, PrDocumentsDialog } from "../list/bidding-detail-dialogs" import { openBiddingAction } from "@/lib/bidding/actions" import { BiddingParticipantsDialog } from "@/components/bidding/receive/bidding-participants-dialog" import { getAllBiddingCompanies } from "@/lib/bidding/detail/service" type BiddingReceiveItem = { id: number biddingNumber: string originalBiddingNumber: string | null title: string status: string contractType: string prNumber: string | null submissionStartDate: Date | null submissionEndDate: Date | null bidPicName: string | null supplyPicName: string | null createdBy: string | null createdAt: Date | null updatedAt: Date | null // 참여 현황 participantExpected: number participantParticipated: number participantDeclined: number participantPending: number participantFinalSubmitted: number // 개찰 정보 openedAt: Date | null openedBy: string | null } interface BiddingsReceiveTableProps { promises: Promise< [ Awaited> ] > } export function BiddingsReceiveTable({ promises }: BiddingsReceiveTableProps) { const [biddingsResult] = React.use(promises) // biddingsResult에서 data와 pageCount 추출 const { data, pageCount } = biddingsResult const [isCompact, setIsCompact] = React.useState(false) // const [specMeetingDialogOpen, setSpecMeetingDialogOpen] = React.useState(false) // const [prDocumentsDialogOpen, setPrDocumentsDialogOpen] = React.useState(false) const [selectedBidding, setSelectedBidding] = React.useState(null) const [rowAction, setRowAction] = React.useState | null>(null) const [isOpeningBidding, setIsOpeningBidding] = React.useState(false) // 협력사 다이얼로그 관련 상태 const [participantsDialogOpen, setParticipantsDialogOpen] = React.useState(false) const [selectedParticipantType, setSelectedParticipantType] = React.useState<'expected' | 'participated' | 'declined' | 'pending' | null>(null) const [selectedBiddingId, setSelectedBiddingId] = React.useState(null) const [participantCompanies, setParticipantCompanies] = React.useState([]) const [isLoadingParticipants, setIsLoadingParticipants] = React.useState(false) const router = useRouter() const { data: session } = useSession() // 협력사 클릭 핸들러 const handleParticipantClick = React.useCallback(async (biddingId: number, participantType: 'expected' | 'participated' | 'declined' | 'pending') => { setSelectedBiddingId(biddingId) setSelectedParticipantType(participantType) setIsLoadingParticipants(true) setParticipantsDialogOpen(true) try { // 협력사 데이터 로드 (모든 초대된 협력사) const companies = await getAllBiddingCompanies(biddingId) console.log('Loaded companies:', companies) // 필터링 없이 모든 데이터 그대로 표시 // invitationStatus가 그대로 다이얼로그에 표시됨 setParticipantCompanies(companies) } catch (error) { console.error('Failed to load participant companies:', error) toast.error('협력사 목록을 불러오는데 실패했습니다.') setParticipantCompanies([]) } finally { setIsLoadingParticipants(false) } }, []) const columns = React.useMemo( () => getBiddingsReceiveColumns({ setRowAction, onParticipantClick: handleParticipantClick }), [setRowAction, handleParticipantClick] ) // rowAction 변경 감지하여 해당 다이얼로그 열기 React.useEffect(() => { if (rowAction) { setSelectedBidding(rowAction.row.original) switch (rowAction.type) { case "view": // 상세 페이지로 이동 router.push(`/evcp/bid/${rowAction.row.original.id}`) break default: break } } }, [rowAction, router]) const filterFields: DataTableFilterField[] = [ { id: "biddingNumber", label: "입찰번호", placeholder: "입찰번호를 입력하세요", }, { id: "prNumber", label: "P/R번호", placeholder: "P/R번호를 입력하세요", }, { id: "title", label: "입찰명", placeholder: "입찰명을 입력하세요", }, ] const advancedFilterFields: DataTableAdvancedFilterField[] = [ { id: "title", label: "입찰명", type: "text" }, { id: "biddingNumber", label: "입찰번호", type: "text" }, { id: "bidPicName", label: "입찰담당자", type: "text" }, { id: "status", label: "진행상태", type: "multi-select", options: Object.entries(biddingStatusLabels).map(([value, label]) => ({ label, value, })), }, { id: "contractType", label: "계약구분", type: "select", options: Object.entries(contractTypeLabels).map(([value, label]) => ({ label, value, })), }, { id: "createdAt", label: "등록일", type: "date" }, { id: "submissionStartDate", label: "제출시작일", type: "date" }, { id: "submissionEndDate", label: "제출마감일", type: "date" }, ] const { table } = useDataTable({ data, columns, pageCount, filterFields, enablePinning: true, enableAdvancedFilter: true, enableRowSelection: true, enableMultiRowSelection: false, // 단일 선택만 가능 initialState: { sorting: [{ id: "createdAt", desc: true }], columnPinning: { right: ["actions"] }, }, getRowId: (originalRow) => String(originalRow.id), shallow: false, clearOnDefault: true, }) const handleCompactChange = React.useCallback((compact: boolean) => { setIsCompact(compact) }, []) // 선택된 행 가져오기 const selectedRows = table.getFilteredSelectedRowModel().rows const selectedBiddingForAction = selectedRows.length > 0 ? selectedRows[0].original : null // 개찰 가능 여부 확인 const canOpen = React.useMemo(() => { if (!selectedBiddingForAction) return false const now = new Date() const submissionEndDate = selectedBiddingForAction.submissionEndDate ? new Date(selectedBiddingForAction.submissionEndDate) : null // 1. 입찰 마감일이 지났으면 무조건 가능 if (submissionEndDate && now > submissionEndDate) return true // 2. 입찰 기간 내 조기개찰 조건 확인 // - 미제출 협력사가 0이어야 함 (참여예정 = 최종제출 + 포기) const participatedOrDeclined = selectedBiddingForAction.participantFinalSubmitted + selectedBiddingForAction.participantDeclined const isEarlyOpenPossible = participatedOrDeclined === selectedBiddingForAction.participantExpected return isEarlyOpenPossible }, [selectedBiddingForAction]) const handleOpenBidding = React.useCallback(async () => { if (!selectedBiddingForAction) return setIsOpeningBidding(true) try { const result = await openBiddingAction(selectedBiddingForAction.id) if (result.success) { toast.success("개찰이 완료되었습니다.") // 데이터 리프레시 window.location.reload() } else { toast.error(result.message || "개찰에 실패했습니다.") } } catch (error) { toast.error("개찰 중 오류가 발생했습니다.") } finally { setIsOpeningBidding(false) } }, [selectedBiddingForAction]) return ( <>
{/* 사양설명회 다이얼로그 */} {/* */} {/* PR 문서 다이얼로그 */} {/* */} {/* 참여 협력사 다이얼로그 */} ) }