"use client" import { useState, useRef } from "react" import { useSession } from "next-auth/react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Alert, AlertDescription } from "@/components/ui/alert" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Upload, FileText, File, Trash2, Download, AlertCircle, Paperclip, FileCheck, Calculator, Wrench, X, CheckCircle } from "lucide-react" import { formatBytes } from "@/lib/utils" import { cn } from "@/lib/utils" import { toast } from "sonner" import { deleteVendorResponseAttachment } from "../../service" interface FileWithType extends File { attachmentType?: "구매" | "설계" description?: string } interface AttachmentsUploadProps { attachments: FileWithType[] onAttachmentsChange: (files: FileWithType[]) => void existingAttachments?: any[] onExistingAttachmentsChange?: (files: any[]) => void responseId?: number userId?: number isSubmitted?: boolean } const acceptedFileTypes = { documents: ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx", images: ".jpg,.jpeg,.png,.gif,.bmp", compressed: ".zip,.rar,.7z", all: ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.bmp,.zip,.rar,.7z" } export default function AttachmentsUpload({ attachments, onAttachmentsChange, existingAttachments = [], onExistingAttachmentsChange, responseId, userId, isSubmitted = false }: AttachmentsUploadProps) { const purchaseInputRef = useRef(null) const designInputRef = useRef(null) const [purchaseDragActive, setPurchaseDragActive] = useState(false) const [designDragActive, setDesignDragActive] = useState(false) const [uploadErrors, setUploadErrors] = useState([]) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [fileToDelete, setFileToDelete] = useState<{file: any, isExisting: boolean, index: number} | null>(null) // 파일 유효성 검사 const validateFile = (file: File): string | null => { const maxSize = 1024 * 1024 * 1024 // 10MB const allowedExtensions = acceptedFileTypes.all.split(',') const fileExtension = `.${file.name.split('.').pop()?.toLowerCase()}` if (file.size > maxSize) { return `${file.name}: 파일 크기가 1GB를 초과합니다` } if (!allowedExtensions.includes(fileExtension)) { return `${file.name}: 허용되지 않은 파일 형식입니다` } return null } // 파일 추가 const handleFileAdd = (files: FileList | null, type: "구매" | "설계") => { if (!files) return const newFiles: FileWithType[] = [] const errors: string[] = [] Array.from(files).forEach(file => { const error = validateFile(file) if (error) { errors.push(error) } else { const fileWithType = Object.assign(file, { attachmentType: type, description: "" }) // 디버그 로그 추가 console.log(`파일 추가됨: ${file.name}, 타입: ${type}`) newFiles.push(fileWithType) } }) if (errors.length > 0) { setUploadErrors(errors) setTimeout(() => setUploadErrors([]), 5000) } if (newFiles.length > 0) { const updatedFiles = [...attachments, ...newFiles] onAttachmentsChange(updatedFiles) // 추가된 파일들의 타입 확인 로그 console.log('업데이트된 파일 목록:', updatedFiles.map(f => ({ name: f.name, attachmentType: f.attachmentType }))) // 각 파일의 타입이 제대로 설정되었는지 검증 newFiles.forEach(file => { console.log(`새 파일 타입 검증: ${file.name} = ${file.attachmentType}`) }) } } // 구매 드래그 앤 드롭 핸들러 const handlePurchaseDrag = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (e.type === "dragenter" || e.type === "dragover") { setPurchaseDragActive(true) } else if (e.type === "dragleave") { setPurchaseDragActive(false) } } const handlePurchaseDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setPurchaseDragActive(false) if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileAdd(e.dataTransfer.files, "구매") } } // 설계 드래그 앤 드롭 핸들러 const handleDesignDrag = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (e.type === "dragenter" || e.type === "dragover") { setDesignDragActive(true) } else if (e.type === "dragleave") { setDesignDragActive(false) } } const handleDesignDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDesignDragActive(false) if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileAdd(e.dataTransfer.files, "설계") } } // 파일 삭제 const handleFileRemove = (index: number) => { const newFiles = attachments.filter((_, i) => i !== index) onAttachmentsChange(newFiles) } // 파일 타입 변경 const handleTypeChange = (index: number, newType: "구매" | "설계") => { const newFiles = [...attachments] const oldType = newFiles[index].attachmentType newFiles[index].attachmentType = newType console.log(`파일 타입 변경: ${newFiles[index].name} (${oldType} -> ${newType})`) console.log('변경 후 파일 목록:', newFiles.map(f => ({ name: f.name, attachmentType: f.attachmentType }))) onAttachmentsChange(newFiles) } // 파일 삭제 확인 const handleDeleteClick = (file: any, isExisting: boolean, index: number) => { setFileToDelete({ file, isExisting, index }) setDeleteDialogOpen(true) } // 파일 삭제 실행 const handleDeleteConfirm = async () => { if (!fileToDelete) return const { isExisting, index } = fileToDelete if (isExisting) { // 기존 첨부파일 삭제 - 서버액션 호출 if (responseId && userId && fileToDelete.file.id) { try { const result = await deleteVendorResponseAttachment({ attachmentId: fileToDelete.file.id, responseId, userId }) if (result.success) { // 클라이언트 상태 업데이트 const newExistingAttachments = existingAttachments.filter((_, i) => i !== index) onExistingAttachmentsChange?.(newExistingAttachments) } else { toast.error(`삭제 실패: ${result.error}`) return } } catch (error) { console.error('삭제 API 호출 실패:', error) toast.error('삭제 중 오류가 발생했습니다.') return } } } else { // 새 첨부파일 삭제 (클라이언트에서만) const newFiles = attachments.filter((_, i) => i !== index) onAttachmentsChange(newFiles) } setDeleteDialogOpen(false) setFileToDelete(null) } // 파일 삭제 취소 const handleDeleteCancel = () => { setDeleteDialogOpen(false) setFileToDelete(null) } // 파일 아이콘 가져오기 const getFileIcon = (fileName: string) => { const extension = fileName.split('.').pop()?.toLowerCase() const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp'] if (imageExtensions.includes(extension || '')) { return } return } // 구매/설계 문서 개수 계산 const purchaseCount = attachments.filter(f => f.attachmentType === "구매").length + existingAttachments.filter(f => f.attachmentType === "구매").length const designCount = attachments.filter(f => f.attachmentType === "설계").length + existingAttachments.filter(f => f.attachmentType === "설계").length return (
{/* 제출완료 상태 알림 */} {isSubmitted && ( 제출완료: 견적서가 이미 제출되어 추가 파일 업로드가 제한됩니다. )} {/* 필수 파일 안내 */} 문서 분류: 구매 문서(견적서, 상업조건 등)와 설계 문서(기술문서, 성적서, 인증서 등)를 구분하여 업로드하세요.
허용 파일: PDF, Word, Excel, PowerPoint, 이미지 파일, 압축 파일(ZIP, RAR, 7Z) (최대 1GB)
{/* 업로드 오류 표시 */} {uploadErrors.length > 0 && (
    {uploadErrors.map((error, index) => (
  • {error}
  • ))}
)} {/* 두 개의 드래그존 */}
{/* 구매 문서 업로드 영역 */} 구매 문서 견적서, 금액, 상업조건 관련 문서

구매 문서를 드래그하여 업로드

handleFileAdd(e.target.files, "구매")} className="hidden" /> {purchaseCount > 0 && (
{purchaseCount}개 업로드됨
)}
{/* 설계 문서 업로드 영역 */} 설계 문서 기술문서, 성적서, 인증서, 도면 등

설계 문서를 드래그하여 업로드

handleFileAdd(e.target.files, "설계")} className="hidden" /> {designCount > 0 && (
{designCount}개 업로드됨
)} {/*

최대 1GB, 여러 파일 선택 가능

*/}
{/* 첨부파일 목록 */} {(attachments.length > 0 || existingAttachments.length > 0) && (
첨부파일 목록
구매 {purchaseCount} 설계 {designCount} 총 {attachments.length + existingAttachments.length}개
유형 파일명 크기 문서 구분 상태 작업 {/* 기존 첨부파일 */} {existingAttachments.map((file, index) => ( {getFileIcon(file.originalFileName)}

{file.originalFileName}

{file.description && (

{file.description}

)}
{formatBytes(file.fileSize || 0)} {file.attachmentType === "구매" ? : } {file.attachmentType} 기존
))} {/* 새로 추가된 파일 */} {attachments.map((file, index) => ( {getFileIcon(file.name)}

{file.name}

{formatBytes(file.size)}
신규
))}
)} {/* 파일 삭제 확인 다이얼로그 */} 파일 삭제 {fileToDelete?.isExisting ? '기존 첨부파일' : '새로 업로드한 파일'} "{fileToDelete?.file.originalFileName || fileToDelete?.file.name}"을(를) 삭제하시겠습니까?
삭제된 파일은 복구할 수 없습니다.
) }