From a3525f8bdfcf849cc1716fab81cb8facadbe9a8e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 27 Oct 2025 10:03:06 +0000 Subject: (최겸) 구매 협력업체 관리(PQ/실사관리, 정기평가 협력업체 제출 상세 dialog 개발, MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/evaluation/table/vendor-submission-dialog.tsx | 623 ++++++++++++++++++++++ 1 file changed, 623 insertions(+) create mode 100644 lib/evaluation/table/vendor-submission-dialog.tsx (limited to 'lib/evaluation/table/vendor-submission-dialog.tsx') diff --git a/lib/evaluation/table/vendor-submission-dialog.tsx b/lib/evaluation/table/vendor-submission-dialog.tsx new file mode 100644 index 00000000..aff8dc56 --- /dev/null +++ b/lib/evaluation/table/vendor-submission-dialog.tsx @@ -0,0 +1,623 @@ +"use client" + +import * as React from "react" +import { + Eye, + Building2, + User, + Calendar, + CheckCircle2, + Clock, + MessageSquare, + Award, + FileText, + Paperclip, + Download, + File, + BarChart3, + ChevronDown, + ChevronRight +} from "lucide-react" + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Separator } from "@/components/ui/separator" +import { Skeleton } from "@/components/ui/skeleton" +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { PeriodicEvaluationView } from "@/db/schema" +import { getVendorSubmissionDetails, type VendorSubmissionDetail } from "../vendor-submission-service" +import { formatFileSize, getFileInfo } from "@/lib/file-download" + +interface VendorSubmissionDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + evaluation: PeriodicEvaluationView | null +} + +// 상태별 배지 색상 +const getSubmissionStatusBadge = (status: string) => { + switch (status) { + case "submitted": + return 제출완료 + case "draft": + return 임시저장 + case "reviewed": + return 검토완료 + default: + return {status} + } +} + +// 진행률 계산 +const getProgressPercentage = (completed: number, total: number) => { + if (total === 0) return 0 + return Math.round((completed / total) * 100) +} + +export function VendorSubmissionDialog({ + open, + onOpenChange, + evaluation, +}: VendorSubmissionDialogProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [submissionDetails, setSubmissionDetails] = React.useState(null) + const [expandedGeneralItems, setExpandedGeneralItems] = React.useState>(new Set()) + const [expandedEsgItems, setExpandedEsgItems] = React.useState>(new Set()) + + // 첨부파일 다운로드 핸들러 + const handleDownloadAttachment = async (filePath: string, fileName: string) => { + try { + const { downloadFile } = await import('@/lib/file-download') + await downloadFile(filePath, fileName, { + action: 'download', + showToast: true, + showSuccessToast: true, + onError: (error) => { + console.error("파일 다운로드 실패:", error) + }, + }) + } catch (error) { + console.error("파일 다운로드 실패:", error) + } + } + + // 일반평가 항목 토글 + const toggleGeneralItem = (itemId: number) => { + const newExpanded = new Set(expandedGeneralItems) + if (newExpanded.has(itemId)) { + newExpanded.delete(itemId) + } else { + newExpanded.add(itemId) + } + setExpandedGeneralItems(newExpanded) + } + + // ESG 평가 항목 토글 + const toggleEsgItem = (itemId: number) => { + const newExpanded = new Set(expandedEsgItems) + if (newExpanded.has(itemId)) { + newExpanded.delete(itemId) + } else { + newExpanded.add(itemId) + } + setExpandedEsgItems(newExpanded) + } + + // 제출 상세 정보 로드 + React.useEffect(() => { + if (open && evaluation?.id) { + const loadSubmissionDetails = async () => { + try { + setIsLoading(true) + const details = await getVendorSubmissionDetails(evaluation.id) + setSubmissionDetails(details) + } catch (error) { + console.error("Failed to load vendor submission details:", error) + } finally { + setIsLoading(false) + } + } + + loadSubmissionDetails() + } + }, [open, evaluation?.id]) + + // 다이얼로그 닫을 때 데이터 리셋 + React.useEffect(() => { + if (!open) { + setSubmissionDetails(null) + setExpandedGeneralItems(new Set()) + setExpandedEsgItems(new Set()) + } + }, [open]) + + if (!evaluation) return null + + return ( + + + + {/* 고정 헤더 */} + + + + 협력업체 제출 상세 + + + {/* 평가 기본 정보 */} + + + + + 평가 정보 + + + +
+ {/* 협력업체 */} +
+ 협력업체: + {evaluation.vendorName} + ({evaluation.vendorCode}) +
+ + {/* 평가년도 */} +
+ 년도: + {evaluation.evaluationYear}년 +
+ + {/* 구분 */} +
+ 구분: + + {evaluation.division === "PLANT" ? "해양" : "조선"} + +
+ + {/* 진행상태 */} +
+ 상태: + {evaluation.status} +
+
+
+
+
+ + {/* 스크롤 가능한 컨텐츠 영역 */} +
+ {isLoading ? ( +
+ + + + + + + + +
+ ) : submissionDetails ? ( +
+ {/* 제출 정보 요약 */} + + + + + 제출 정보 + + + +
+
+
제출 상태
+
{getSubmissionStatusBadge(submissionDetails.submissionStatus)}
+
+
+
제출일
+
+ {submissionDetails.submittedAt + ? new Date(submissionDetails.submittedAt).toLocaleDateString('ko-KR') + : "-" + } +
+
+ {/*
+
ESG 평균 점수
+
+ {submissionDetails.averageEsgScore + ? `${submissionDetails.averageEsgScore.toFixed(1)}점` + : "-" + } +
+
*/} + {/*
+
검토자
+
+ {submissionDetails.reviewedBy || "-"} +
+
*/} +
+ + {/* 진행률 표시 */} + {/*
+
+
+ 일반평가 진행률 + + {submissionDetails.completedGeneralItems}/{submissionDetails.totalGeneralItems} + ({getProgressPercentage(submissionDetails.completedGeneralItems, submissionDetails.totalGeneralItems)}%) + +
+
+
+
+
+ +
+
+ ESG 평가 진행률 + + {submissionDetails.completedEsgItems}/{submissionDetails.totalEsgItems} + ({getProgressPercentage(submissionDetails.completedEsgItems, submissionDetails.totalEsgItems)}%) + +
+
+
+
+
+
*/} + + + + {/* 탭으로 일반평가와 ESG 평가 구분 */} + + + + + 일반평가 ({submissionDetails.generalEvaluations.length}개) + + {submissionDetails.vendor.country === "KR" && ( + + + ESG 평가 ({submissionDetails.esgEvaluations.length}개) + + )} + + + {/* 일반평가 탭 */} + + {submissionDetails.generalEvaluations.length > 0 ? ( +
+ {submissionDetails.generalEvaluations.map((item) => ( + + + + toggleGeneralItem(item.id)} + > +
+
+ {expandedGeneralItems.has(item.id) ? ( + + ) : ( + + )} + + {item.serialNumber}. {item.category} + +
+
+ {item.response ? ( + 응답완료 + ) : ( + 미응답 + )} + {item.response?.hasAttachments && ( + + + 첨부파일 + + )} +
+
+
+
+ + +
+
평가 항목
+
{item.inspectionItem}
+ {item.remarks && ( +
+
비고
+
{item.remarks}
+
+ )} +
+ + {item.response ? ( +
+ +
+
협력업체 응답
+
+ {item.response.responseText || "응답 내용이 없습니다."} +
+
+ + {/* 첨부파일 */} + {item.response.attachments.length > 0 && ( +
+
첨부파일
+
+ {item.response.attachments.map((attachment) => { + const fileInfo = getFileInfo(attachment.originalFileName) + return ( +
+ {fileInfo.icon} + + + + {attachment.originalFileName} + + + +
+
{attachment.originalFileName}
+
크기: {formatFileSize(attachment.fileSize)}
+
타입: {fileInfo.type}
+
업로드: {attachment.uploadedBy}
+
+
+
+ +
+ ) + })} +
+
+ )} + + {/* 검토자 의견 */} + {item.response.reviewComments && ( +
+
검토자 의견
+
+ {item.response.reviewComments} +
+
+ )} +
+ ) : ( +
+ +
아직 응답하지 않았습니다
+
+ )} +
+
+
+
+ ))} +
+ ) : ( + + +
+ +
일반평가 항목이 없습니다
+
+
+
+ )} +
+ + {/* ESG 평가 탭 */} + + {submissionDetails.esgEvaluations.length > 0 ? ( +
+ {submissionDetails.esgEvaluations.map((esgEvaluation) => ( + + + + toggleEsgItem(esgEvaluation.id)} + > +
+
+ {expandedEsgItems.has(esgEvaluation.id) ? ( + + ) : ( + + )} + + {esgEvaluation.serialNumber}. {esgEvaluation.category} + +
+
+ + {esgEvaluation.evaluationItems.length}개 항목 + +
+
+
+
+ + +
+
평가 항목
+
{esgEvaluation.inspectionItem}
+
+ + + + {/* ESG 평가 세부 항목들 */} +
+ {esgEvaluation.evaluationItems.map((item) => ( +
+
+
{item.evaluationItem}
+ {item.evaluationItemDescription && ( +
+ {item.evaluationItemDescription} +
+ )} + + {item.response ? ( +
+
+ + 선택된 답변 + + + {item.response.answerOption.answerText} + + + {item.response.selectedScore}점 + +
+ {item.response.additionalComments && ( +
+
추가 의견:
+
{item.response.additionalComments}
+
+ )} +
+ ) : ( +
+ +
아직 응답하지 않았습니다
+
+ )} +
+
+ ))} +
+
+
+
+
+ ))} +
+ ) : ( + + +
+ +
ESG 평가 항목이 없습니다
+
+
+
+ )} +
+
+ + {/* 첨부파일 요약 */} + {submissionDetails.attachmentStats.totalFiles > 0 && ( + + + + + 첨부파일 요약 + + + +
+
+
전체 파일 수
+
{submissionDetails.attachmentStats.totalFiles}개
+
+
+
전체 파일 크기
+
{formatFileSize(submissionDetails.attachmentStats.totalSize)}
+
+
+
일반평가 첨부파일
+
{submissionDetails.attachmentStats.generalEvaluationFiles}개
+
+
+
ESG 평가 첨부파일
+
{submissionDetails.attachmentStats.esgEvaluationFiles}개
+
+
+
+
+ )} + + {/* 검토자 의견 */} + {submissionDetails.reviewComments && ( + + + + + 검토자 의견 + + + +
+ {submissionDetails.reviewComments} +
+
+
+ )} +
+ ) : ( + + +
+ +
제출 내용이 없습니다
+
협력업체가 아직 평가를 제출하지 않았습니다
+
+
+
+ )} +
+ + {/* 고정 푸터 */} +
+ +
+ +
+
+ ) +} -- cgit v1.2.3