"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 { SaveIcon, CheckIcon, XIcon, BarChart3Icon, TrendingUpIcon } from "lucide-react" import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { toast } from "sonner" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion" import { getEsgEvaluationFormData, saveEsgEvaluationResponse, recalculateEvaluationProgress, EsgEvaluationFormData } from "../service" import { EvaluationSubmissionWithVendor } from "../service" interface EsgEvaluationFormSheetProps { open: boolean onOpenChange: (open: boolean) => void submission: EvaluationSubmissionWithVendor | null onSuccess: () => void } // 폼 스키마 정의 const formSchema = z.object({ responses: z.array(z.object({ itemId: z.number(), selectedOptionId: z.number().optional(), selectedScore: z.number().default(0), additionalComments: z.string().optional(), })) }) type FormData = z.infer export function EsgEvaluationFormSheet({ open, onOpenChange, submission, onSuccess, }: EsgEvaluationFormSheetProps) { const [isLoading, setIsLoading] = React.useState(false) const [isSaving, setIsSaving] = React.useState(false) const [formData, setFormData] = React.useState(null) const [currentScores, setCurrentScores] = React.useState>({}) const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { responses: [] } }) // 데이터 로딩 React.useEffect(() => { if (open && submission?.id) { loadFormData() } }, [open, submission?.id]) const loadFormData = async () => { if (!submission?.id) return setIsLoading(true) try { const data = await getEsgEvaluationFormData(submission.id) setFormData(data) // 폼 초기값 설정 const responses: any[] = [] const scores: Record = {} data.evaluations.forEach(evaluation => { evaluation.items.forEach(item => { responses.push({ itemId: item.item.id, selectedOptionId: item.response?.esgAnswerOptionId, selectedScore: item.response?.selectedScore || 0, additionalComments: item.response?.additionalComments || '', }) if (item.response?.selectedScore) { scores[item.item.id] = item.response.selectedScore } }) }) setCurrentScores(scores) form.reset({ responses }) } catch (error) { console.error('Error loading ESG form data:', error) toast.error('ESG 평가 데이터를 불러오는데 실패했습니다.') } finally { setIsLoading(false) } } // 개별 응답 저장 const handleSaveResponse = async (itemId: number, optionId: number, score: number) => { if (!submission?.id) return try { const formResponse = form.getValues('responses').find(r => r.itemId === itemId) await saveEsgEvaluationResponse({ submissionId: submission.id, esgEvaluationItemId: itemId, esgAnswerOptionId: optionId, selectedScore: score, additionalComments: formResponse?.additionalComments || '', }) // 현재 점수 업데이트 setCurrentScores(prev => ({ ...prev, [itemId]: score })) // 평균 점수 재계산 await recalculateEvaluationProgress(submission.id) toast.success('응답이 저장되었습니다.') } catch (error) { console.error('Error saving ESG response:', error) toast.error('응답 저장에 실패했습니다.') } } // 선택 변경 핸들러 const handleOptionChange = (itemId: number, optionId: string, score: number) => { const responseIndex = form.getValues('responses').findIndex(r => r.itemId === itemId) if (responseIndex >= 0) { form.setValue(`responses.${responseIndex}.selectedOptionId`, parseInt(optionId)) form.setValue(`responses.${responseIndex}.selectedScore`, score) } // 자동 저장 handleSaveResponse(itemId, parseInt(optionId), score) } // 전체 저장 const onSubmit = async (data: FormData) => { if (!submission?.id || !formData) return setIsSaving(true) try { // 모든 응답을 순차적으로 저장 for (const response of data.responses) { if (response.selectedOptionId && response.selectedScore > 0) { await saveEsgEvaluationResponse({ submissionId: submission.id, esgEvaluationItemId: response.itemId, esgAnswerOptionId: response.selectedOptionId, selectedScore: response.selectedScore, additionalComments: response.additionalComments || '', }) } } // 평균 점수 재계산 await recalculateEvaluationProgress(submission.id) toast.success('모든 ESG 평가가 저장되었습니다.') onSuccess() } catch (error) { console.error('Error saving all ESG responses:', error) toast.error('ESG 평가 저장에 실패했습니다.') } finally { setIsSaving(false) } } // 진행률 및 점수 계산 const getProgress = () => { if (!formData) return { completed: 0, total: 0, percentage: 0, averageScore: 0, maxAverageScore: 0 } let total = 0 let completed = 0 let totalScore = 0 let maxTotalScore = 0 formData.evaluations.forEach(evaluation => { evaluation.items.forEach(item => { total++ if (currentScores[item.item.id] > 0) { completed++ totalScore += currentScores[item.item.id] } // 최대 점수 계산 const maxOptionScore = Math.max(...item.answerOptions.map(opt => parseFloat(opt.score.toString()))) maxTotalScore += maxOptionScore }) }) const percentage = total > 0 ? Math.round((completed / total) * 100) : 0 const averageScore = completed > 0 ? totalScore / completed : 0 const maxAverageScore = total > 0 ? maxTotalScore / total : 0 return { completed, total, percentage, averageScore, maxAverageScore } } const progress = getProgress() if (isLoading) { return (

ESG 평가 데이터를 불러오는 중...

) } return ( ESG 평가 작성 {formData?.submission.vendorName}의 ESG 평가를 작성해주세요. {formData && ( <> {/* 진행률 및 점수 표시 */}
진행률
{progress.completed}/{progress.total}

{progress.percentage}% 완료

평균 점수
{progress.averageScore.toFixed(1)} / {progress.maxAverageScore.toFixed(1)}
0 ? (progress.averageScore / progress.maxAverageScore) * 100 : 0}%` }} />

{progress.completed > 0 ? `${progress.completed}개 항목 평균` : '응답 없음'}

`evaluation-${i}`)}> {formData.evaluations.map((evaluation, evalIndex) => (
{evaluation.evaluation.serialNumber}
{evaluation.evaluation.category}
{evaluation.evaluation.inspectionItem}
{evaluation.items.filter(item => currentScores[item.item.id] > 0 ).length}/{evaluation.items.length}
{evaluation.items.map((item, itemIndex) => { const responseIndex = form.getValues('responses').findIndex( r => r.itemId === item.item.id ) return ( {item.item.evaluationItem} {currentScores[item.item.id] > 0 && ( {currentScores[item.item.id]}점 )} {item.item.evaluationItemDescription && (

{item.item.evaluationItemDescription}

)}
{/* 답변 옵션들 */} { const option = item.answerOptions.find( opt => opt.id === parseInt(value) ) if (option) { handleOptionChange( item.item.id, value, parseFloat(option.score.toString()) ) } }} >
{item.answerOptions.map((option) => (
))}
{/* 추가 의견 */} {responseIndex >= 0 && ( ( 추가 의견 (선택사항)