diff options
Diffstat (limited to 'lib/bidding/vendor/partners-bidding-participation-dialog.tsx')
| -rw-r--r-- | lib/bidding/vendor/partners-bidding-participation-dialog.tsx | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/lib/bidding/vendor/partners-bidding-participation-dialog.tsx b/lib/bidding/vendor/partners-bidding-participation-dialog.tsx new file mode 100644 index 00000000..8d6fbeea --- /dev/null +++ b/lib/bidding/vendor/partners-bidding-participation-dialog.tsx @@ -0,0 +1,249 @@ +'use client' + +import * as React from 'react' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { CheckCircle, XCircle, AlertCircle, Calendar, Package } from 'lucide-react' +import { PartnersBiddingListItem } from '../detail/service' +import { respondToPreQuoteInvitation, getBiddingCompaniesForPartners } from '../pre-quote/service' +import { useToast } from '@/hooks/use-toast' +import { useTransition } from 'react' +import { formatDate } from '@/lib/utils' + +interface PartnersBiddingParticipationDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + bidding: PartnersBiddingListItem | null + companyId: number + onSuccess: () => void +} + +export function PartnersBiddingParticipationDialog({ + open, + onOpenChange, + bidding, + companyId, + onSuccess +}: PartnersBiddingParticipationDialogProps) { + const { toast } = useToast() + const [isPending, startTransition] = useTransition() + const [selectedResponse, setSelectedResponse] = React.useState<'accepted' | 'declined' | null>(null) + + const handleSubmit = () => { + if (!bidding || !selectedResponse) { + toast({ + title: '오류', + description: '참여 의사를 선택해주세요.', + variant: 'destructive', + }) + return + } + + startTransition(async () => { + try { + // 먼저 해당 업체의 biddingCompanyId를 조회 + const biddingCompanyData = await getBiddingCompaniesForPartners(bidding.biddingId, companyId) + + if (!biddingCompanyData || !biddingCompanyData.biddingCompanyId) { + toast({ + title: '오류', + description: '입찰 업체 정보를 찾을 수 없습니다.', + variant: 'destructive', + }) + return + } + + const result = await respondToPreQuoteInvitation( + biddingCompanyData.biddingCompanyId, + selectedResponse, + 'current-user' // TODO: 실제 사용자 ID + ) + + if (result.success) { + toast({ + title: '성공', + description: result.message, + }) + setSelectedResponse(null) + onOpenChange(false) + onSuccess() + } else { + toast({ + title: '오류', + description: result.error, + variant: 'destructive', + }) + } + } catch (error) { + toast({ + title: '오류', + description: '처리 중 오류가 발생했습니다.', + variant: 'destructive', + }) + } + }) + } + + const handleOpenChange = (open: boolean) => { + onOpenChange(open) + if (!open) { + setSelectedResponse(null) + } + } + + if (!bidding) return null + + return ( + <Dialog open={open} onOpenChange={handleOpenChange}> + <DialogContent className="sm:max-w-[600px]"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <AlertCircle className="w-5 h-5" /> + 사전견적 참여 의사 결정 + </DialogTitle> + <DialogDescription> + 아래 입찰건에 대한 사전견적 참여 여부를 결정해주세요. + </DialogDescription> + </DialogHeader> + + <div className="py-4"> + {/* 입찰 정보 카드 */} + <Card className="mb-6"> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <Package className="w-5 h-5" /> + 입찰 정보 + </CardTitle> + </CardHeader> + <CardContent> + <div className="space-y-3"> + <div> + <strong>입찰번호:</strong> {bidding.biddingNumber} + {bidding.revision > 0 && ( + <Badge variant="outline" className="ml-2"> + Rev.{bidding.revision} + </Badge> + )} + </div> + <div> + <strong>입찰명:</strong> {bidding.title} + </div> + <div> + <strong>품목명:</strong> {bidding.itemName} + </div> + <div> + <strong>프로젝트:</strong> {bidding.projectName} + </div> + {bidding.preQuoteDate && ( + <div className="flex items-center gap-2"> + <Calendar className="w-4 h-4" /> + <strong>사전견적 마감일:</strong> + <span className="text-red-600 font-semibold"> + {formatDate(bidding.preQuoteDate, 'KR')} + </span> + </div> + )} + <div> + <strong>담당자:</strong> {bidding.managerName} + </div> + </div> + </CardContent> + </Card> + + {/* 참여 의사 선택 */} + <div className="space-y-4"> + <h3 className="text-lg font-semibold">참여 의사를 선택해주세요:</h3> + + <div className="grid grid-cols-2 gap-4"> + {/* 참여 수락 */} + <Card + className={`cursor-pointer transition-all ${ + selectedResponse === 'accepted' + ? 'ring-2 ring-green-500 bg-green-50' + : 'hover:shadow-md' + }`} + onClick={() => setSelectedResponse('accepted')} + > + <CardContent className="p-6 text-center"> + <CheckCircle className="w-12 h-12 text-green-600 mx-auto mb-4" /> + <h4 className="text-lg font-semibold text-green-700 mb-2"> + 참여 수락 + </h4> + <p className="text-sm text-gray-600"> + 사전견적에 참여하겠습니다. + </p> + </CardContent> + </Card> + + {/* 참여 거절 */} + <Card + className={`cursor-pointer transition-all ${ + selectedResponse === 'declined' + ? 'ring-2 ring-red-500 bg-red-50' + : 'hover:shadow-md' + }`} + onClick={() => setSelectedResponse('declined')} + > + <CardContent className="p-6 text-center"> + <XCircle className="w-12 h-12 text-red-600 mx-auto mb-4" /> + <h4 className="text-lg font-semibold text-red-700 mb-2"> + 참여 거절 + </h4> + <p className="text-sm text-gray-600"> + 사전견적에 참여하지 않겠습니다. + </p> + </CardContent> + </Card> + </div> + + {selectedResponse && ( + <div className="mt-4 p-4 rounded-lg bg-blue-50 border border-blue-200"> + <div className="flex items-center gap-2"> + <AlertCircle className="w-5 h-5 text-blue-600" /> + <span className="font-medium text-blue-800"> + {selectedResponse === 'accepted' + ? '참여 수락을 선택하셨습니다.' + : '참여 거절을 선택하셨습니다.' + } + </span> + </div> + <p className="text-sm text-blue-600 mt-1"> + {selectedResponse === 'accepted' + ? '수락 후 사전견적서를 작성하실 수 있습니다.' + : '거절 후에는 이 입찰건에 참여할 수 없습니다.' + } + </p> + </div> + )} + </div> + </div> + + <DialogFooter> + <Button variant="outline" onClick={() => handleOpenChange(false)}> + 취소 + </Button> + <Button + onClick={handleSubmit} + disabled={isPending || !selectedResponse} + className={selectedResponse === 'accepted' ? 'bg-green-600 hover:bg-green-700' : + selectedResponse === 'declined' ? 'bg-red-600 hover:bg-red-700' : ''} + > + {selectedResponse === 'accepted' && <CheckCircle className="w-4 h-4 mr-2" />} + {selectedResponse === 'declined' && <XCircle className="w-4 h-4 mr-2" />} + {selectedResponse === 'accepted' ? '참여 수락' : + selectedResponse === 'declined' ? '참여 거절' : '선택하세요'} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +} |
