summaryrefslogtreecommitdiff
path: root/lib/bidding/vendor/components/pre-quote-file-upload.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/vendor/components/pre-quote-file-upload.tsx')
-rw-r--r--lib/bidding/vendor/components/pre-quote-file-upload.tsx367
1 files changed, 0 insertions, 367 deletions
diff --git a/lib/bidding/vendor/components/pre-quote-file-upload.tsx b/lib/bidding/vendor/components/pre-quote-file-upload.tsx
deleted file mode 100644
index b6d8990b..00000000
--- a/lib/bidding/vendor/components/pre-quote-file-upload.tsx
+++ /dev/null
@@ -1,367 +0,0 @@
-'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<UploadedDocument[]>([])
- 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 (
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <FileText className="w-5 h-5" />
- 견적 문서 업로드
- </CardTitle>
- </CardHeader>
- <CardContent className="space-y-4">
- {!readOnly && (
- <div
- className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
- dragActive
- ? 'border-primary bg-primary/5'
- : 'border-gray-300 hover:border-gray-400'
- }`}
- onDragEnter={handleDrag}
- onDragLeave={handleDrag}
- onDragOver={handleDrag}
- onDrop={handleDrop}
- >
- <Upload className="w-8 h-8 mx-auto text-gray-400 mb-2" />
- <div className="space-y-2">
- <p className="text-sm text-gray-600">
- 파일을 드래그하여 업로드하거나 클릭하여 선택하세요
- </p>
- <Input
- type="file"
- multiple
- accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.zip"
- onChange={(e) => e.target.files && handleFileUpload(e.target.files)}
- className="hidden"
- id="file-upload"
- />
- <Label htmlFor="file-upload">
- <Button variant="outline" className="cursor-pointer" asChild>
- <span>파일 선택</span>
- </Button>
- </Label>
- </div>
- <p className="text-xs text-gray-500 mt-2">
- 지원 형식: PDF, Word, Excel, 이미지, ZIP (최대 50MB)
- </p>
- </div>
- )}
-
- {isUploading && (
- <div className="space-y-2">
- <div className="flex items-center gap-2">
- <Upload className="w-4 h-4 animate-pulse" />
- <span className="text-sm">업로드 중...</span>
- </div>
- <Progress value={uploadProgress} className="h-2" />
- </div>
- )}
-
- {/* 업로드된 문서 목록 */}
- {documents.length > 0 ? (
- <div className="space-y-2">
- <Label className="text-sm font-medium">업로드된 문서</Label>
- <Table>
- <TableHeader>
- <TableRow>
- <TableHead>파일명</TableHead>
- <TableHead>크기</TableHead>
- <TableHead>업로드일</TableHead>
- <TableHead className="w-24">작업</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {documents.map((doc) => (
- <TableRow key={doc.id}>
- <TableCell>
- <div className="flex items-center gap-2">
- <FileText className="w-4 h-4 text-gray-500" />
- <span className="truncate max-w-48" title={doc.originalFileName}>
- {doc.originalFileName}
- </span>
- </div>
- </TableCell>
- <TableCell className="text-sm text-gray-500">
- {formatFileSize(doc.fileSize)}
- </TableCell>
- <TableCell className="text-sm text-gray-500">
- {new Date(doc.uploadedAt).toLocaleDateString('ko-KR')}
- </TableCell>
- <TableCell>
- <Button
- variant="outline"
- size="sm"
- onClick={() => handleDownload(doc)}
- >
- <Download className="w-3 h-3" />
- </Button>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </div>
- ) : (
- <div className="text-center py-4 text-gray-500">
- <FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
- <p className="text-sm">업로드된 문서가 없습니다</p>
- </div>
- )}
-
- {readOnly && documents.length === 0 && (
- <div className="flex items-center gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
- <AlertCircle className="w-4 h-4 text-yellow-600" />
- <span className="text-sm text-yellow-800">
- 견적 문서가 업로드되지 않았습니다.
- </span>
- </div>
- )}
- </CardContent>
- </Card>
- )
-}