From 8642ee064ddf96f1db2b948b4cc8bbbd6cfee820 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 12 Nov 2025 10:42:36 +0000 Subject: (최겸) 구매 일반계약, 입찰 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bidding/failure/biddings-failure-table.tsx | 266 ++++++++++++++++++++++--- 1 file changed, 233 insertions(+), 33 deletions(-) (limited to 'lib/bidding/failure/biddings-failure-table.tsx') diff --git a/lib/bidding/failure/biddings-failure-table.tsx b/lib/bidding/failure/biddings-failure-table.tsx index 901648d2..c80021ea 100644 --- a/lib/bidding/failure/biddings-failure-table.tsx +++ b/lib/bidding/failure/biddings-failure-table.tsx @@ -18,7 +18,12 @@ import { biddingStatusLabels, contractTypeLabels, } from "@/db/schema" -import { SpecificationMeetingDialog, PrDocumentsDialog } from "../list/bidding-detail-dialogs" +import { BiddingsClosureDialog } from "./biddings-closure-dialog" +import { Button } from "@/components/ui/button" +import { FileX, RefreshCw, Undo2 } from "lucide-react" +import { bidClosureAction, cancelDisposalAction } from "@/lib/bidding/actions" +import { increaseRoundOrRebid } from "@/lib/bidding/service" +import { useToast } from "@/hooks/use-toast" type BiddingFailureItem = { id: number @@ -30,7 +35,7 @@ type BiddingFailureItem = { prNumber: string | null // 가격 정보 - targetPrice: number | null + targetPrice: string | number | null currency: string | null // 일정 정보 @@ -47,6 +52,15 @@ type BiddingFailureItem = { disposalUpdatedAt: Date | null // 폐찰수정일 disposalUpdatedBy: string | null // 폐찰수정자 + // 폐찰 정보 + closureReason: string | null // 폐찰사유 + closureDocuments: { + id: number + fileName: string + originalFileName: string + filePath: string + }[] // 폐찰 첨부파일들 + // 기타 정보 createdBy: string | null createdAt: Date | null @@ -69,9 +83,9 @@ export function BiddingsFailureTable({ promises }: BiddingsFailureTableProps) { const { data, pageCount } = biddingsResult const [isCompact, setIsCompact] = React.useState(false) - const [specMeetingDialogOpen, setSpecMeetingDialogOpen] = React.useState(false) - const [prDocumentsDialogOpen, setPrDocumentsDialogOpen] = React.useState(false) + const [biddingClosureDialogOpen, setBiddingClosureDialogOpen] = React.useState(false) const [selectedBidding, setSelectedBidding] = React.useState(null) + const { toast } = useToast() const [rowAction, setRowAction] = React.useState | null>(null) @@ -89,17 +103,18 @@ export function BiddingsFailureTable({ promises }: BiddingsFailureTableProps) { setSelectedBidding(rowAction.row.original) switch (rowAction.type) { - case "view": - // 상세 페이지로 이동 - router.push(`/evcp/bid/${rowAction.row.original.id}`) + case "rebid": + // 재입찰 + handleRebid(rowAction.row.original) break - case "history": - // 이력보기 (추후 구현) - console.log('이력보기:', rowAction.row.original) + case "closure": + // 폐찰 + setSelectedBidding(rowAction.row.original) + setBiddingClosureDialogOpen(true) break - case "rebid": - // 재입찰 (추후 구현) - console.log('재입찰:', rowAction.row.original) + case "cancelDisposal": + // 유찰취소 + handleCancelDisposal(rowAction.row.original) break default: break @@ -163,6 +178,8 @@ export function BiddingsFailureTable({ promises }: BiddingsFailureTableProps) { filterFields, enablePinning: true, enableAdvancedFilter: true, + enableRowSelection: true, + singleRowSelection: true, initialState: { sorting: [{ id: "disposalDate", desc: true }], // 유찰일 기준 최신순 columnPinning: { right: ["actions"] }, @@ -176,17 +193,85 @@ export function BiddingsFailureTable({ promises }: BiddingsFailureTableProps) { setIsCompact(compact) }, []) - const handleSpecMeetingDialogClose = React.useCallback(() => { - setSpecMeetingDialogOpen(false) + const handleBiddingClosureDialogClose = React.useCallback(() => { + setBiddingClosureDialogOpen(false) setRowAction(null) setSelectedBidding(null) }, []) - const handlePrDocumentsDialogClose = React.useCallback(() => { - setPrDocumentsDialogOpen(false) - setRowAction(null) - setSelectedBidding(null) - }, []) + const handleRebid = React.useCallback(async (bidding: BiddingFailureItem) => { + if (!session?.user?.id) { + toast({ + title: "오류", + description: "사용자 정보를 찾을 수 없습니다.", + variant: "destructive", + }) + return + } + + try { + const result = await increaseRoundOrRebid(bidding.id, session.user.id, 'rebidding') + + if (result.success) { + toast({ + title: "성공", + description: result.message, + }) + // 페이지 새로고침 + router.refresh() + } else { + toast({ + title: "오류", + description: result.error || "재입찰 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + } catch (error) { + console.error('재입찰 실패:', error) + toast({ + title: "오류", + description: "재입찰 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + }, [session?.user?.id, toast, router]) + + const handleCancelDisposal = React.useCallback(async (bidding: BiddingFailureItem) => { + if (!session?.user?.id) { + toast({ + title: "오류", + description: "사용자 정보를 찾을 수 없습니다.", + variant: "destructive", + }) + return + } + + try { + const result = await cancelDisposalAction(bidding.id, session.user.id) + + if (result.success) { + toast({ + title: "성공", + description: result.message, + }) + // 페이지 새로고침 + router.refresh() + } else { + toast({ + title: "오류", + description: result.error || "유찰취소 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + } catch (error) { + console.error('유찰취소 실패:', error) + toast({ + title: "오류", + description: "유찰취소 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + }, [session?.user?.id, toast, router]) return ( <> @@ -202,22 +287,137 @@ export function BiddingsFailureTable({ promises }: BiddingsFailureTableProps) { compactStorageKey="biddingsFailureTableCompact" onCompactChange={handleCompactChange} > + {/* Toolbar 액션 버튼들 */} +
+ + + + + +
- {/* 사양설명회 다이얼로그 */} - - - {/* PR 문서 다이얼로그 */} - + {/* 폐찰 다이얼로그 */} + {selectedBidding && session?.user?.id && ( + { + router.refresh() + handleBiddingClosureDialogClose() + }} + /> + )} ) } -- cgit v1.2.3