"use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { CalendarIcon, Loader, Upload, X, FileText } from "lucide-react" import { format } from "date-fns" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { z } from "zod" import { getInvestigationAttachments, deleteInvestigationAttachment } from "../service" import { downloadFile } from "@/lib/file-download" import { Download } from "lucide-react" // Validation schema for editing investigation const editInvestigationSchema = z.object({ confirmedAt: z.union([ z.date(), z.string().transform((str) => str ? new Date(str) : undefined) ]).optional(), evaluationResult: z.enum(["APPROVED", "SUPPLEMENT", "REJECTED"]).optional(), investigationNotes: z.string().max(1000, "구매 의견은 1000자 이내로 입력해주세요.").optional(), attachments: z.array(z.instanceof(File)).optional(), }) type EditInvestigationSchema = z.infer interface EditInvestigationDialogProps { isOpen: boolean onClose: () => void investigation: { id: number confirmedAt?: Date | null evaluationResult?: string | null investigationNotes?: string | null } | null onSubmit: (data: EditInvestigationSchema) => Promise } export function EditInvestigationDialog({ isOpen, onClose, investigation, onSubmit, }: EditInvestigationDialogProps) { const [isPending, startTransition] = React.useTransition() const [selectedFiles, setSelectedFiles] = React.useState([]) const [existingAttachments, setExistingAttachments] = React.useState([]) const [loadingAttachments, setLoadingAttachments] = React.useState(false) const fileInputRef = React.useRef(null) const form = useForm({ resolver: zodResolver(editInvestigationSchema), defaultValues: { confirmedAt: investigation?.confirmedAt || undefined, evaluationResult: investigation?.evaluationResult as "APPROVED" | "SUPPLEMENT" | "REJECTED" | undefined, investigationNotes: investigation?.investigationNotes || "", attachments: [], }, }) // Reset form when investigation changes React.useEffect(() => { if (investigation) { form.reset({ confirmedAt: investigation.confirmedAt || undefined, evaluationResult: investigation.evaluationResult as "APPROVED" | "SUPPLEMENT" | "REJECTED" | undefined, investigationNotes: investigation.investigationNotes || "", attachments: [], }) setSelectedFiles([]) // 기존 첨부파일 로드 loadExistingAttachments(investigation.id) } }, [investigation, form]) // 기존 첨부파일 로드 함수 const loadExistingAttachments = async (investigationId: number) => { setLoadingAttachments(true) try { const result = await getInvestigationAttachments(investigationId) if (result.success) { setExistingAttachments(result.attachments || []) } else { toast.error("첨부파일 목록을 불러오는데 실패했습니다.") } } catch (error) { console.error("첨부파일 로드 실패:", error) toast.error("첨부파일 목록을 불러오는 중 오류가 발생했습니다.") } finally { setLoadingAttachments(false) } } // 첨부파일 삭제 함수 const handleDeleteAttachment = async (attachmentId: number) => { if (!investigation) return try { const result = await deleteInvestigationAttachment(attachmentId) if (result.success) { toast.success("첨부파일이 삭제되었습니다.") // 목록 새로고침 loadExistingAttachments(investigation.id) } else { toast.error(result.error || "첨부파일 삭제에 실패했습니다.") } } catch (error) { console.error("첨부파일 삭제 오류:", error) toast.error("첨부파일 삭제 중 오류가 발생했습니다.") } } // 첨부파일 다운로드 함수 const handleDownloadAttachment = async (attachment: any) => { if (!attachment.filePath || !attachment.fileName) { toast.error("첨부파일 정보가 올바르지 않습니다.") return } try { await downloadFile(attachment.filePath, attachment.fileName, { showToast: true, action: 'download' }) } catch (error) { console.error("첨부파일 다운로드 오류:", error) toast.error("첨부파일 다운로드 중 오류가 발생했습니다.") } } // 파일 선택 핸들러 const handleFileSelect = (event: React.ChangeEvent) => { const files = Array.from(event.target.files || []) if (files.length > 0) { const newFiles = [...selectedFiles, ...files] setSelectedFiles(newFiles) form.setValue('attachments', newFiles, { shouldValidate: true }) } } // 파일 제거 핸들러 const removeFile = (index: number) => { const updatedFiles = selectedFiles.filter((_, i) => i !== index) setSelectedFiles(updatedFiles) form.setValue('attachments', updatedFiles, { shouldValidate: true }) } // 파일 크기 포맷팅 const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } const handleSubmit = async (values: EditInvestigationSchema) => { startTransition(async () => { try { // 선택된 파일들을 values에 포함 const submitData = { ...values, attachments: selectedFiles, } await onSubmit(submitData) toast.success("실사 정보가 업데이트되었습니다!") onClose() } catch (error) { console.error("실사 정보 업데이트 오류:", error) toast.error("실사 정보 업데이트 중 오류가 발생했습니다.") } }) } return ( 구매자체평가 실사 결과 수정 구매자체평가 실사 결과를 수정합니다.
{/* 실사 확정일 */} ( 실사 계획 확정일 )} /> {/* 평가 결과 */} ( 평가 결과 )} /> {/* 구매 의견 */} ( 구매 의견