'use client' import * as React from 'react' import { useTransition } from 'react' import { useSession } from 'next-auth/react' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { Trophy, Building2, Calculator } from 'lucide-react' import { useToast } from '@/hooks/use-toast' import { getAwardedCompanies } from '@/lib/bidding/detail/service' import { requestBiddingAwardWithApproval, prepareBiddingAwardApprovalData } from '@/lib/bidding/approval-actions' import { AwardSimpleFileUpload } from './components/award-simple-file-upload' import { ApprovalPreviewDialog } from '@/lib/approval/approval-preview-dialog' interface BiddingAwardDialogProps { biddingId: number open: boolean onOpenChange: (open: boolean) => void onSuccess: () => void onApprovalPreview?: (data: { templateName: string variables: Record title: string selectionReason: string awardedCompanies: { companyId: number companyName: string | null finalQuoteAmount: number awardRatio: number }[] }) => void } interface AwardedCompany { companyId: number companyName: string | null finalQuoteAmount: number awardRatio: number } export function BiddingAwardDialog({ biddingId, open, onOpenChange, onSuccess, onApprovalPreview }: BiddingAwardDialogProps) { const { toast } = useToast() const { data: session } = useSession() const [isPending, startTransition] = useTransition() const [selectionReason, setSelectionReason] = React.useState('') const [awardedCompanies, setAwardedCompanies] = React.useState([]) const [isLoading, setIsLoading] = React.useState(false) const [isApprovalDialogOpen, setIsApprovalDialogOpen] = React.useState(false) const [approvalVariables, setApprovalVariables] = React.useState>({}) const [approvalTitle, setApprovalTitle] = React.useState('') const userId = session?.user?.id || '2'; // 낙찰된 업체 정보 로드 React.useEffect(() => { if (open) { setIsLoading(true) getAwardedCompanies(biddingId) .then(companies => { setAwardedCompanies(companies) }) .catch(error => { console.error('Failed to load awarded companies:', error) toast({ title: '오류', description: '낙찰 업체 정보를 불러오는데 실패했습니다.', variant: 'destructive', }) }) .finally(() => { setIsLoading(false) }) } }, [open, biddingId, toast]) // 최종입찰가 계산 const finalBidPrice = React.useMemo(() => { return awardedCompanies.reduce((sum, company) => { return sum + (company.finalQuoteAmount * company.awardRatio / 100) }, 0) }, [awardedCompanies]) // 다음단계 버튼 핸들러 - 결재 준비 및 부모로 데이터 전달 const handleNextStep = async () => { if (!selectionReason.trim()) { toast({ title: '유효성 오류', description: '낙찰 사유를 입력해주세요.', variant: 'destructive', }) return } if (awardedCompanies.length === 0) { toast({ title: '유효성 오류', description: '낙찰된 업체가 없습니다. 먼저 발주비율을 산정해주세요.', variant: 'destructive', }) return } try { // 결재 데이터 준비 (템플릿 변수, 제목 등) const approvalData = await prepareBiddingAwardApprovalData({ biddingId, selectionReason: selectionReason.trim(), awardedCompanies, }) // 결재 준비 완료 - 부모 컴포넌트의 결재 미리보기 콜백 호출 if (onApprovalPreview) { onApprovalPreview({ templateName: '입찰 결과 업체 선정 품의 요청서', variables: approvalData.variables, title: `낙찰 - ${approvalData.bidding.title}`, selectionReason: selectionReason.trim(), awardedCompanies, }) } else { // 기존 로직 유지 (내부 결재 다이얼로그 사용) setApprovalVariables(approvalData.variables) setApprovalTitle(`낙찰 - ${approvalData.bidding.title}`) setIsApprovalDialogOpen(true) } } catch (error) { console.error('결재 준비 중 오류 발생:', error) toast({ title: '오류', description: '결재 준비 중 오류가 발생했습니다.', variant: 'destructive', }) } } // 결재 상신 핸들러 - 결재 완료 시 실제 낙찰 등록 실행 const handleApprovalSubmit = async ({ approvers, title, attachments }: { approvers: string[], title: string, attachments?: File[] }) => { try { if (!session?.user?.id || !session.user.epId) { toast({ title: '오류', description: '필요한 정보가 없습니다.', variant: 'destructive', }) return } // 결재 상신 const result = await requestBiddingAwardWithApproval({ biddingId, selectionReason: selectionReason.trim(), awardedCompanies, // ✅ 결재 상신 시점의 낙찰 대상 정보 전달 currentUser: { id: Number(session.user.id), epId: session.user.epId, email: session.user.email || undefined, }, approvers, }) if (result.status === 'pending_approval') { toast({ title: '낙찰 결재 상신 완료', description: `결재가 상신되었습니다. (ID: ${result.approvalId})`, }) setIsApprovalDialogOpen(false) onOpenChange(false) setSelectionReason('') onSuccess() } } catch (error) { console.error('결재 상신 중 오류 발생:', error) toast({ title: '오류', description: '결재 상신 중 오류가 발생했습니다.', variant: 'destructive', }) } } return ( 낙찰 처리 낙찰된 업체의 발주비율과 선정 사유를 확인하고 낙찰을 완료하세요.
{/* 낙찰 업체 정보 */} 낙찰 업체 정보 {isLoading ? (

낙찰 업체 정보를 불러오는 중...

) : awardedCompanies.length > 0 ? (
업체명 견적금액 발주비율 발주금액 {awardedCompanies.map((company) => (
낙찰 {company.companyName}
{company.finalQuoteAmount.toLocaleString()}원 {company.awardRatio}% {(company.finalQuoteAmount * company.awardRatio / 100).toLocaleString()}원
))}
{/* 최종입찰가 요약 */}
최종입찰가
{finalBidPrice.toLocaleString()}원
) : (

낙찰된 업체가 없습니다

먼저 업체 수정 다이얼로그에서 발주비율을 산정해주세요.

)}
{/* 낙찰 사유 */}