'use client' import * as React from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Progress } from '@/components/ui/progress' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Upload, FileText, Download, Trash2, AlertCircle } from 'lucide-react' import { useToast } from '@/hooks/use-toast' import { saveFile } from '@/lib/file-stroage' import { downloadFile } from '@/lib/file-download' import { uploadPreQuoteDocument, getPreQuoteDocuments } from '../../pre-quote/service' interface UploadedDocument { id: number fileName: string originalFileName: string fileSize: number | null filePath: string title: string | null description: string | null uploadedAt: string } interface PreQuoteFileUploadProps { biddingId: number companyId: number onUploadComplete?: (documentId: number) => void readOnly?: boolean } export function PreQuoteFileUpload({ biddingId, companyId, onUploadComplete, readOnly = false }: PreQuoteFileUploadProps) { const { toast } = useToast() const [documents, setDocuments] = React.useState([]) const [isUploading, setIsUploading] = React.useState(false) const [uploadProgress, setUploadProgress] = React.useState(0) const [dragActive, setDragActive] = React.useState(false) // 업로드된 문서 목록 로드 const loadDocuments = React.useCallback(async () => { try { const docs = await getPreQuoteDocuments(biddingId, companyId) // Date를 string으로 변환 const mappedDocs = docs.map(doc => ({ ...doc, uploadedAt: doc.uploadedAt.toString() })) setDocuments(mappedDocs) } catch (error) { console.error('Failed to load documents:', error) toast({ title: '오류', description: '업로드된 문서 목록을 불러오는데 실패했습니다.', variant: 'destructive', }) } }, [biddingId, companyId, toast]) React.useEffect(() => { loadDocuments() }, [loadDocuments]) // 파일 업로드 처리 const handleFileUpload = async (files: FileList | File[]) => { if (readOnly) return const fileArray = Array.from(files) if (fileArray.length === 0) return setIsUploading(true) setUploadProgress(0) try { for (let i = 0; i < fileArray.length; i++) { const file = fileArray[i] // 파일 크기 체크 (50MB 제한) if (file.size > 50 * 1024 * 1024) { toast({ title: '파일 크기 초과', description: `${file.name}의 크기가 50MB를 초과합니다.`, variant: 'destructive', }) continue } // 파일 타입 체크 const allowedTypes = [ 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'image/jpeg', 'image/png', 'application/zip' ] if (!allowedTypes.includes(file.type)) { toast({ title: '지원하지 않는 파일 형식', description: `${file.name}: PDF, Word, Excel, 이미지, ZIP 파일만 업로드 가능합니다.`, variant: 'destructive', }) continue } // 파일 저장 const saveResult = await saveFile({ file, directory: `bidding/${biddingId}/quotations`, originalName: file.name, userId: 'current-user' // TODO: 실제 사용자 ID }) if (!saveResult.success) { toast({ title: '업로드 실패', description: `${file.name}: ${saveResult.error}`, variant: 'destructive', }) continue } // 데이터베이스에 문서 정보 저장 const uploadResult = await uploadPreQuoteDocument( biddingId, companyId, { fileName: saveResult.fileName!, originalFileName: file.name, fileSize: file.size, mimeType: file.type, filePath: saveResult.path! }, 'current-user' // TODO: 실제 사용자 ID ) if (uploadResult.success) { toast({ title: '업로드 완료', description: `${file.name}이 성공적으로 업로드되었습니다.`, }) if (onUploadComplete && uploadResult.documentId) { onUploadComplete(uploadResult.documentId) } } else { toast({ title: '업로드 실패', description: uploadResult.error, variant: 'destructive', }) } // 진행률 업데이트 setUploadProgress(((i + 1) / fileArray.length) * 100) } // 문서 목록 새로고침 await loadDocuments() } catch (error) { console.error('Upload error:', error) toast({ title: '업로드 오류', description: '파일 업로드 중 오류가 발생했습니다.', variant: 'destructive', }) } finally { setIsUploading(false) setUploadProgress(0) } } // 드래그 앤 드롭 처리 const handleDrag = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true) } else if (e.type === 'dragleave') { setDragActive(false) } } const handleDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDragActive(false) if (readOnly) return if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileUpload(e.dataTransfer.files) } } // 파일 다운로드 const handleDownload = async (document: UploadedDocument) => { try { await downloadFile(document.filePath, document.originalFileName, { showToast: true }) } catch (error) { console.error('Failed to download document:', error) toast({ title: '다운로드 실패', description: '파일 다운로드에 실패했습니다.', variant: 'destructive', }) } } // 파일 크기 포맷팅 const formatFileSize = (bytes: number | null) => { if (!bytes) return '-' 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] } return ( 견적 문서 업로드 {!readOnly && (

파일을 드래그하여 업로드하거나 클릭하여 선택하세요

e.target.files && handleFileUpload(e.target.files)} className="hidden" id="file-upload" />

지원 형식: PDF, Word, Excel, 이미지, ZIP (최대 50MB)

)} {isUploading && (
업로드 중...
)} {/* 업로드된 문서 목록 */} {documents.length > 0 ? (
파일명 크기 업로드일 작업 {documents.map((doc) => (
{doc.originalFileName}
{formatFileSize(doc.fileSize)} {new Date(doc.uploadedAt).toLocaleDateString('ko-KR')}
))}
) : (

업로드된 문서가 없습니다

)} {readOnly && documents.length === 0 && (
견적 문서가 업로드되지 않았습니다.
)}
) }