"use client" import React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Edit, FileText, Loader2, AlertTriangle, Trash2, CheckCircle, Clock } from "lucide-react" import { toast } from "sonner" import { updateRevisionAction, deleteRevisionAction } from "@/lib/vendor-document-list/enhanced-document-service" // ✅ 서버 액션 import /* ------------------------------------------------------------------------------------------------- * Schema & Types * -----------------------------------------------------------------------------------------------*/ interface RevisionInfo { id: number issueStageId: number revision: string uploaderType: string uploaderId: number | null uploaderName: string | null comment: string | null usage: string | null usageType: string | null revisionStatus: string submittedDate: string | null approvedDate: string | null uploadedAt: string | null reviewStartDate: string | null rejectedDate: string | null reviewerId: number | null reviewerName: string | null reviewComments: string | null createdAt: Date updatedAt: Date stageName?: string attachments: AttachmentInfo[] } interface AttachmentInfo { id: number revisionId: number fileName: string filePath: string dolceFilePath: string | null fileSize: number | null fileType: string | null createdAt: Date updatedAt: Date } // drawingKind에 따른 동적 스키마 생성 (수정용) const createEditRevisionSchema = (drawingKind: string) => { const baseSchema = { usage: z.string().min(1, "Please select a usage"), revision: z.string().min(1, "Please enter a revision").max(50, "Revision must be 50 characters or less"), // ✅ revision 필드 추가 comment: z.string().optional(), } // B3인 경우에만 usageType 필드 추가 if (drawingKind === 'B3') { return z.object({ ...baseSchema, usageType: z.string().min(1, "Please select a usage type"), }) } else { return z.object({ ...baseSchema, usageType: z.string().optional(), }) } } // drawingKind에 따른 용도 옵션 const getUsageOptions = (drawingKind: string) => { switch (drawingKind) { case 'B3': return [ { value: "Approval", label: "Approval" }, { value: "Working", label: "Working" }, { value: "Comments", label: "Comments" }, ] case 'B4': return [ { value: "Pre", label: "Pre" }, { value: "Working", label: "Working" }, ] case 'B5': return [ { value: "Pre", label: "Pre" }, { value: "Working", label: "Working" }, ] default: return [ { value: "Pre", label: "Pre" }, { value: "Working", label: "Working" }, ] } } // B3 전용 용도 타입 옵션 const getUsageTypeOptions = (usage: string) => { switch (usage) { case 'Approval': return [ { value: "Full", label: "Full" }, { value: "Partial", label: "Partial" }, ] case 'Working': return [ { value: "Full", label: "Full" }, { value: "Partial", label: "Partial" }, ] case 'Comments': return [ { value: "Comments", label: "Comments" }, ] default: return [] } } // ✅ 리비전 가이드 생성 (NewRevisionDialog와 동일) const getRevisionGuide = () => { return "Enter in R01, R02, R03... format" } interface EditRevisionDialogProps { open: boolean onOpenChange: (open: boolean) => void revision: RevisionInfo | null drawingKind?: string onSuccess: (action: 'update' | 'delete', result?: any) => void } /* ------------------------------------------------------------------------------------------------- * Revision Info Display Component * -----------------------------------------------------------------------------------------------*/ function RevisionInfoDisplay({ revision }: { revision: RevisionInfo }) { const canEdit = React.useMemo(() => { if (!revision.attachments || revision.attachments.length === 0) { return true } return revision.attachments.every(attachment => !attachment.dolceFilePath || attachment.dolceFilePath.trim() === '' ) }, [revision.attachments]) const processedCount = revision.attachments?.filter(attachment => attachment.dolceFilePath && attachment.dolceFilePath.trim() !== '' ).length || 0 const getStatusColor = (status: string) => { switch (status) { case 'APPROVED': return 'bg-green-100 text-green-800' case 'UPLOADED': return 'bg-blue-100 text-blue-800' case 'REJECTED': return 'bg-red-100 text-red-800' default: return 'bg-gray-100 text-gray-800' } } return (
{revision.revision} {revision.revisionStatus} {!canEdit && ( Partially Processed )}
{revision.attachments?.length || 0} file(s) {processedCount > 0 && ( ({processedCount} processed) )}
Uploader: {revision.uploaderName || '-'}
Upload Date: {revision.uploadedAt ? new Date(revision.uploadedAt).toLocaleDateString() : '-' }
{revision.comment && (
Current Comment:

{revision.comment}

)} {!canEdit && (
Some files have been processed. Editing is limited.
)}
) } /* ------------------------------------------------------------------------------------------------- * Main Dialog Component * -----------------------------------------------------------------------------------------------*/ export function EditRevisionDialog({ open, onOpenChange, revision, drawingKind = 'B4', onSuccess }: EditRevisionDialogProps) { const [isLoading, setIsLoading] = React.useState(false) const [isDeleting, setIsDeleting] = React.useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false) // drawingKind에 따른 동적 스키마 및 옵션 생성 const editRevisionSchema = React.useMemo(() => createEditRevisionSchema(drawingKind), [drawingKind]) const usageOptions = React.useMemo(() => getUsageOptions(drawingKind), [drawingKind]) const showUsageType = drawingKind === 'B3' type EditRevisionSchema = z.infer const form = useForm({ resolver: zodResolver(editRevisionSchema), defaultValues: { usage: "", revision: "", // ✅ revision 기본값 추가 comment: "", usageType: showUsageType ? "" : undefined, }, }) const watchedUsage = form.watch("usage") // 용도 선택에 따른 용도 타입 옵션 업데이트 const usageTypeOptions = React.useMemo(() => { if (drawingKind === 'B3' && watchedUsage) { return getUsageTypeOptions(watchedUsage) } return [] }, [drawingKind, watchedUsage]) // ✅ 리비전 가이드 텍스트 const revisionGuide = React.useMemo(() => { return getRevisionGuide() }, []) // revision이 변경될 때 폼 데이터 초기화 React.useEffect(() => { if (revision) { form.reset({ usage: revision.usage || "", revision: revision.revision || "", // ✅ revision 값 설정 comment: revision.comment || "", usageType: showUsageType ? (revision.usageType || "") : undefined, }) } }, [revision, showUsageType, form]) // 용도 변경 시 용도 타입 초기화 또는 자동 설정 (NewRevisionDialog와 동일한 로직) React.useEffect(() => { if (showUsageType && watchedUsage) { if (watchedUsage === "Comments") { form.setValue("usageType", "Comments") } else { // Comments가 아닌 경우, 초기 로드가 아니라면 초기화 const currentValue = form.getValues("usageType") if (revision && watchedUsage !== revision.usage) { form.setValue("usageType", "") } } } }, [watchedUsage, showUsageType, form, revision]) // 수정 가능 여부 확인 const canEdit = React.useMemo(() => { if (!revision?.attachments || revision.attachments.length === 0) { return true } return revision.attachments.every(attachment => !attachment.dolceFilePath || attachment.dolceFilePath.trim() === '' ) }, [revision?.attachments]) // 폼 변경 체크 const hasChanges = React.useMemo(() => { if (!revision) return false const currentValues = form.getValues() return ( currentValues.comment !== (revision.comment || '') || currentValues.usage !== (revision.usage || '') || currentValues.revision !== (revision.revision || '') || // ✅ revision 변경 체크 추가 (showUsageType && currentValues.usageType !== (revision.usageType || '')) ) }, [revision, form, showUsageType]) const handleDialogClose = () => { if (!isLoading && !isDeleting) { setShowDeleteConfirm(false) form.reset() onOpenChange(false) } } const onSubmit = async (data: EditRevisionSchema) => { if (!revision || !canEdit) { toast.error("Cannot edit this revision") return } setIsLoading(true) try { // ✅ 서버 액션 호출 - revision 필드 추가 const result = await updateRevisionAction({ revisionId: revision.id, revision: data.revision.trim(), // ✅ revision 추가 comment: data.comment?.trim() || null, usage: data.usage.trim(), usageType: showUsageType && 'usageType' in data ? data.usageType?.trim() || null : null, }) if (!result.success) { throw new Error(result.error || 'Failed to update revision') } toast.success( result.message || `Revision ${data.revision} updated successfully` // ✅ 새 revision 값 사용 ) setTimeout(() => { handleDialogClose() onSuccess('update', result) }, 1000) } catch (error) { console.error('❌ Update error:', error) toast.error(error instanceof Error ? error.message : "An error occurred during update") } finally { setIsLoading(false) } } const handleDelete = async () => { if (!revision || !canEdit) { toast.error("Cannot delete this revision") return } setIsDeleting(true) try { // ✅ 서버 액션 호출 const result = await deleteRevisionAction({ revisionId: revision.id, }) if (!result.success) { throw new Error(result.error || 'Failed to delete revision') } toast.success( result.message || `Revision ${revision.revision} deleted successfully` ) setTimeout(() => { handleDialogClose() onSuccess('delete', result) }, 1000) } catch (error) { console.error('❌ Delete error:', error) toast.error(error instanceof Error ? error.message : "An error occurred during deletion") } finally { setIsDeleting(false) } } if (!revision) return null return ( {/* 고정 헤더 */} Edit Revision Modify revision details and metadata {!showDeleteConfirm ? (
{/* 스크롤 가능한 중간 영역 */}
{/* 리비전 정보 표시 */} {/* ✅ 리비전 필드 추가 */} ( Revision
{revisionGuide}
)} /> {/* 용도 선택 */} ( Usage )} /> {/* 용도 타입 선택 (B3만) */} {showUsageType && watchedUsage && ( ( Usage Type {watchedUsage === "Comments" && (
Automatically set to "Comments" for this usage
)}
)} /> )} {/* 코멘트 */} ( Comment