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/table/periodic-evaluation-action-dialogs.tsx | |
| parent | fbb3b7f05737f9571b04b0a8f4f15c0928de8545 (diff) | |
(대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가
Diffstat (limited to 'lib/evaluation/table/periodic-evaluation-action-dialogs.tsx')
| -rw-r--r-- | lib/evaluation/table/periodic-evaluation-action-dialogs.tsx | 231 |
1 files changed, 196 insertions, 35 deletions
diff --git a/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx index 30ff9535..fc07aea1 100644 --- a/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx +++ b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx @@ -14,11 +14,42 @@ 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 { FileText, Users, Calendar, Send, Mail, Building } from "lucide-react" import { toast } from "sonner" import { PeriodicEvaluationView } from "@/db/schema" -import { checkExistingSubmissions, requestDocumentsFromVendors } from "../service" +import { + checkExistingSubmissions, + requestDocumentsFromVendors, + getReviewersForEvaluations, + createReviewerEvaluationsRequest +} from "../service" +import { DEPARTMENT_CODE_LABELS } from "@/types/evaluation" +// ================================================================ +// 부서 코드 매핑 +// ================================================================ + + +const getDepartmentLabel = (code: string): string => { + return DEPARTMENT_CODE_LABELS[code as keyof typeof DEPARTMENT_CODE_LABELS] || code +} + +// ================================================================ +// 타입 정의 +// ================================================================ +interface ReviewerInfo { + id: number + name: string + email: string + deptName: string | null + departmentCode: string + evaluationTargetId: number + evaluationTargetReviewerId: number +} + +interface EvaluationWithReviewers extends PeriodicEvaluationView { + reviewers: ReviewerInfo[] +} // ================================================================ // 2. 협력업체 자료 요청 다이얼로그 @@ -259,10 +290,8 @@ export function RequestDocumentsDialog({ ) } - - // ================================================================ -// 3. 평가자 평가 요청 다이얼로그 +// 3. 평가자 평가 요청 다이얼로그 (업데이트됨) // ================================================================ interface RequestEvaluationDialogProps { open: boolean @@ -278,10 +307,61 @@ export function RequestEvaluationDialog({ onSuccess, }: RequestEvaluationDialogProps) { const [isLoading, setIsLoading] = React.useState(false) + const [isLoadingReviewers, setIsLoadingReviewers] = React.useState(false) const [message, setMessage] = React.useState("") + const [evaluationsWithReviewers, setEvaluationsWithReviewers] = React.useState<EvaluationWithReviewers[]>([]) // 제출완료 상태인 평가들만 필터링 - const submittedEvaluations = evaluations.filter(e => e.status === "SUBMITTED") + const submittedEvaluations = evaluations.filter(e => + e.status === "SUBMITTED" || e.status === "PENDING_SUBMISSION" + ) + + // 리뷰어 정보 로딩 + React.useEffect(() => { + if (!open || submittedEvaluations.length === 0) { + setEvaluationsWithReviewers([]) + return + } + + const loadReviewers = async () => { + setIsLoadingReviewers(true) + try { + const evaluationTargetIds = submittedEvaluations + .map(e => e.evaluationTargetId) + .filter(id => id !== null) + + if (evaluationTargetIds.length === 0) { + setEvaluationsWithReviewers([]) + return + } + + const reviewersData = await getReviewersForEvaluations(evaluationTargetIds) + + // 평가별로 리뷰어 그룹핑 + const evaluationsWithReviewersData = submittedEvaluations.map(evaluation => ({ + ...evaluation, + reviewers: reviewersData.filter(reviewer => + reviewer.evaluationTargetId === evaluation.evaluationTargetId + ) + })) + + setEvaluationsWithReviewers(evaluationsWithReviewersData) + } catch (error) { + console.error('Error loading reviewers:', error) + toast.error("평가자 정보를 불러오는데 실패했습니다.") + setEvaluationsWithReviewers([]) + } finally { + setIsLoadingReviewers(false) + } + } + + loadReviewers() + }, [open, submittedEvaluations.length]) + + // 총 리뷰어 수 계산 + const totalReviewers = evaluationsWithReviewers.reduce((sum, evaluation) => + sum + evaluation.reviewers.length, 0 + ) const handleSubmit = async () => { if (!message.trim()) { @@ -289,13 +369,34 @@ export function RequestEvaluationDialog({ return } + if (evaluationsWithReviewers.length === 0) { + toast.error("평가 요청할 대상이 없습니다.") + return + } + setIsLoading(true) try { - // TODO: 평가자들에게 평가 요청 API 호출 - toast.success(`${submittedEvaluations.length}개 평가에 대한 평가 요청이 발송되었습니다.`) - onSuccess() - onOpenChange(false) - setMessage("") + // 리뷰어 평가 레코드 생성 데이터 준비 + const reviewerEvaluationsData = evaluationsWithReviewers.flatMap(evaluation => + evaluation.reviewers.map(reviewer => ({ + periodicEvaluationId: evaluation.id, + evaluationTargetId: evaluation.evaluationTargetId, // 추가됨 + evaluationTargetReviewerId: reviewer.evaluationTargetReviewerId, + message: message.trim() + })) + ) + + // 서버 액션 호출 + const result = await createReviewerEvaluationsRequest(reviewerEvaluationsData) + + if (result.success) { + toast.success(result.message) + onSuccess() + onOpenChange(false) + setMessage("") + } else { + toast.error(result.message) + } } catch (error) { console.error('Error requesting evaluation:', error) toast.error("평가 요청 발송 중 오류가 발생했습니다.") @@ -306,7 +407,7 @@ export function RequestEvaluationDialog({ return ( <Dialog open={open} onOpenChange={onOpenChange}> - <DialogContent className="sm:max-w-2xl"> + <DialogContent className="sm:max-w-4xl max-h-[80vh] overflow-y-auto"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <Users className="size-4" /> @@ -318,28 +419,84 @@ export function RequestEvaluationDialog({ </DialogHeader> <div className="space-y-4"> - {/* 대상 평가 목록 */} - <Card> - <CardHeader className="pb-3"> - <CardTitle className="text-sm"> - 평가 대상 ({submittedEvaluations.length}개 평가) - </CardTitle> - </CardHeader> - <CardContent className="space-y-2 max-h-32 overflow-y-auto"> - {submittedEvaluations.map((evaluation) => ( - <div - key={evaluation.id} - className="flex items-center justify-between text-sm" - > - <span className="font-medium">{evaluation.vendorName}</span> - <div className="flex gap-2"> - <Badge variant="outline">{evaluation.evaluationPeriod}</Badge> - <Badge variant="secondary">제출완료</Badge> + {isLoadingReviewers ? ( + <div className="flex items-center justify-center py-8"> + <div className="text-sm text-muted-foreground">평가자 정보를 불러오고 있습니다...</div> + </div> + ) : ( + <> + {/* 평가별 리뷰어 목록 */} + {evaluationsWithReviewers.length > 0 ? ( + <div className="space-y-4"> + <div className="text-sm font-medium text-green-600"> + 총 {evaluationsWithReviewers.length}개 평가, {totalReviewers}명의 평가자 </div> + + {evaluationsWithReviewers.map((evaluation) => ( + <Card key={evaluation.id}> + <CardHeader className="pb-3"> + <CardTitle className="text-sm flex items-center justify-between"> + <span>{evaluation.vendorName}</span> + <div className="flex gap-2"> + <Badge variant="outline">{evaluation.vendorCode}</Badge> + <Badge variant={evaluation.submissionDate ? "default" : "secondary"}> + {evaluation.submissionDate ? "자료 제출완료" : "자료 미제출"} + </Badge> + </div> + </CardTitle> + </CardHeader> + <CardContent> + {evaluation.reviewers.length > 0 ? ( + <div className="space-y-2"> + <div className="text-xs text-muted-foreground mb-2"> + 평가자 {evaluation.reviewers.length}명 + </div> + <div className="grid grid-cols-1 md:grid-cols-2 gap-2"> + {evaluation.reviewers.map((reviewer) => ( + <div + key={reviewer.evaluationTargetReviewerId} + className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg" + > + <div className="flex-1"> + <div className="font-medium text-sm">{reviewer.name}</div> + <div className="flex items-center gap-2 text-xs text-muted-foreground"> + <Mail className="size-3" /> + {reviewer.email} + </div> + {reviewer.deptName && ( + <div className="flex items-center gap-2 text-xs text-muted-foreground"> + <Building className="size-3" /> + {reviewer.deptName} + </div> + )} + </div> + <Badge variant="outline" className="text-xs"> + {getDepartmentLabel(reviewer.departmentCode)} + </Badge> + </div> + ))} + </div> + </div> + ) : ( + <div className="text-sm text-muted-foreground text-center py-4"> + 지정된 평가자가 없습니다. + </div> + )} + </CardContent> + </Card> + ))} </div> - ))} - </CardContent> - </Card> + ) : ( + <Card> + <CardContent className="pt-6"> + <div className="text-center text-sm text-muted-foreground"> + 평가 요청할 대상이 없습니다. + </div> + </CardContent> + </Card> + )} + </> + )} {/* 요청 메시지 */} <div className="space-y-2"> @@ -350,6 +507,7 @@ export function RequestEvaluationDialog({ value={message} onChange={(e) => setMessage(e.target.value)} rows={4} + disabled={isLoadingReviewers} /> </div> </div> @@ -358,13 +516,16 @@ export function RequestEvaluationDialog({ <Button variant="outline" onClick={() => onOpenChange(false)} - disabled={isLoading} + disabled={isLoading || isLoadingReviewers} > 취소 </Button> - <Button onClick={handleSubmit} disabled={isLoading}> + <Button + onClick={handleSubmit} + disabled={isLoading || isLoadingReviewers || totalReviewers === 0} + > <Send className="size-4 mr-2" /> - {isLoading ? "발송 중..." : `${submittedEvaluations.length}개 평가 요청`} + {isLoading ? "발송 중..." : `${totalReviewers}명에게 평가 요청`} </Button> </DialogFooter> </DialogContent> |
