"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 (
)
}