summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/bidding/detail/table/bidding-award-dialog.tsx202
-rw-r--r--lib/bidding/failure/biddings-closure-dialog.tsx62
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