'use client' import * as React from 'react' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Upload, FileText, Download, Trash2 } from 'lucide-react' import { useToast } from '@/hooks/use-toast' import { useTransition } from 'react' import { downloadFile } from '@/lib/file-download' import { uploadBiddingDocument, getBiddingDocuments, deleteBiddingDocument } from '../service' interface UploadedDocument { id: number biddingId: number companyId: number | null documentType: string fileName: string originalFileName: string fileSize: number | null filePath: string title: string | null description: string | null uploadedAt: string uploadedBy: string } interface BiddingDocumentUploadDialogProps { open: boolean onOpenChange: (open: boolean) => void biddingId: number userId: string onSuccess?: () => void } const documentTypes = [ { value: 'notice', label: '입찰공고서' }, { value: 'specification', label: '사양서' }, { value: 'specification_meeting', label: '사양설명회' }, { value: 'contract_draft', label: '계약서 초안' }, { value: 'financial_doc', label: '재무 관련 문서' }, { value: 'technical_doc', label: '기술 관련 문서' }, { value: 'certificate', label: '인증서류' }, { value: 'pr_document', label: 'PR 문서' }, { value: 'spec_document', label: 'SPEC 문서' }, { value: 'evaluation_doc', label: '평가 관련 문서' }, { value: 'bid_attachment', label: '입찰 첨부파일' }, { value: 'other', label: '기타' } ] export function BiddingDocumentUploadDialog({ open, onOpenChange, biddingId, userId, onSuccess }: BiddingDocumentUploadDialogProps) { const { toast } = useToast() const [isPending, startTransition] = useTransition() const [documents, setDocuments] = React.useState([]) const [isLoading, setIsLoading] = React.useState(false) // 업로드 폼 상태 const [selectedFile, setSelectedFile] = React.useState(null) const [documentType, setDocumentType] = React.useState('') const [title, setTitle] = React.useState('') const [description, setDescription] = React.useState('') // 다이얼로그가 열릴 때 문서 목록 로드 React.useEffect(() => { if (open) { loadDocuments() resetForm() } }, [open, biddingId]) const resetForm = () => { setSelectedFile(null) setDocumentType('') setTitle('') setDescription('') } const loadDocuments = async () => { setIsLoading(true) try { // 서버 액션 직접 호출 const docs = await getBiddingDocuments(biddingId) const mappedDocs = docs.map((doc: any) => ({ ...doc, uploadedAt: doc.uploadedAt?.toString() || '', uploadedBy: doc.uploadedBy || '' })) setDocuments(mappedDocs) } catch (error) { console.error('Failed to load documents:', error) toast({ title: '오류', description: '문서 목록을 불러오는데 실패했습니다.', variant: 'destructive', }) } finally { setIsLoading(false) } } const handleFileSelect = (event: React.ChangeEvent) => { const files = event.target.files if (!files || files.length === 0) return const file = files[0] // 파일 크기 체크 (50MB 제한) if (file.size > 50 * 1024 * 1024) { toast({ title: '파일 크기 초과', description: '파일 크기가 50MB를 초과합니다.', variant: 'destructive', }) return } // 파일 타입 체크 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: 'PDF, Word, Excel, 이미지, ZIP 파일만 업로드 가능합니다.', variant: 'destructive', }) return } setSelectedFile(file) } const handleUpload = async () => { if (!selectedFile || !documentType) { toast({ title: '입력 오류', description: '파일과 문서 타입을 선택해주세요.', variant: 'destructive', }) return } startTransition(async () => { try { // 서버 액션 직접 호출 const result = await uploadBiddingDocument( biddingId, selectedFile, documentType, title, description, userId ) if (result.success) { toast({ title: '업로드 완료', description: result.message || '문서가 성공적으로 업로드되었습니다.', }) resetForm() await loadDocuments() onSuccess?.() } else { toast({ title: '업로드 실패', description: result.error || '문서 업로드에 실패했습니다.', variant: 'destructive', }) } } catch (error) { console.error('Upload error:', error) toast({ title: '업로드 실패', description: '문서 업로드 중 오류가 발생했습니다.', variant: 'destructive', }) } }) } // 파일 다운로드 const handleDownload = (document: UploadedDocument) => { startTransition(async () => { try { await downloadFile(document.filePath, document.originalFileName, { showToast: true }) } catch (error) { toast({ title: '다운로드 실패', description: '파일 다운로드에 실패했습니다.', variant: 'destructive', }) } }) } // 파일 삭제 const handleDelete = (document: UploadedDocument) => { if (!confirm(`"${document.originalFileName}" 파일을 삭제하시겠습니까?`)) { return } startTransition(async () => { try { // 서버 액션 직접 호출 const result = await deleteBiddingDocument(document.id, biddingId, userId) if (result.success) { toast({ title: '삭제 완료', description: result.message || '문서가 성공적으로 삭제되었습니다.', }) await loadDocuments() onSuccess?.() } else { toast({ title: '삭제 실패', description: result.error || '문서 삭제에 실패했습니다.', variant: 'destructive', }) } } catch (error) { console.error('Delete error:', 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] } const getDocumentTypeLabel = (type: string) => { return documentTypes.find(dt => dt.value === type)?.label || type } return ( 입찰 문서 등록 입찰 관련 문서를 업로드하고 관리할 수 있습니다.
{/* 파일 업로드 섹션 */} 새 문서 업로드

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

setTitle(e.target.value)} />