"use client" import * as React from "react" import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Check, ChevronsUpDown, Loader2, X } from "lucide-react" import { toast } from "sonner" import { useSession } from "next-auth/react" import { Button } from "@/components/ui/button" import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { cn } from "@/lib/utils" import { updateEvaluationTarget, getAvailableReviewers, getDepartmentInfo, type UpdateEvaluationTargetInput, } from "../service" import { EvaluationTargetWithDepartments } from "@/db/schema" import { getMaterialTypeBadge } from "./evaluation-targets-columns" import { getStatusLabel } from "../validation" // 편집 가능한 필드들에 대한 스키마 const editEvaluationTargetSchema = z.object({ adminComment: z.string().optional(), consolidatedComment: z.string().optional(), ldClaimCount: z.number().min(0).optional(), ldClaimAmount: z.number().min(0).optional(), ldClaimCurrency: z.enum(["KRW", "USD", "EUR", "JPY"]).optional(), consensusStatus: z.boolean().nullable().optional(), orderIsApproved: z.boolean().nullable().optional(), procurementIsApproved: z.boolean().nullable().optional(), qualityIsApproved: z.boolean().nullable().optional(), designIsApproved: z.boolean().nullable().optional(), csIsApproved: z.boolean().nullable().optional(), // 담당자 정보 수정 orderReviewerEmail: z.string().optional(), procurementReviewerEmail: z.string().optional(), qualityReviewerEmail: z.string().optional(), designReviewerEmail: z.string().optional(), csReviewerEmail: z.string().optional(), }) type EditEvaluationTargetFormValues = z.infer interface EditEvaluationTargetSheetProps { open: boolean onOpenChange: (open: boolean) => void evaluationTarget: EvaluationTargetWithDepartments | null } // 권한 타입 정의 type PermissionLevel = "none" | "department" | "admin" interface UserPermissions { level: PermissionLevel editableApprovals: string[] // 편집 가능한 approval 필드들 } export function EditEvaluationTargetSheet({ open, onOpenChange, evaluationTarget, }: EditEvaluationTargetSheetProps) { const router = useRouter() const [isSubmitting, setIsSubmitting] = React.useState(false) const { data: session } = useSession() // 담당자 관련 상태 const [reviewers, setReviewers] = React.useState>([]) const [isLoadingReviewers, setIsLoadingReviewers] = React.useState(false) // 각 부서별 담당자 선택 상태 const [reviewerSearches, setReviewerSearches] = React.useState>({}) const [reviewerOpens, setReviewerOpens] = React.useState>({}) // 부서 정보 상태 const [departments, setDepartments] = React.useState>([]) // 사용자 권한 계산 const userPermissions = React.useMemo((): UserPermissions => { if (!session?.user || !evaluationTarget) { return { level: "none", editableApprovals: [] } } const userEmail = session.user.email const userRole = session.user?.roles // 평가관리자는 모든 권한 if (userRole?.some(role => role.includes('정기평가'))|| userRole?.some(role => role.toLocaleLowerCase().includes('admin'))) { return { level: "admin", editableApprovals: [ "orderIsApproved", "procurementIsApproved", "qualityIsApproved", "designIsApproved", "csIsApproved" ] } } // 부서별 담당자 권한 확인 const editableApprovals: string[] = [] if (evaluationTarget.orderReviewerEmail === userEmail) { editableApprovals.push("orderIsApproved") } if (evaluationTarget.procurementReviewerEmail === userEmail) { editableApprovals.push("procurementIsApproved") } if (evaluationTarget.qualityReviewerEmail === userEmail) { editableApprovals.push("qualityIsApproved") } if (evaluationTarget.designReviewerEmail === userEmail) { editableApprovals.push("designIsApproved") } if (evaluationTarget.csReviewerEmail === userEmail) { editableApprovals.push("csIsApproved") } return { level: editableApprovals.length > 0 ? "department" : "none", editableApprovals } }, [session, evaluationTarget]) const form = useForm({ resolver: zodResolver(editEvaluationTargetSchema), defaultValues: { adminComment: "", consolidatedComment: "", ldClaimCount: 0, ldClaimAmount: 0, ldClaimCurrency: "KRW", consensusStatus: null, orderIsApproved: null, procurementIsApproved: null, qualityIsApproved: null, designIsApproved: null, csIsApproved: null, orderReviewerEmail: "", procurementReviewerEmail: "", qualityReviewerEmail: "", designReviewerEmail: "", csReviewerEmail: "", }, }) // 부서 정보 로드 const loadDepartments = React.useCallback(async () => { try { const departmentList = await getDepartmentInfo() setDepartments(departmentList) } catch (error) { console.error("Error loading departments:", error) toast.error("부서 정보를 불러오는데 실패했습니다.") } }, []) // 담당자 목록 로드 const loadReviewers = React.useCallback(async () => { setIsLoadingReviewers(true) try { const reviewerList = await getAvailableReviewers() setReviewers(reviewerList) } catch (error) { console.error("Error loading reviewers:", error) toast.error("담당자 목록을 불러오는데 실패했습니다.") } finally { setIsLoadingReviewers(false) } }, []) // 시트가 열릴 때 데이터 로드 및 폼 초기화 React.useEffect(() => { if (open && evaluationTarget) { loadDepartments() loadReviewers() // 폼에 기존 데이터 설정 form.reset({ adminComment: evaluationTarget.adminComment || "", consolidatedComment: evaluationTarget.consolidatedComment || "", ldClaimCount: evaluationTarget.ldClaimCount || 0, ldClaimAmount: parseFloat(evaluationTarget.ldClaimAmount || "0"), ldClaimCurrency: evaluationTarget.ldClaimCurrency || "KRW", consensusStatus: evaluationTarget.consensusStatus, orderIsApproved: evaluationTarget.orderIsApproved, procurementIsApproved: evaluationTarget.procurementIsApproved, qualityIsApproved: evaluationTarget.qualityIsApproved, designIsApproved: evaluationTarget.designIsApproved, csIsApproved: evaluationTarget.csIsApproved, orderReviewerEmail: evaluationTarget.orderReviewerEmail || "", procurementReviewerEmail: evaluationTarget.procurementReviewerEmail || "", qualityReviewerEmail: evaluationTarget.qualityReviewerEmail || "", designReviewerEmail: evaluationTarget.designReviewerEmail || "", csReviewerEmail: evaluationTarget.csReviewerEmail || "", }) } }, [open, evaluationTarget, form]) // 폼 제출 async function onSubmit(data: EditEvaluationTargetFormValues) { if (!evaluationTarget) return setIsSubmitting(true) try { const input: UpdateEvaluationTargetInput = { id: evaluationTarget.id, ...data, } console.log("Updating evaluation target:", input) const result = await updateEvaluationTarget(input) if (result.success) { toast.success(result.message || "평가 대상이 성공적으로 수정되었습니다.") onOpenChange(false) router.refresh() } else { toast.error(result.error || "평가 대상 수정에 실패했습니다.") } } catch (error) { console.error("Error updating evaluation target:", error) toast.error("평가 대상 수정 중 오류가 발생했습니다.") } finally { setIsSubmitting(false) } } // 시트 닫기 핸들러 const handleOpenChange = (open: boolean) => { onOpenChange(open) if (!open) { form.reset() setReviewerSearches({}) setReviewerOpens({}) } } // 담당자 검색 필터링 const getFilteredReviewers = (search: string) => { if (!search) return reviewers return reviewers.filter(reviewer => reviewer.name.toLowerCase().includes(search.toLowerCase()) || reviewer.email.toLowerCase().includes(search.toLowerCase()) ) } // 필드 편집 권한 확인 const canEditField = (fieldName: string): boolean => { if (userPermissions.level === "admin") return true if (userPermissions.level === "none") return false // 부서 담당자는 자신의 approval만 편집 가능 return userPermissions.editableApprovals.includes(fieldName) } // 관리자 전용 필드 확인 const canEditAdminFields = (): boolean => { return userPermissions.level === "admin" } if (!evaluationTarget) { return null } // 권한이 없는 경우 if (userPermissions.level === "none") { return ( 평가 대상 수정 권한이 없어 수정할 수 없습니다.

이 평가 대상을 수정할 권한이 없습니다.

) } return ( 평가 대상 수정 평가 대상 정보를 수정합니다. {userPermissions.level === "department" && (
부서 담당자 권한: 해당 부서의 평가 항목만 수정 가능합니다.
)} {userPermissions.level === "admin" && (
평가관리자 권한: 모든 항목을 수정할 수 있습니다.
)}
{/* 스크롤 가능한 컨텐츠 영역 */}
{/* 기본 정보 (읽기 전용) */} 기본 정보
평가년도: {evaluationTarget.evaluationYear}
구분: {evaluationTarget.division === "PLANT" ? "해양" : "조선"}
벤더 코드: {evaluationTarget.vendorCode}
벤더명: {evaluationTarget.vendorName}
자재구분: {getMaterialTypeBadge(evaluationTarget.materialType)}
상태: {getStatusLabel(evaluationTarget.status)}
{/* L/D 클레임 정보 (관리자만) */} {canEditAdminFields() && ( L/D 클레임 정보
( 클레임 건수 field.onChange(parseInt(e.target.value) || 0)} /> )} /> ( 클레임 금액 field.onChange(parseFloat(e.target.value) || 0)} /> )} /> ( 통화단위 )} />
)} {/* 평가 상태 */} 평가 상태 {/* 의견 일치 여부 (관리자만) */} {canEditAdminFields() && ( ( 의견 일치 여부 )} /> )} {/* 각 부서별 평가 */}
{[ { key: "orderIsApproved", label: "발주 부서 평가", email: evaluationTarget.orderReviewerEmail }, { key: "procurementIsApproved", label: "조달 부서 평가", email: evaluationTarget.procurementReviewerEmail }, { key: "qualityIsApproved", label: "품질 부서 평가", email: evaluationTarget.qualityReviewerEmail }, { key: "designIsApproved", label: "설계 부서 평가", email: evaluationTarget.designReviewerEmail }, { key: "csIsApproved", label: "CS 부서 평가", email: evaluationTarget.csReviewerEmail }, ].map(({ key, label, email }) => ( ( {label} {email && ( 담당자: {email} )} {!canEditField(key) && (

편집 권한이 없습니다.

)}
)} /> ))}
{/* 의견 */} 의견 {/* 관리자 의견 (관리자만) */} {canEditAdminFields() && ( ( 관리자 의견