diff options
| -rw-r--r-- | lib/bidding/detail/table/bidding-award-dialog.tsx | 202 | ||||
| -rw-r--r-- | lib/bidding/failure/biddings-closure-dialog.tsx | 62 |
2 files changed, 61 insertions, 203 deletions
diff --git a/lib/bidding/detail/table/bidding-award-dialog.tsx b/lib/bidding/detail/table/bidding-award-dialog.tsx index ff104fac..b168e884 100644 --- a/lib/bidding/detail/table/bidding-award-dialog.tsx +++ b/lib/bidding/detail/table/bidding-award-dialog.tsx @@ -35,12 +35,6 @@ interface BiddingAwardDialogProps { open: boolean onOpenChange: (open: boolean) => void onSuccess: () => void - onApprovalPreview?: (data: { - templateName: string - variables: Record<string, string> - title: string - selectionReason: string - }) => void } interface AwardedCompany { @@ -54,8 +48,7 @@ export function BiddingAwardDialog({ biddingId, open, onOpenChange, - onSuccess, - onApprovalPreview + onSuccess }: BiddingAwardDialogProps) { const { toast } = useToast() const { data: session } = useSession() @@ -114,36 +107,43 @@ const userId = session?.user?.id || '2'; return } - // 결재 템플릿 변수 준비 - const { mapBiddingAwardToTemplateVariables } = await import('@/lib/bidding/handlers') - - try { - const variables = await mapBiddingAwardToTemplateVariables({ - biddingId, - selectionReason, - requestedAt: new Date() - }) + // 서버 액션을 사용하여 결재 상신 + startTransition(async () => { + try { + const result = await requestBiddingAwardWithApproval({ + biddingId, + selectionReason: selectionReason.trim(), + currentUser: { + id: Number(userId), + epId: session?.user?.epId || null, + email: session?.user?.email || undefined, + }, + }) - // 상위 컴포넌트로 결재 미리보기 데이터 전달 - if (onApprovalPreview) { - onApprovalPreview({ - templateName: '입찰 결과 업체 선정 품의 요청서', - variables, - title: `낙찰 - ${bidding?.title}`, - selectionReason + if (result.status === 'pending_approval') { + toast({ + title: '성공', + description: '낙찰 결재가 상신되었습니다.', + }) + onOpenChange(false) + setSelectionReason('') + onSuccess() + } else { + toast({ + title: '오류', + description: '결재 상신에 실패했습니다.', + variant: 'destructive', + }) + } + } catch (error) { + console.error('낙찰 결재 상신 실패:', error) + toast({ + title: '오류', + description: error instanceof Error ? error.message : '결재 상신 중 오류가 발생했습니다.', + variant: 'destructive', }) } - - onOpenChange(false) - setSelectionReason('') - } catch (error) { - console.error('낙찰 템플릿 변수 준비 실패:', error) - toast({ - title: '오류', - description: '결재 문서 준비 중 오류가 발생했습니다.', - variant: 'destructive', - }) - } + }) } @@ -276,136 +276,4 @@ const userId = session?.user?.id || '2'; </DialogContent> </Dialog> ) - - return ( - <> - <Dialog open={open} onOpenChange={onOpenChange}> - <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> - <DialogHeader> - <DialogTitle className="flex items-center gap-2"> - <Trophy className="w-5 h-5 text-yellow-600" /> - 낙찰 처리 - </DialogTitle> - <DialogDescription> - 낙찰된 업체의 발주비율과 선정 사유를 확인하고 낙찰을 완료하세요. - </DialogDescription> - </DialogHeader> - - <form onSubmit={handleSubmit}> - <div className="space-y-6"> - {/* 낙찰 업체 정보 */} - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - <Building2 className="w-4 h-4" /> - 낙찰 업체 정보 - </CardTitle> - </CardHeader> - <CardContent> - {isLoading ? ( - <div className="text-center py-4"> - <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div> - <p className="mt-2 text-sm text-muted-foreground">낙찰 업체 정보를 불러오는 중...</p> - </div> - ) : awardedCompanies.length > 0 ? ( - <div className="space-y-4"> - <Table> - <TableHeader> - <TableRow> - <TableHead>업체명</TableHead> - <TableHead className="text-right">견적금액</TableHead> - <TableHead className="text-right">발주비율</TableHead> - <TableHead className="text-right">발주금액</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {awardedCompanies.map((company) => ( - <TableRow key={company.companyId}> - <TableCell className="font-medium"> - <div className="flex items-center gap-2"> - <Badge variant="default" className="bg-green-600">낙찰</Badge> - {company.companyName} - </div> - </TableCell> - <TableCell className="text-right"> - {company.finalQuoteAmount.toLocaleString()}원 - </TableCell> - <TableCell className="text-right"> - {company.awardRatio}% - </TableCell> - <TableCell className="text-right font-semibold"> - {(company.finalQuoteAmount * company.awardRatio / 100).toLocaleString()}원 - </TableCell> - </TableRow> - ))} - </TableBody> - </Table> - - {/* 최종입찰가 요약 */} - <div className="flex items-center justify-between p-4 bg-blue-50 border border-blue-200 rounded-lg"> - <div className="flex items-center gap-2"> - <Calculator className="w-5 h-5 text-blue-600" /> - <span className="font-semibold text-blue-800">최종입찰가</span> - </div> - <span className="text-xl font-bold text-blue-800"> - {finalBidPrice.toLocaleString()}원 - </span> - </div> - </div> - ) : ( - <div className="text-center py-8"> - <Trophy className="w-12 h-12 text-gray-400 mx-auto mb-4" /> - <p className="text-gray-500 mb-2">낙찰된 업체가 없습니다</p> - <p className="text-sm text-gray-400"> - 먼저 업체 수정 다이얼로그에서 발주비율을 산정해주세요. - </p> - </div> - )} - </CardContent> - </Card> - - {/* 낙찰 사유 */} - <div className="space-y-2"> - <Label htmlFor="selectionReason"> - 낙찰 사유 <span className="text-red-500">*</span> - </Label> - <Textarea - id="selectionReason" - placeholder="낙찰 사유를 상세히 입력해주세요..." - value={selectionReason} - onChange={(e) => setSelectionReason(e.target.value)} - rows={4} - className="resize-none" - /> - </div> - - {/* 첨부파일 */} - <AwardSimpleFileUpload - biddingId={biddingId} - userId={userId} - readOnly={false} - /> - </div> - - <DialogFooter className="mt-6"> - <Button - type="button" - variant="outline" - onClick={() => onOpenChange(false)} - disabled={isPending} - > - 취소 - </Button> - <Button - type="submit" - disabled={isPending || awardedCompanies.length === 0} - > - {isPending ? '상신 중...' : '결재 상신'} - </Button> - </DialogFooter> - </form> - </DialogContent> - </Dialog> - </> - ) } diff --git a/lib/bidding/failure/biddings-closure-dialog.tsx b/lib/bidding/failure/biddings-closure-dialog.tsx index 93ba0eda..f331167b 100644 --- a/lib/bidding/failure/biddings-closure-dialog.tsx +++ b/lib/bidding/failure/biddings-closure-dialog.tsx @@ -21,26 +21,13 @@ interface BiddingsClosureDialogProps { biddingNumber: string; } | null; onSuccess?: () => void; - onApprovalPreview?: (data: { - templateName: string - variables: Record<string, string> - title: string - description: string - files?: File[] - }) => void } - -interface ClosureFormData { - description: string; - files: File[]; -} export function BiddingsClosureDialog({ open, onOpenChange, bidding, - onSuccess, - onApprovalPreview + onSuccess }: BiddingsClosureDialogProps) { const { data: session } = useSession() const [description, setDescription] = useState('') @@ -55,34 +42,37 @@ interface ClosureFormData { return } - // 결재 템플릿 변수 준비 - const { mapBiddingClosureToTemplateVariables } = await import('@/lib/bidding/handlers') + setIsSubmitting(true) try { - const variables = await mapBiddingClosureToTemplateVariables({ + const result = await requestBiddingClosureWithApproval({ biddingId: bidding.id, description: description.trim(), - requestedAt: new Date() + files, + currentUser: { + id: session?.user?.id ? Number(session.user.id) : 0, + epId: session?.user?.epId || null, + email: session?.user?.email || undefined, + }, }) - // 상위 컴포넌트로 결재 미리보기 데이터 전달 - if (onApprovalPreview) { - onApprovalPreview({ - templateName: '폐찰 품의 요청서', - variables, - title: `폐찰 - ${bidding.title}`, - description: description.trim(), - files - }) + if (result.status === 'pending_approval') { + toast.success('폐찰 결재가 상신되었습니다.') + onOpenChange(false) + // 폼 초기화 + setDescription('') + setFiles([]) + if (onSuccess) { + onSuccess() + } + } else { + toast.error('결재 상신에 실패했습니다.') } - - onOpenChange(false) - // 폼 초기화 - setDescription('') - setFiles([]) } catch (error) { - console.error('폐찰 템플릿 변수 준비 실패:', error) - toast.error('결재 문서 준비 중 오류가 발생했습니다.') + console.error('폐찰 결재 상신 실패:', error) + toast.error(error instanceof Error ? error.message : '결재 상신 중 오류가 발생했습니다.') + } finally { + setIsSubmitting(false) } } @@ -158,6 +148,6 @@ interface ClosureFormData { </form> </DialogContent> </Dialog> - </> - ) + ) + }
\ No newline at end of file |
