summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor/partners-bidding-participation-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor/partners-bidding-participation-dialog.tsx')
-rw-r--r--lib/bidding/vendor/partners-bidding-participation-dialog.tsx249
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>
+ )
+}