diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
| commit | 90f79a7a691943a496f67f01c1e493256070e4de (patch) | |
| tree | 37275fde3ae08c2bca384fbbc8eb378de7e39230 /lib/evaluation-submit/table/evaluation-submit-dialog.tsx | |
| parent | fbb3b7f05737f9571b04b0a8f4f15c0928de8545 (diff) | |
(대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가
Diffstat (limited to 'lib/evaluation-submit/table/evaluation-submit-dialog.tsx')
| -rw-r--r-- | lib/evaluation-submit/table/evaluation-submit-dialog.tsx | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/lib/evaluation-submit/table/evaluation-submit-dialog.tsx b/lib/evaluation-submit/table/evaluation-submit-dialog.tsx new file mode 100644 index 00000000..20ed5f30 --- /dev/null +++ b/lib/evaluation-submit/table/evaluation-submit-dialog.tsx @@ -0,0 +1,353 @@ +"use client" + +import * as React from "react" +import { + AlertTriangleIcon, + CheckCircleIcon, + SendIcon, + XCircleIcon, + FileTextIcon, + ClipboardListIcon, + LoaderIcon +} from "lucide-react" + +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { toast } from "sonner" + +// Progress 컴포넌트 (간단한 구현) +function Progress({ value, className }: { value: number; className?: string }) { + return ( + <div className={`w-full bg-gray-200 rounded-full overflow-hidden ${className}`}> + <div + className={`h-full bg-blue-600 transition-all duration-300 ${ + value === 100 ? 'bg-green-500' : value >= 50 ? 'bg-blue-500' : 'bg-yellow-500' + }`} + style={{ width: `${Math.min(100, Math.max(0, value))}%` }} + /> + </div> + ) +} + +import { + getEvaluationSubmissionCompleteness, + updateEvaluationSubmissionStatus +} from "../service" +import type { EvaluationSubmissionWithVendor } from "../service" + +interface EvaluationSubmissionDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + submission: EvaluationSubmissionWithVendor | null + onSuccess: () => void +} + +type CompletenessData = { + general: { + total: number + completed: number + percentage: number + isComplete: boolean + } + esg: { + total: number + completed: number + percentage: number + averageScore: number + isComplete: boolean + } + overall: { + isComplete: boolean + totalItems: number + completedItems: number + } +} + +export function EvaluationSubmissionDialog({ + open, + onOpenChange, + submission, + onSuccess, +}: EvaluationSubmissionDialogProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [completeness, setCompleteness] = React.useState<CompletenessData | null>(null) + + // 완성도 데이터 로딩 + React.useEffect(() => { + if (open && submission?.id) { + loadCompleteness() + } + }, [open, submission?.id]) + + const loadCompleteness = async () => { + if (!submission?.id) return + + setIsLoading(true) + try { + const data = await getEvaluationSubmissionCompleteness(submission.id) + setCompleteness(data) + } catch (error) { + console.error('Error loading completeness:', error) + toast.error('완성도 정보를 불러오는데 실패했습니다.') + } finally { + setIsLoading(false) + } + } + + // 제출하기 + const handleSubmit = async () => { + if (!submission?.id || !completeness) return + + if (!completeness.overall.isComplete) { + toast.error('모든 평가 항목을 완료해야 제출할 수 있습니다.') + return + } + + setIsSubmitting(true) + try { + await updateEvaluationSubmissionStatus(submission.id, 'submitted') + toast.success('평가가 성공적으로 제출되었습니다.') + onSuccess() + } catch (error: any) { + console.error('Error submitting evaluation:', error) + toast.error(error.message || '제출에 실패했습니다.') + } finally { + setIsSubmitting(false) + } + } + + const isKorean = submission?.vendor.countryCode === 'KR' + + if (isLoading) { + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="sm:max-w-[500px]"> + <div className="flex items-center justify-center py-8"> + <div className="text-center space-y-4"> + <LoaderIcon className="h-8 w-8 animate-spin mx-auto" /> + <p>완성도를 확인하는 중...</p> + </div> + </div> + </DialogContent> + </Dialog> + ) + } + + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="sm:max-w-[600px]"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <SendIcon className="h-5 w-5" /> + 평가 제출하기 + </DialogTitle> + <DialogDescription> + {submission?.vendor.vendorName}의 {submission?.evaluationYear}년 평가를 제출합니다. + </DialogDescription> + </DialogHeader> + + {completeness && ( + <div className="space-y-6"> + {/* 전체 완성도 카드 */} + <Card> + <CardHeader> + <CardTitle className="text-base flex items-center justify-between"> + <span>전체 완성도</span> + <Badge + variant={completeness.overall.isComplete ? "default" : "secondary"} + className={ + completeness.overall.isComplete + ? "bg-green-100 text-green-800 border-green-200" + : "" + } + > + {completeness.overall.isComplete ? "완료" : "미완료"} + </Badge> + </CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <div className="space-y-2"> + <div className="flex items-center justify-between text-sm"> + <span>전체 진행률</span> + <span className="font-medium"> + {completeness.overall.completedItems}/{completeness.overall.totalItems}개 완료 + </span> + </div> + <Progress + value={ + completeness.overall.totalItems > 0 + ? (completeness.overall.completedItems / completeness.overall.totalItems) * 100 + : 0 + } + className="h-2" + /> + <p className="text-xs text-muted-foreground"> + {completeness.overall.totalItems > 0 + ? Math.round((completeness.overall.completedItems / completeness.overall.totalItems) * 100) + : 0}% 완료 + </p> + </div> + </CardContent> + </Card> + + {/* 세부 완성도 */} + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + {/* 일반평가 */} + <Card> + <CardHeader className="pb-3"> + <CardTitle className="text-sm flex items-center gap-2"> + <FileTextIcon className="h-4 w-4" /> + 일반평가 + {completeness.general.isComplete ? ( + <CheckCircleIcon className="h-4 w-4 text-green-600" /> + ) : ( + <XCircleIcon className="h-4 w-4 text-red-600" /> + )} + </CardTitle> + </CardHeader> + <CardContent className="space-y-3"> + <div className="space-y-1"> + <div className="flex items-center justify-between text-xs"> + <span>응답 완료</span> + <span className="font-medium"> + {completeness.general.completed}/{completeness.general.total}개 + </span> + </div> + <Progress value={completeness.general.percentage} className="h-1" /> + <p className="text-xs text-muted-foreground"> + {completeness.general.percentage.toFixed(0)}% 완료 + </p> + </div> + + {!completeness.general.isComplete && ( + <p className="text-xs text-red-600"> + {completeness.general.total - completeness.general.completed}개 항목이 미완료입니다. + </p> + )} + </CardContent> + </Card> + + {/* ESG평가 */} + {isKorean ? ( + <Card> + <CardHeader className="pb-3"> + <CardTitle className="text-sm flex items-center gap-2"> + <ClipboardListIcon className="h-4 w-4" /> + ESG평가 + {completeness.esg.isComplete ? ( + <CheckCircleIcon className="h-4 w-4 text-green-600" /> + ) : ( + <XCircleIcon className="h-4 w-4 text-red-600" /> + )} + </CardTitle> + </CardHeader> + <CardContent className="space-y-3"> + <div className="space-y-1"> + <div className="flex items-center justify-between text-xs"> + <span>응답 완료</span> + <span className="font-medium"> + {completeness.esg.completed}/{completeness.esg.total}개 + </span> + </div> + <Progress value={completeness.esg.percentage} className="h-1" /> + <p className="text-xs text-muted-foreground"> + {completeness.esg.percentage.toFixed(0)}% 완료 + </p> + </div> + + {completeness.esg.completed > 0 && ( + <div className="text-xs"> + <span className="text-muted-foreground">평균 점수: </span> + <span className="font-medium text-blue-600"> + {completeness.esg.averageScore.toFixed(1)}점 + </span> + </div> + )} + + {!completeness.esg.isComplete && ( + <p className="text-xs text-red-600"> + {completeness.esg.total - completeness.esg.completed}개 항목이 미완료입니다. + </p> + )} + </CardContent> + </Card> + ) : ( + <Card> + <CardHeader className="pb-3"> + <CardTitle className="text-sm flex items-center gap-2"> + <ClipboardListIcon className="h-4 w-4" /> + ESG평가 + </CardTitle> + </CardHeader> + <CardContent> + <div className="text-center text-muted-foreground"> + <Badge variant="outline">해당없음</Badge> + <p className="text-xs mt-2">한국 업체가 아니므로 ESG 평가가 제외됩니다.</p> + </div> + </CardContent> + </Card> + )} + </div> + + {/* 제출 상태 알림 */} + {completeness.overall.isComplete ? ( + <Alert> + <CheckCircleIcon className="h-4 w-4" /> + <AlertTitle>제출 준비 완료</AlertTitle> + <AlertDescription> + 모든 평가 항목이 완료되었습니다. 제출하시겠습니까? + </AlertDescription> + </Alert> + ) : ( + <Alert variant="destructive"> + <AlertTriangleIcon className="h-4 w-4" /> + <AlertTitle>제출 불가</AlertTitle> + <AlertDescription> + 아직 완료되지 않은 평가 항목이 있습니다. 모든 항목을 완료한 후 제출해 주세요. + </AlertDescription> + </Alert> + )} + </div> + )} + + <DialogFooter> + <Button variant="outline" onClick={() => onOpenChange(false)}> + 취소 + </Button> + <Button + onClick={handleSubmit} + disabled={!completeness?.overall.isComplete || isSubmitting} + className="min-w-[100px]" + > + {isSubmitting ? ( + <> + <LoaderIcon className="mr-2 h-4 w-4 animate-spin" /> + 제출 중... + </> + ) : ( + <> + <SendIcon className="mr-2 h-4 w-4" /> + 제출하기 + </> + )} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
