summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor/vendor-prequote-participation-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor/vendor-prequote-participation-dialog.tsx')
-rw-r--r--lib/bidding/vendor/vendor-prequote-participation-dialog.tsx268
1 files changed, 268 insertions, 0 deletions
diff --git a/lib/bidding/vendor/vendor-prequote-participation-dialog.tsx b/lib/bidding/vendor/vendor-prequote-participation-dialog.tsx
new file mode 100644
index 00000000..c8098c3d
--- /dev/null
+++ b/lib/bidding/vendor/vendor-prequote-participation-dialog.tsx
@@ -0,0 +1,268 @@
+'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, Building2, User } from 'lucide-react'
+import { useToast } from '@/hooks/use-toast'
+import { useTransition } from 'react'
+import { formatDate } from '@/lib/utils'
+
+interface VendorPreQuoteParticipationDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ biddingDetail: any // BiddingDetail 타입
+ onParticipationDecision: (participate: boolean) => Promise<void>
+}
+
+export function VendorPreQuoteParticipationDialog({
+ open,
+ onOpenChange,
+ biddingDetail,
+ onParticipationDecision
+}: VendorPreQuoteParticipationDialogProps) {
+ const { toast } = useToast()
+ const [isPending, startTransition] = useTransition()
+ const [selectedDecision, setSelectedDecision] = React.useState<boolean | null>(null)
+
+ const handleSubmit = () => {
+ if (selectedDecision === null) {
+ toast({
+ title: '선택 필요',
+ description: '사전견적 참여 여부를 선택해주세요.',
+ variant: 'destructive',
+ })
+ return
+ }
+
+ startTransition(async () => {
+ try {
+ await onParticipationDecision(selectedDecision)
+
+ toast({
+ title: '완료',
+ description: selectedDecision
+ ? '사전견적 참여를 결정했습니다. 이제 견적서를 작성하실 수 있습니다.'
+ : '사전견적 참여를 거절했습니다.',
+ })
+
+ setSelectedDecision(null)
+ onOpenChange(false)
+ } catch (error) {
+ toast({
+ title: '오류',
+ description: '처리 중 오류가 발생했습니다.',
+ variant: 'destructive',
+ })
+ }
+ })
+ }
+
+ const handleOpenChange = (open: boolean) => {
+ onOpenChange(open)
+ if (!open) {
+ setSelectedDecision(null)
+ }
+ }
+
+ if (!biddingDetail) return null
+
+ return (
+ <Dialog open={open} onOpenChange={handleOpenChange}>
+ <DialogContent className="sm:max-w-[700px]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <AlertCircle className="w-5 h-5 text-blue-600" />
+ 사전견적 참여 의사 결정
+ </DialogTitle>
+ <DialogDescription>
+ 다음 입찰건에 대한 사전견적 참여 여부를 결정해주세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="py-4 space-y-6">
+ {/* 입찰 정보 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg flex items-center gap-2">
+ <Package className="w-5 h-5" />
+ 입찰 상세 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ <div>
+ <strong className="text-gray-700">입찰번호:</strong>
+ <div className="flex items-center gap-2 mt-1">
+ <span className="font-mono">{biddingDetail.biddingNumber}</span>
+ {biddingDetail.revision && biddingDetail.revision > 0 && (
+ <Badge variant="outline">Rev.{biddingDetail.revision}</Badge>
+ )}
+ </div>
+ </div>
+
+ <div>
+ <strong className="text-gray-700">프로젝트:</strong>
+ <div className="flex items-center gap-2 mt-1">
+ <Building2 className="w-4 h-4" />
+ <span>{biddingDetail.projectName}</span>
+ </div>
+ </div>
+
+ <div className="md:col-span-2">
+ <strong className="text-gray-700">입찰명:</strong>
+ <div className="mt-1">
+ <span className="text-lg">{biddingDetail.title}</span>
+ </div>
+ </div>
+
+ <div>
+ <strong className="text-gray-700">품목명:</strong>
+ <div className="mt-1">{biddingDetail.itemName}</div>
+ </div>
+
+ <div>
+ <strong className="text-gray-700">담당자:</strong>
+ <div className="flex items-center gap-2 mt-1">
+ <User className="w-4 h-4" />
+ <span>{biddingDetail.managerName}</span>
+ </div>
+ </div>
+
+ {biddingDetail.preQuoteDate && (
+ <div className="md:col-span-2">
+ <strong className="text-gray-700">사전견적 마감일:</strong>
+ <div className="flex items-center gap-2 mt-1">
+ <Calendar className="w-4 h-4 text-red-500" />
+ <span className="text-red-600 font-semibold">
+ {formatDate(biddingDetail.preQuoteDate, 'KR')}
+ </span>
+ </div>
+ </div>
+ )}
+
+ {biddingDetail.budget && (
+ <div>
+ <strong className="text-gray-700">예산:</strong>
+ <div className="mt-1 font-mono">
+ {biddingDetail.budget?.toLocaleString()} {biddingDetail.currency || 'KRW'}
+ </div>
+ </div>
+ )}
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 참여 의사 선택 */}
+ <div className="space-y-4">
+ <h3 className="text-lg font-semibold text-gray-900">
+ 사전견적에 참여하시겠습니까?
+ </h3>
+
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ {/* 참여 */}
+ <Card
+ className={`cursor-pointer transition-all border-2 ${
+ selectedDecision === true
+ ? 'border-green-500 bg-green-50 shadow-md'
+ : 'border-gray-200 hover:border-green-300 hover:shadow-sm'
+ }`}
+ onClick={() => setSelectedDecision(true)}
+ >
+ <CardContent className="p-6 text-center">
+ <CheckCircle className="w-16 h-16 text-green-600 mx-auto mb-4" />
+ <h4 className="text-xl font-semibold text-green-700 mb-2">
+ 참여하겠습니다
+ </h4>
+ <p className="text-sm text-gray-600 leading-relaxed">
+ 사전견적서를 작성하여 제출하겠습니다.<br/>
+ 마감일까지 견적을 완료해주세요.
+ </p>
+ </CardContent>
+ </Card>
+
+ {/* 참여 안함 */}
+ <Card
+ className={`cursor-pointer transition-all border-2 ${
+ selectedDecision === false
+ ? 'border-red-500 bg-red-50 shadow-md'
+ : 'border-gray-200 hover:border-red-300 hover:shadow-sm'
+ }`}
+ onClick={() => setSelectedDecision(false)}
+ >
+ <CardContent className="p-6 text-center">
+ <XCircle className="w-16 h-16 text-red-600 mx-auto mb-4" />
+ <h4 className="text-xl font-semibold text-red-700 mb-2">
+ 참여하지 않겠습니다
+ </h4>
+ <p className="text-sm text-gray-600 leading-relaxed">
+ 이번 사전견적에는 참여하지 않겠습니다.<br/>
+ 다음 기회에 참여하겠습니다.
+ </p>
+ </CardContent>
+ </Card>
+ </div>
+
+ {selectedDecision !== null && (
+ <div className={`mt-4 p-4 rounded-lg border ${
+ selectedDecision
+ ? 'bg-green-50 border-green-200'
+ : 'bg-red-50 border-red-200'
+ }`}>
+ <div className="flex items-center gap-2">
+ {selectedDecision ? (
+ <CheckCircle className="w-5 h-5 text-green-600" />
+ ) : (
+ <XCircle className="w-5 h-5 text-red-600" />
+ )}
+ <span className={`font-medium ${
+ selectedDecision ? 'text-green-800' : 'text-red-800'
+ }`}>
+ {selectedDecision
+ ? '사전견적 참여를 선택하셨습니다.'
+ : '사전견적 참여를 거절하셨습니다.'
+ }
+ </span>
+ </div>
+ <p className={`text-sm mt-1 ${
+ selectedDecision ? 'text-green-600' : 'text-red-600'
+ }`}>
+ {selectedDecision
+ ? '확인을 누르시면 견적서 작성 화면으로 이동합니다.'
+ : '확인을 누르시면 이 입찰건의 참여가 종료됩니다.'
+ }
+ </p>
+ </div>
+ )}
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button variant="outline" onClick={() => handleOpenChange(false)}>
+ 취소
+ </Button>
+ <Button
+ onClick={handleSubmit}
+ disabled={isPending || selectedDecision === null}
+ className={selectedDecision === true ? 'bg-green-600 hover:bg-green-700' :
+ selectedDecision === false ? 'bg-red-600 hover:bg-red-700' : ''}
+ >
+ {selectedDecision === true && <CheckCircle className="w-4 h-4 mr-2" />}
+ {selectedDecision === false && <XCircle className="w-4 h-4 mr-2" />}
+ {selectedDecision === true ? '참여 확정' :
+ selectedDecision === false ? '참여 거절' : '선택하세요'}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}