diff options
Diffstat (limited to 'lib/bidding/vendor/vendor-prequote-participation-dialog.tsx')
| -rw-r--r-- | lib/bidding/vendor/vendor-prequote-participation-dialog.tsx | 268 |
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> + ) +} |
