diff options
Diffstat (limited to 'lib/bidding/detail/table')
3 files changed, 115 insertions, 23 deletions
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx index 1a1b331e..6e5481f4 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx @@ -24,6 +24,7 @@ interface BiddingDetailVendorEditDialogProps { onSuccess: () => void biddingAwardCount?: string // 낙찰수 정보 추가 biddingStatus?: string // 입찰 상태 정보 추가 + allVendors?: QuotationVendor[] // 전체 벤더 목록 추가 } export function BiddingDetailVendorEditDialog({ @@ -32,7 +33,8 @@ export function BiddingDetailVendorEditDialog({ onOpenChange, onSuccess, biddingAwardCount, - biddingStatus + biddingStatus, + allVendors = [] }: BiddingDetailVendorEditDialogProps) { const { toast } = useToast() const [isPending, startTransition] = useTransition() @@ -42,6 +44,14 @@ export function BiddingDetailVendorEditDialog({ awardRatio: 0, }) + // 단수낙찰의 경우 이미 100%인 벤더가 있는지 확인 + const hasWinnerWith100Percent = React.useMemo(() => { + if (biddingAwardCount === 'single') { + return allVendors.some(v => v.awardRatio === 100 && v.id !== vendor?.id) + } + return false + }, [allVendors, biddingAwardCount, vendor?.id]) + // vendor가 변경되면 폼 데이터 업데이트 React.useEffect(() => { if (vendor) { @@ -135,7 +145,7 @@ export function BiddingDetailVendorEditDialog({ value={formData.awardRatio} onChange={(e) => setFormData({ ...formData, awardRatio: Number(e.target.value) })} placeholder="발주비율을 입력하세요" - disabled={vendor?.isBiddingParticipated !== true || biddingAwardCount === 'single' || biddingStatus === 'vendor_selected'} + disabled={vendor?.isBiddingParticipated !== true || biddingAwardCount === 'single' || biddingStatus === 'vendor_selected' || hasWinnerWith100Percent} /> {vendor?.isBiddingParticipated !== true && ( <p className="text-sm text-muted-foreground"> @@ -152,15 +162,20 @@ export function BiddingDetailVendorEditDialog({ 낙찰이 완료되어 발주비율을 수정할 수 없습니다. </p> )} + {hasWinnerWith100Percent && ( + <p className="text-sm text-orange-600"> + 단수 낙찰의 경우 이미 100% 발주비율이 설정된 업체가 있어 다른 업체의 발주비율을 수정할 수 없습니다. + </p> + )} </div> </div> <DialogFooter> <Button variant="outline" onClick={() => onOpenChange(false)}> 취소 </Button> - <Button - onClick={handleEdit} - disabled={isPending || vendor?.isBiddingParticipated !== true} + <Button + onClick={handleEdit} + disabled={isPending || vendor?.isBiddingParticipated !== true || hasWinnerWith100Percent} > 산정 </Button> diff --git a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx index cfdab9c6..1fa116ab 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx @@ -66,16 +66,16 @@ const advancedFilterFields: DataTableAdvancedFilterField<QuotationVendor>[] = [ label: '견적금액', type: 'number', }, - { - id: 'status', - label: '상태', - type: 'multi-select', - options: [ - { label: '제출완료', value: 'submitted' }, - { label: '선정완료', value: 'selected' }, - { label: '미제출', value: 'pending' }, - ], - }, + { + id: 'invitationStatus', + label: '상태', + type: 'multi-select', + options: [ + { label: '제출완료', value: 'bidding_submitted' }, + { label: '선정완료', value: 'bidding_accepted' }, + { label: '미제출', value: 'pending' }, + ], + }, ] export function BiddingDetailVendorTableContent({ @@ -201,6 +201,7 @@ export function BiddingDetailVendorTableContent({ userId={userId} onOpenAwardDialog={() => setIsAwardDialogOpen(true)} onSuccess={onRefresh} + winnerVendor={vendors.find(v => v.awardRatio === 100)} /> </DataTableAdvancedToolbar> </DataTable> @@ -210,8 +211,9 @@ export function BiddingDetailVendorTableContent({ open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen} onSuccess={onRefresh} - biddingAwardCount={bidding.awardCount} + biddingAwardCount={bidding.awardCount || undefined} biddingStatus={bidding.status} + allVendors={vendors} /> <BiddingAwardDialog diff --git a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx index f2c23de9..c1d59677 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx @@ -6,7 +6,7 @@ import { useTransition } from "react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Plus, Send, RotateCcw, XCircle, Trophy, FileText, DollarSign, RotateCw } from "lucide-react" -import { registerBidding, markAsDisposal, createRebidding } from "@/lib/bidding/detail/service" +import { registerBidding, markAsDisposal, createRebidding, cancelAwardRatio } from "@/lib/bidding/detail/service" import { sendBiddingBasicContracts, getSelectedVendorsForBidding } from "@/lib/bidding/pre-quote/service" import { increaseRoundOrRebid } from "@/lib/bidding/service" @@ -14,6 +14,7 @@ import { BiddingDetailVendorCreateDialog } from "../../../../components/bidding/ import { BiddingDocumentUploadDialog } from "./bidding-document-upload-dialog" import { Bidding } from "@/db/schema" import { useToast } from "@/hooks/use-toast" +import { QuotationVendor } from "@/lib/bidding/detail/service" interface BiddingDetailVendorToolbarActionsProps { biddingId: number @@ -21,6 +22,7 @@ interface BiddingDetailVendorToolbarActionsProps { userId: string onOpenAwardDialog: () => void onSuccess: () => void + winnerVendor?: QuotationVendor | null // 100% 낙찰된 벤더 } export function BiddingDetailVendorToolbarActions({ @@ -28,7 +30,8 @@ export function BiddingDetailVendorToolbarActions({ bidding, userId, onOpenAwardDialog, - onSuccess + onSuccess, + winnerVendor }: BiddingDetailVendorToolbarActionsProps) { const router = useRouter() const { toast } = useToast() @@ -39,6 +42,7 @@ export function BiddingDetailVendorToolbarActions({ const [isBiddingInvitationDialogOpen, setIsBiddingInvitationDialogOpen] = React.useState(false) const [selectedVendors, setSelectedVendors] = React.useState<any[]>([]) const [isRoundIncreaseDialogOpen, setIsRoundIncreaseDialogOpen] = React.useState(false) + const [isCancelAwardDialogOpen, setIsCancelAwardDialogOpen] = React.useState(false) // 본입찰 초대 다이얼로그가 열릴 때 선정된 업체들 조회 React.useEffect(() => { @@ -178,26 +182,51 @@ export function BiddingDetailVendorToolbarActions({ }) } + const handleCancelAward = () => { + if (!winnerVendor) return + + startTransition(async () => { + const result = await cancelAwardRatio(winnerVendor.id) + + if (result.success) { + toast({ + title: "성공", + description: result.message, + }) + setIsCancelAwardDialogOpen(false) + onSuccess() + } else { + toast({ + title: "오류", + description: result.error || "발주비율 취소 중 오류가 발생했습니다.", + variant: 'destructive', + }) + } + }) + } + const handleRoundIncreaseWithNavigation = () => { startTransition(async () => { const result = await increaseRoundOrRebid(bidding.id, userId, 'round_increase') if (result.success) { + const successResult = result as { success: true; message: string; biddingId: number; biddingNumber: string } toast({ title: "성공", - description: result.message, + description: successResult.message, }) // 새로 생성된 입찰의 상세 페이지로 이동 - if (result.biddingId) { - router.push(`/evcp/bid/${result.biddingId}`) + if (successResult.biddingId) { + router.push(`/evcp/bid/${successResult.biddingId}/info`) } else { - router.push(`/evcp/bid`) + router.push(`/evcp/bid/${biddingId}/info`) } onSuccess() } else { + const errorResult = result as { success: false; error: string } toast({ title: "오류", - description: result.error || "차수증가 중 오류가 발생했습니다.", + description: errorResult.error || "차수증가 중 오류가 발생했습니다.", variant: 'destructive', }) } @@ -244,6 +273,19 @@ export function BiddingDetailVendorToolbarActions({ </Button> </> )} + + {/* 발주비율 취소: 100% 낙찰된 벤더가 있는 경우 */} + {winnerVendor && ( + <Button + variant="outline" + size="sm" + onClick={() => setIsCancelAwardDialogOpen(true)} + disabled={isPending} + > + <RotateCcw className="mr-2 h-4 w-4" /> + 발주비율 취소 + </Button> + )} {/* 구분선 */} {(bidding.status === 'bidding_generated' || bidding.status === 'bidding_disposal') && ( @@ -307,6 +349,39 @@ export function BiddingDetailVendorToolbarActions({ </DialogContent> </Dialog> + {/* 발주비율 취소 확인 다이얼로그 */} + <Dialog open={isCancelAwardDialogOpen} onOpenChange={setIsCancelAwardDialogOpen}> + <DialogContent> + <DialogHeader> + <DialogTitle>발주비율 취소 확인</DialogTitle> + <DialogDescription> + {winnerVendor && ( + <> + <strong>{winnerVendor.vendorName}</strong> 업체의 발주비율(100%)을 취소하시겠습니까? + <br /> + 취소 후 다른 업체의 발주비율을 설정할 수 있습니다. + </> + )} + </DialogDescription> + </DialogHeader> + <DialogFooter> + <Button + variant="outline" + onClick={() => setIsCancelAwardDialogOpen(false)} + > + 아니오 + </Button> + <Button + variant="destructive" + onClick={handleCancelAward} + disabled={isPending} + > + 취소하기 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + </> ) } |
