From 4ee8b24cfadf47452807fa2af801385ed60ab47c Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 15 Sep 2025 14:41:01 +0000 Subject: (대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tbe-last/table/evaluation-dialog.tsx | 432 +++++++++++++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 lib/tbe-last/table/evaluation-dialog.tsx (limited to 'lib/tbe-last/table/evaluation-dialog.tsx') diff --git a/lib/tbe-last/table/evaluation-dialog.tsx b/lib/tbe-last/table/evaluation-dialog.tsx new file mode 100644 index 00000000..ac1d923b --- /dev/null +++ b/lib/tbe-last/table/evaluation-dialog.tsx @@ -0,0 +1,432 @@ +// lib/tbe-last/table/dialogs/evaluation-dialog.tsx + +"use client" + +import * as React from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import * as z from "zod" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Checkbox } from "@/components/ui/checkbox" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { TbeLastView } from "@/db/schema" +import { toast } from "sonner" +import { updateTbeEvaluation ,getTbeVendorDocuments} from "../service" +import { + FileText, + CheckCircle, + XCircle, + AlertCircle, + Clock, + Loader2, + Info +} from "lucide-react" + +// 폼 스키마 +const evaluationSchema = z.object({ + evaluationResult: z.enum(["Acceptable", "Acceptable with Comment", "Not Acceptable"], { + required_error: "평가 결과를 선택해주세요", + }), + conditionalRequirements: z.string().optional(), + conditionsFulfilled: z.boolean().default(false), + overallRemarks: z.string().optional(), +}) + +type EvaluationFormValues = z.infer + +interface EvaluationDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + selectedSession: TbeLastView | null + onSuccess?: () => void +} + +export function EvaluationDialog({ + open, + onOpenChange, + selectedSession, + onSuccess +}: EvaluationDialogProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [isLoadingDocs, setIsLoadingDocs] = React.useState(false) + const [vendorDocuments, setVendorDocuments] = React.useState([]) + + const form = useForm({ + resolver: zodResolver(evaluationSchema), + defaultValues: { + evaluationResult: undefined, + conditionalRequirements: "", + conditionsFulfilled: false, + overallRemarks: "", + }, + }) + + const watchEvaluationResult = form.watch("evaluationResult") + const isFormValid = form.formState.isValid + + // 벤더 문서 리뷰 상태 가져오기 + React.useEffect(() => { + if (open && selectedSession?.tbeSessionId) { + fetchVendorDocuments() + + // 기존 평가 데이터가 있으면 폼에 설정 + if (selectedSession.evaluationResult) { + form.reset({ + evaluationResult: selectedSession.evaluationResult as any, + conditionalRequirements: selectedSession.conditionalRequirements || "", + conditionsFulfilled: selectedSession.conditionsFulfilled || false, + overallRemarks: selectedSession.overallRemarks || "", + }) + } else { + // 기존 평가 데이터가 없으면 초기화 + form.reset({ + evaluationResult: undefined, + conditionalRequirements: "", + conditionsFulfilled: false, + overallRemarks: "", + }) + } + } else if (!open) { + // 다이얼로그가 닫힐 때 폼 리셋 + form.reset({ + evaluationResult: undefined, + conditionalRequirements: "", + conditionsFulfilled: false, + overallRemarks: "", + }) + setVendorDocuments([]) + } + }, [open, selectedSession]) + + const fetchVendorDocuments = async () => { + if (!selectedSession?.tbeSessionId) return + + setIsLoadingDocs(true) + try { + // 서버 액션 호출 + const result = await getTbeVendorDocuments(selectedSession.tbeSessionId) + + if (result.success) { + setVendorDocuments(result.documents || []) + } else { + console.error("Failed to fetch vendor documents:", result.error) + toast.error(result.error || "벤더 문서 정보를 불러오는데 실패했습니다") + } + } catch (error) { + console.error("Failed to fetch vendor documents:", error) + toast.error("벤더 문서 정보를 불러오는데 실패했습니다") + } finally { + setIsLoadingDocs(false) + } + } + + const getReviewStatusIcon = (status: string) => { + switch (status) { + case "승인": + return + case "반려": + return + case "재검토필요": + return + case "검토완료": + return + case "검토중": + return + default: + return + } + } + + const getReviewStatusVariant = (status: string): any => { + switch (status) { + case "승인": + return "default" + case "반려": + return "destructive" + case "재검토필요": + return "secondary" + case "검토완료": + return "outline" + default: + return "outline" + } + } + + const onSubmit = async (values: EvaluationFormValues) => { + if (!selectedSession?.tbeSessionId) return + + // 벤더 문서가 없는 경우 경고 + if (vendorDocuments.length === 0 && !isLoadingDocs) { + const confirmed = window.confirm( + "검토된 벤더 문서가 없습니다. 그래도 평가를 진행하시겠습니까?" + ) + if (!confirmed) return + } + + setIsLoading(true) + try { + // 서버 액션 호출 + const result = await updateTbeEvaluation(selectedSession.tbeSessionId, { + ...values, + status: "완료", // 평가 완료 시 상태 업데이트 + }) + + if (result.success) { + toast.success("평가가 성공적으로 저장되었습니다") + form.reset() + onOpenChange(false) + onSuccess?.() + } else { + toast.error(result.error || "평가 저장에 실패했습니다") + } + } catch (error) { + console.error("Failed to save evaluation:", error) + toast.error("평가 저장 중 오류가 발생했습니다") + } finally { + setIsLoading(false) + } + } + + const allDocumentsApproved = vendorDocuments.length > 0 && + vendorDocuments.every((doc: any) => doc.reviewStatus === "승인" || doc.reviewStatus === "검토완료") + + const hasRejectedDocuments = vendorDocuments.some((doc: any) => doc.reviewStatus === "반려") + + return ( + + + + TBE 결과 입력 + + {selectedSession?.sessionCode} - {selectedSession?.vendorName} + + + +
+
+ {/* 벤더 문서 검토 현황 */} +
+

벤더 문서 검토 현황

+ + {isLoadingDocs ? ( +
+ + 문서 정보 로딩 중... +
+ ) : vendorDocuments.length === 0 ? ( + + + + 검토할 벤더 문서가 없습니다. + + + ) : ( +
+ {vendorDocuments.map((doc: any) => ( +
+
+ +
+

{doc.documentName}

+

{doc.documentType}

+
+
+
+ {getReviewStatusIcon(doc.reviewStatus)} + + {doc.reviewStatus} + +
+
+ ))} + + {/* 문서 검토 상태 요약 */} +
+
+ 전체 문서: {vendorDocuments.length}개 +
+ + 승인: {vendorDocuments.filter(d => d.reviewStatus === "승인").length} + + + 반려: {vendorDocuments.filter(d => d.reviewStatus === "반려").length} + + + 미검토: {vendorDocuments.filter(d => d.reviewStatus === "미검토").length} + +
+
+ + {hasRejectedDocuments && ( + + + + 반려된 문서가 있습니다. 평가 결과를 "Not Acceptable"로 설정하는 것을 권장합니다. + + + )} + + {!allDocumentsApproved && !hasRejectedDocuments && ( + + + + 모든 문서 검토가 완료되지 않았습니다. + + + )} +
+
+ )} +
+ + {/* 평가 폼 */} +
+ + ( + + + 평가 결과 * + + + + 최종 평가 결과를 선택합니다. + + + + )} + /> + + {/* 조건부 승인 필드 */} + {watchEvaluationResult === "Acceptable with Comment" && ( + <> + ( + + 조건부 요구사항 + +