From e9897d416b3e7327bbd4d4aef887eee37751ae82 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 27 Jun 2025 01:16:20 +0000 Subject: (대표님) 20250627 오전 10시 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/periodic-evaluation-action-dialogs.tsx | 373 +++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 lib/evaluation/table/periodic-evaluation-action-dialogs.tsx (limited to 'lib/evaluation/table/periodic-evaluation-action-dialogs.tsx') diff --git a/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx new file mode 100644 index 00000000..30ff9535 --- /dev/null +++ b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx @@ -0,0 +1,373 @@ +"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 { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { FileText, Users, Calendar, Send } from "lucide-react" +import { toast } from "sonner" +import { PeriodicEvaluationView } from "@/db/schema" +import { checkExistingSubmissions, requestDocumentsFromVendors } from "../service" + + +// ================================================================ +// 2. 협력업체 자료 요청 다이얼로그 +// ================================================================ +interface RequestDocumentsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + evaluations: PeriodicEvaluationView[] + onSuccess: () => void +} + +interface EvaluationWithSubmissionStatus extends PeriodicEvaluationView { + hasExistingSubmission?: boolean + submissionDate?: Date | null +} + +export function RequestDocumentsDialog({ + open, + onOpenChange, + evaluations, + onSuccess, +}: RequestDocumentsDialogProps) { + + console.log(evaluations) + + const [isLoading, setIsLoading] = React.useState(false) + const [isCheckingStatus, setIsCheckingStatus] = React.useState(false) + const [message, setMessage] = React.useState("") + const [evaluationsWithStatus, setEvaluationsWithStatus] = React.useState([]) + + // 제출대기 상태인 평가들만 필터링 + const pendingEvaluations = React.useMemo(() => + evaluations.filter(e => e.status === "PENDING_SUBMISSION"), + [evaluations] + ) + + React.useEffect(() => { + if (!open) return; + + // 대기 중 평가가 없으면 초기화 + if (pendingEvaluations.length === 0) { + setEvaluationsWithStatus([]); + setIsCheckingStatus(false); + return; + } + + // 상태 확인 + (async () => { + setIsCheckingStatus(true); + try { + const ids = pendingEvaluations.map(e => e.id); + const existing = await checkExistingSubmissions(ids); + + setEvaluationsWithStatus( + pendingEvaluations.map(e => ({ + ...e, + hasExistingSubmission: existing.some(s => s.periodicEvaluationId === e.id), + submissionDate: existing.find(s => s.periodicEvaluationId === e.id)?.createdAt ?? null, + })), + ); + } catch (err) { + console.error(err); + setEvaluationsWithStatus( + pendingEvaluations.map(e => ({ ...e, hasExistingSubmission: false })), + ); + } finally { + setIsCheckingStatus(false); + } + })(); + }, [open, pendingEvaluations]); // 함수 대신 값에만 의존 + + // 새 요청과 재요청 분리 + const newRequests = evaluationsWithStatus.filter(e => !e.hasExistingSubmission) + const reRequests = evaluationsWithStatus.filter(e => e.hasExistingSubmission) + + const handleSubmit = async () => { + if (!message.trim()) { + toast.error("요청 메시지를 입력해주세요.") + return + } + + setIsLoading(true) + try { + // 서버 액션 데이터 준비 + const requestData = evaluationsWithStatus.map(evaluation => ({ + periodicEvaluationId: evaluation.id, + companyId: evaluation.vendorId, + evaluationYear: evaluation.evaluationYear, + evaluationRound: evaluation.evaluationPeriod, + message: message.trim() + })) + + // 서버 액션 호출 + const result = await requestDocumentsFromVendors(requestData) + + if (result.success) { + toast.success(result.message) + onSuccess() + onOpenChange(false) + setMessage("") + } else { + toast.error(result.message) + } + } catch (error) { + console.error('Error requesting documents:', error) + toast.error("자료 요청 발송 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + } + } + + return ( + + + + + + 협력업체 자료 요청 + + + 선택된 평가의 협력업체들에게 평가 자료 제출을 요청합니다. + + + +
+ {isCheckingStatus ? ( +
+
요청 상태를 확인하고 있습니다...
+
+ ) : ( + <> + {/* 신규 요청 대상 업체 */} + {newRequests.length > 0 && ( + + + + 신규 요청 대상 ({newRequests.length}개 업체) + + + + {newRequests.map((evaluation) => ( +
+ {evaluation.vendorName} +
+ {evaluation.vendorCode} + 신규 +
+
+ ))} +
+
+ )} + + {/* 재요청 대상 업체 */} + {reRequests.length > 0 && ( + + + + 재요청 대상 ({reRequests.length}개 업체) + + + + {reRequests.map((evaluation) => ( +
+ {evaluation.vendorName} +
+ {evaluation.vendorCode} + + 재요청 + + {evaluation.submissionDate && ( + + {new Intl.DateTimeFormat("ko-KR", { + month: "2-digit", + day: "2-digit", + }).format(new Date(evaluation.submissionDate))} + + )} +
+
+ ))} +
+
+ )} + + {/* 요청 대상이 없는 경우 */} + {!isCheckingStatus && evaluationsWithStatus.length === 0 && ( + + +
+ 요청할 수 있는 평가가 없습니다. +
+
+
+ )} + + )} + + {/* 요청 메시지 */} +
+ +