From 4c2d4c235bd80368e31cae9c375e9a585f6a6844 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 25 Sep 2025 03:28:27 +0000 Subject: (대표님) archiver 추가, 데이터룸구현 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/file-manager/FileManager.tsx | 1447 +++++++++++++++++++++++++++++++ 1 file changed, 1447 insertions(+) create mode 100644 components/file-manager/FileManager.tsx (limited to 'components/file-manager/FileManager.tsx') diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx new file mode 100644 index 00000000..483ef773 --- /dev/null +++ b/components/file-manager/FileManager.tsx @@ -0,0 +1,1447 @@ +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { + Folder, + File, + FolderPlus, + Upload, + Trash2, + Edit2, + Download, + Share2, + Eye, + EyeOff, + Lock, + Unlock, + Globe, + Shield, + AlertCircle, + MoreVertical, + ChevronRight, + ChevronDown, + Search, + Grid, + List, + Copy, + X +} from 'lucide-react'; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, +} from '@/components/ui/context-menu'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, +} from '@/components/ui/dropdown-menu'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogDescription, +} from '@/components/ui/dialog'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Badge } from '@/components/ui/badge'; +import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from '@/components/ui/breadcrumb'; +import { useToast } from '@/hooks/use-toast'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { cn } from '@/lib/utils'; +import { useSession } from 'next-auth/react'; +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone"; +import { + FileList, + FileListAction, + FileListDescription, + FileListHeader, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, + FileListSize, +} from "@/components/ui/file-list"; +import { decryptWithServerAction } from '@/components/drm/drmUtils'; +import { Progress } from '@/components/ui/progress'; + +interface FileItem { + id: string; + name: string; + type: 'file' | 'folder'; + size?: number; + mimeType?: string; + category: 'public' | 'restricted' | 'confidential' | 'internal'; + externalAccessLevel?: 'view_only' | 'view_download' | 'full_access'; + updatedAt: Date; + permissions?: { + canView: boolean; + canDownload: boolean; + canEdit: boolean; + canDelete: boolean; + }; + downloadCount?: number; + viewCount?: number; + parentId?: string | null; + children?: FileItem[]; +} + +interface UploadingFile { + file: File; + progress: number; + status: 'pending' | 'uploading' | 'processing' | 'completed' | 'error'; + error?: string; +} + +interface FileManagerProps { + projectId: string; +} + +// 카테고리별 아이콘과 색상 +const categoryConfig = { + public: { icon: Globe, color: 'text-green-500', label: '공개' }, + restricted: { icon: Eye, color: 'text-yellow-500', label: '제한' }, + confidential: { icon: Lock, color: 'text-red-500', label: '기밀' }, + internal: { icon: Shield, color: 'text-blue-500', label: '내부' }, +}; + +// Tree Item Component +const TreeItem: React.FC<{ + item: FileItem; + level: number; + expandedFolders: Set; + selectedItems: Set; + onToggleExpand: (id: string) => void; + onSelectItem: (id: string) => void; + onDoubleClick: (item: FileItem) => void; + onDownload: (item: FileItem) => void; + onDownloadFolder: (item: FileItem) => void; + onDelete: (ids: string[]) => void; + onShare: (item: FileItem) => void; + onRename: (item: FileItem) => void; + isInternalUser: boolean; +}> = ({ + item, + level, + expandedFolders, + selectedItems, + onToggleExpand, + onSelectItem, + onDoubleClick, + onDownload, + onDownloadFolder, + onDelete, + onShare, + onRename, + isInternalUser +}) => { + const hasChildren = item.type === 'folder' && item.children && item.children.length > 0; + const isExpanded = expandedFolders.has(item.id); + const isSelected = selectedItems.has(item.id); + const CategoryIcon = categoryConfig[item.category].icon; + const categoryColor = categoryConfig[item.category].color; + const categoryLabel = categoryConfig[item.category].label; + + const formatFileSize = (bytes?: number) => { + if (!bytes) return '-'; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`; + }; + + return ( + <> +
onSelectItem(item.id)} + onDoubleClick={() => onDoubleClick(item)} + > +
+ {item.type === 'folder' && ( + + )} + {item.type === 'file' && ( +
+ )} +
+ + {item.type === 'folder' ? ( + + ) : ( + + )} + + {item.name} + + + + {categoryLabel} + + + + {formatFileSize(item.size)} + + + {new Date(item.updatedAt).toLocaleDateString()} + + + + + + + + {item.type === 'file' && item.permissions?.canDownload && ( + onDownload(item)}> + + 다운로드 + + )} + + {item.type === 'folder' && ( + onDownloadFolder(item)}> + + 폴더 전체 다운로드 + + )} + + {isInternalUser && ( + <> + onShare(item)}> + + 공유 + + + {item.permissions?.canEdit && ( + onRename(item)}> + + 이름 변경 + + )} + + )} + + {item.permissions?.canDelete && ( + <> + + onDelete([item.id])} + > + + 삭제 + + + )} + + +
+ + {item.type === 'folder' && isExpanded && item.children && ( +
+ {item.children.map((child) => ( + + ))} +
+ )} + + ); +}; + +export function FileManager({ projectId }: FileManagerProps) { + const { data: session } = useSession(); + const [items, setItems] = useState([]); + const [treeItems, setTreeItems] = useState([]); + const [currentPath, setCurrentPath] = useState([]); + const [currentParentId, setCurrentParentId] = useState(null); + const [selectedItems, setSelectedItems] = useState>(new Set()); + const [expandedFolders, setExpandedFolders] = useState>(new Set()); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('list'); + const [searchQuery, setSearchQuery] = useState(''); + const [loading, setLoading] = useState(false); + + // 업로드 상태 + const [uploadDialogOpen, setUploadDialogOpen] = useState(false); + const [uploadingFiles, setUploadingFiles] = useState([]); + const [uploadCategory, setUploadCategory] = useState('confidential'); + + // 다이얼로그 상태 + const [folderDialogOpen, setFolderDialogOpen] = useState(false); + const [shareDialogOpen, setShareDialogOpen] = useState(false); + const [permissionDialogOpen, setPermissionDialogOpen] = useState(false); + const [renameDialogOpen, setRenameDialogOpen] = useState(false); + + // 다이얼로그 데이터 + const [dialogValue, setDialogValue] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('confidential'); + const [selectedFile, setSelectedFile] = useState(null); + const [shareSettings, setShareSettings] = useState({ + accessLevel: 'view_only', + password: '', + expiresAt: '', + maxDownloads: '', + }); + + const { toast } = useToast(); + + // 사용자가 내부 사용자인지 확인 + const isInternalUser = session?.user?.domain !== 'partners'; + + // 트리 구조 생성 함수 + const buildTree = (flatItems: FileItem[]): FileItem[] => { + const itemMap = new Map(); + const rootItems: FileItem[] = []; + + // 모든 아이템을 맵에 저장 (children 초기화) + flatItems.forEach(item => { + itemMap.set(item.id, { ...item, children: [] }); + }); + + // 부모-자식 관계 설정 + flatItems.forEach(item => { + const mappedItem = itemMap.get(item.id)!; + + if (!item.parentId) { + // parentId가 없으면 루트 아이템 + rootItems.push(mappedItem); + } else { + // parentId가 있으면 부모의 children에 추가 + const parent = itemMap.get(item.parentId); + if (parent) { + if (!parent.children) parent.children = []; + parent.children.push(mappedItem); + } else { + // 부모를 찾을 수 없으면 루트로 처리 + rootItems.push(mappedItem); + } + } + }); + + return rootItems; + }; + + // 파일 목록 가져오기 + const fetchItems = useCallback(async () => { + setLoading(true); + try { + const params = new URLSearchParams(); + + // 트리 뷰일 때는 전체 목록을 가져옴 + if (viewMode === 'list') { + params.append('viewMode', 'tree'); + // 트리 뷰에서도 현재 경로 정보는 유지 (하이라이팅 등에 사용) + if (currentParentId) params.append('currentParentId', currentParentId); + } else { + // 그리드 뷰일 때는 현재 폴더의 내용만 가져옴 + if (currentParentId) params.append('parentId', currentParentId); + } + + const response = await fetch(`/api/data-room/${projectId}?${params}`); + if (!response.ok) throw new Error('Failed to fetch files'); + + const data = await response.json(); + setItems(data); + + // 트리 구조 생성 + if (viewMode === 'list') { + const tree = buildTree(data); + setTreeItems(tree); + } + } catch (error) { + toast({ + title: '오류', + description: '파일을 불러오는데 실패했습니다.', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }, [projectId, currentParentId, viewMode, toast]); + + useEffect(() => { + fetchItems(); + }, [fetchItems]); + + // 폴더 생성 + const createFolder = async () => { + try { + const response = await fetch(`/api/data-room/${projectId}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: dialogValue, + type: 'folder', + category: selectedCategory, + parentId: currentParentId, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Failed to create folder'); + } + + await fetchItems(); + setFolderDialogOpen(false); + setDialogValue(''); + + toast({ + title: '성공', + description: '폴더가 생성되었습니다.', + }); + } catch (error: any) { + toast({ + title: '오류', + description: error.message || '폴더 생성에 실패했습니다.', + variant: 'destructive', + }); + } + }; + + // 파일 업로드 처리 + const handleFileUpload = async (files: FileList | File[]) => { + const fileArray = Array.from(files); + + // 업로드 파일 목록 초기화 + const newUploadingFiles: UploadingFile[] = fileArray.map(file => ({ + file, + progress: 0, + status: 'pending' as const + })); + + setUploadingFiles(newUploadingFiles); + + // 각 파일 업로드 처리 + for (let i = 0; i < fileArray.length; i++) { + const file = fileArray[i]; + + try { + // 상태 업데이트: 업로드 중 + setUploadingFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, status: 'uploading', progress: 20 } : f + )); + + // DRM 복호화 + setUploadingFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, status: 'processing', progress: 40 } : f + )); + + const decryptedData = await decryptWithServerAction(file); + + // FormData 생성 + const formData = new FormData(); + const blob = new Blob([decryptedData], { type: file.type }); + formData.append('file', blob, file.name); + formData.append('category', uploadCategory); + formData.append('fileSize', file.size.toString()); // 파일 크기 전달 + if (currentParentId) { + formData.append('parentId', currentParentId); + } + + // 업로드 진행률 업데이트 + setUploadingFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, progress: 60 } : f + )); + + // API 호출 + const response = await fetch(`/api/data-room/${projectId}/upload`, { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Upload failed'); + } + + // 성공 + setUploadingFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, status: 'completed', progress: 100 } : f + )); + + } catch (error: any) { + // 실패 + setUploadingFiles(prev => prev.map((f, idx) => + idx === i ? { + ...f, + status: 'error', + error: error.message || '업로드 실패' + } : f + )); + } + } + + // 모든 업로드 완료 후 목록 새로고침 + await fetchItems(); + + // 성공한 파일이 있으면 토스트 표시 + const successCount = newUploadingFiles.filter(f => f.status === 'completed').length; + if (successCount > 0) { + toast({ + title: '업로드 완료', + description: `${successCount}개 파일이 업로드되었습니다.`, + }); + } + }; + + // 폴더 다운로드 + const downloadFolder = async (folder: FileItem) => { + if (folder.type !== 'folder') return; + + try { + toast({ + title: '권한 확인 중', + description: '폴더 내 파일들의 다운로드 권한을 확인하고 있습니다...', + }); + + // 폴더 다운로드 API 호출 + const response = await fetch(`/api/data-room/${projectId}/download-folder/${folder.id}`, { + method: 'GET', + }); + + if (!response.ok) { + const error = await response.json(); + + // 권한이 없는 파일이 있는 경우 상세 정보 제공 + if (error.unauthorizedFiles) { + toast({ + title: '다운로드 권한 부족', + description: `${error.unauthorizedFiles.length}개 파일에 대한 권한이 없습니다: ${error.unauthorizedFiles.join(', ')}`, + variant: 'destructive', + }); + return; + } + + throw new Error(error.error || '폴더 다운로드 실패'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + + // 폴더명을 파일명에 포함 + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const fileName = `${folder.name}_${timestamp}.zip`; + + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + toast({ + title: '다운로드 완료', + description: `${folder.name} 폴더가 다운로드되었습니다.`, + }); + + } catch (error: any) { + toast({ + title: '오류', + description: error.message || '폴더 다운로드에 실패했습니다.', + variant: 'destructive', + }); + } + }; + + // 파일 공유 + const shareFile = async () => { + if (!selectedFile) return; + + try { + const response = await fetch(`/api/data-room/${projectId}/share`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fileId: selectedFile.id, + ...shareSettings, + }), + }); + + if (!response.ok) { + throw new Error('Failed to create share link'); + } + + const data = await response.json(); + + // 공유 링크 복사 + await navigator.clipboard.writeText(data.shareUrl); + + toast({ + title: '공유 링크 생성됨', + description: '링크가 클립보드에 복사되었습니다.', + }); + + setShareDialogOpen(false); + setSelectedFile(null); + } catch (error) { + toast({ + title: '오류', + description: '공유 링크 생성에 실패했습니다.', + variant: 'destructive', + }); + } + }; + + // 다중 파일 다운로드 + const downloadMultipleFiles = async (itemIds: string[]) => { + // 선택된 파일들 중 실제 파일만 필터링 (폴더 제외) + const filesToDownload = items.filter(item => + itemIds.includes(item.id) && + item.type === 'file' && + item.permissions?.canDownload + ); + + if (filesToDownload.length === 0) { + toast({ + title: '알림', + description: '다운로드 가능한 파일이 없습니다.', + variant: 'default', + }); + return; + } + + // 단일 파일인 경우 일반 다운로드 사용 + if (filesToDownload.length === 1) { + await downloadFile(filesToDownload[0]); + return; + } + + try { + toast({ + title: '다운로드 준비 중', + description: `${filesToDownload.length}개 파일을 압축하고 있습니다...`, + }); + + // 여러 파일 다운로드 API 호출 + const response = await fetch(`/api/data-room/${projectId}/download-multiple`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileIds: filesToDownload.map(f => f.id) }) + }); + + if (!response.ok) { + throw new Error('다운로드 실패'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + + // 현재 날짜시간을 파일명에 포함 + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const fileName = `files_${timestamp}.zip`; + + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + toast({ + title: '다운로드 완료', + description: `${filesToDownload.length}개 파일이 다운로드되었습니다.`, + }); + + } catch (error) { + console.error('다중 다운로드 오류:', error); + + // 실패 시 개별 다운로드 옵션 제공 + toast({ + title: '압축 다운로드 실패', + description: '개별 다운로드를 시도하시겠습니까?', + action: ( + + ), + }); + } + }; + + // 파일 다운로드 + const downloadFile = async (file: FileItem) => { + try { + const response = await fetch(`/api/data-room/${projectId}/${file.id}/download`); + + if (!response.ok) { + throw new Error('Download failed'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = file.name; + document.body.appendChild(a); + a.click(); + + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error) { + toast({ + title: '오류', + description: '다운로드에 실패했습니다.', + variant: 'destructive', + }); + } + }; + + // 파일 삭제 + const deleteItems = async (itemIds: string[]) => { + try { + await Promise.all( + itemIds.map(id => + fetch(`/api/data-room/${projectId}/${id}`, { method: 'DELETE' }) + ) + ); + + await fetchItems(); + setSelectedItems(new Set()); + + toast({ + title: '성공', + description: '선택한 항목이 삭제되었습니다.', + }); + } catch (error) { + toast({ + title: '오류', + description: '삭제에 실패했습니다.', + variant: 'destructive', + }); + } + }; + + // 폴더 더블클릭 처리 + const handleFolderOpen = (folder: FileItem) => { + if (viewMode === 'grid') { + setCurrentPath([...currentPath, folder.name]); + setCurrentParentId(folder.id); + } else { + // 트리 뷰에서는 expand/collapse + const newExpanded = new Set(expandedFolders); + if (newExpanded.has(folder.id)) { + newExpanded.delete(folder.id); + } else { + newExpanded.add(folder.id); + } + setExpandedFolders(newExpanded); + } + setSelectedItems(new Set()); + }; + + // 폴더 확장 토글 + const toggleFolderExpand = (folderId: string) => { + const newExpanded = new Set(expandedFolders); + if (newExpanded.has(folderId)) { + newExpanded.delete(folderId); + } else { + newExpanded.add(folderId); + } + setExpandedFolders(newExpanded); + }; + + // 아이템 선택 + const toggleItemSelection = (itemId: string) => { + const newSelected = new Set(selectedItems); + if (newSelected.has(itemId)) { + newSelected.delete(itemId); + } else { + newSelected.add(itemId); + } + setSelectedItems(newSelected); + }; + + // 경로 탐색 + const navigateToPath = (index: number) => { + if (index === -1) { + setCurrentPath([]); + setCurrentParentId(null); + } else { + setCurrentPath(currentPath.slice(0, index + 1)); + // parentId 업데이트 로직 필요 + } + }; + + // 필터링된 아이템 + const filteredItems = items.filter(item => + item.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const filteredTreeItems = treeItems.filter(item => + item.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // 파일 크기 포맷 + const formatFileSize = (bytes?: number) => { + if (!bytes) return '-'; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`; + }; + + return ( +
+ {/* 툴바 */} +
+
+
+ {isInternalUser && ( + <> + + + + )} + + {selectedItems.size > 0 && ( + <> + {/* 다중 다운로드 버튼 */} + {items.filter(item => + selectedItems.has(item.id) && + item.type === 'file' && + item.permissions?.canDownload + ).length > 0 && ( + + )} + + {/* 삭제 버튼 */} + {items.find(item => selectedItems.has(item.id))?.permissions?.canDelete && ( + + )} + + )} + + {!isInternalUser && ( + + + 외부 사용자 + + )} +
+ +
+
+ + setSearchQuery(e.target.value)} + /> +
+ + +
+
+ + {/* Breadcrumb */} + + + + navigateToPath(-1)}> + Home + + + {currentPath.map((path, index) => ( + + + navigateToPath(index)}> + {path} + + + ))} + + +
+ + {/* 파일 목록 */} + + {loading ? ( +
+
로딩 중...
+
+ ) : filteredItems.length === 0 ? ( +
+ +

비어있음

+
+ ) : viewMode === 'grid' ? ( +
+ {filteredItems.map((item) => { + const CategoryIcon = categoryConfig[item.category].icon; + const categoryColor = categoryConfig[item.category].color; + + return ( + + +
toggleItemSelection(item.id)} + onDoubleClick={() => { + if (item.type === 'folder') { + handleFolderOpen(item); + } + }} + > +
+ {item.type === 'folder' ? ( + + ) : ( + + )} + +
+ + + {item.name} + + + {item.viewCount !== undefined && ( +
+ + + {item.viewCount} + + {item.downloadCount !== undefined && ( + + + {item.downloadCount} + + )} +
+ )} +
+
+ + + {item.type === 'folder' && ( + <> + handleFolderOpen(item)}> + 열기 + + downloadFolder(item)}> + + 폴더 전체 다운로드 + + + )} + + {item.type === 'file' && item.permissions?.canDownload && ( + downloadFile(item)}> + + 다운로드 + + )} + + {isInternalUser && ( + <> + + + + + 카테고리 변경 + + + {Object.entries(categoryConfig).map(([key, config]) => ( + + + {config.label} + + ))} + + + + { + setSelectedFile(item); + setShareDialogOpen(true); + }} + > + + 공유 + + + {item.permissions?.canEdit && ( + { + setSelectedFile(item); + setDialogValue(item.name); + setRenameDialogOpen(true); + }}> + + 이름 변경 + + )} + + )} + + {item.permissions?.canDelete && ( + <> + + deleteItems([item.id])} + > + + 삭제 + + + )} + +
+ ); + })} +
+ ) : ( + // Tree View +
+ {filteredTreeItems.map((item) => ( + { + setSelectedFile(item); + setShareDialogOpen(true); + }} + onRename={(item) => { + setSelectedFile(item); + setDialogValue(item.name); + setRenameDialogOpen(true); + }} + isInternalUser={isInternalUser} + /> + ))} +
+ )} +
+ + {/* 업로드 다이얼로그 */} + + + + 파일 업로드 + + 파일을 드래그 앤 드롭하거나 클릭하여 선택하세요. + + + +
+ {/* 카테고리 선택 */} +
+ + +
+ + {/* Dropzone */} + { + handleFileUpload(acceptedFiles); + }} + accept={{ + 'application/pdf': ['.pdf'], + 'application/msword': ['.doc'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], + 'application/vnd.ms-excel': ['.xls'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], + 'application/vnd.ms-powerpoint': ['.ppt'], + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'], + 'text/plain': ['.txt'], + 'text/csv': ['.csv'], + 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], + 'application/zip': ['.zip'], + 'application/x-rar-compressed': ['.rar'], + 'application/x-7z-compressed': ['.7z'], + 'application/x-dwg': ['.dwg'], + 'application/x-dxf': ['.dxf'], + }} + multiple={true} + disabled={false} + > + + +
+ + 파일을 드래그하거나 클릭하여 업로드 + 여러 파일을 동시에 업로드할 수 있습니다 +
+
+
+ + {/* 업로드 중인 파일 목록 */} + {uploadingFiles.length > 0 && ( + + 업로드 중인 파일 + {uploadingFiles.map((uploadFile, index) => ( + + + + + + {uploadFile.file.name} + +
+ {uploadFile.file.size} + {uploadFile.status === 'uploading' && 업로드 중...} + {uploadFile.status === 'processing' && 처리 중...} + {uploadFile.status === 'completed' && ( + 완료 + )} + {uploadFile.status === 'error' && ( + {uploadFile.error} + )} +
+ {(uploadFile.status === 'uploading' || uploadFile.status === 'processing') && ( + + )} +
+
+ + {uploadFile.status === 'error' && ( + + )} + +
+ ))} +
+ )} +
+ + + + +
+
+ + {/* 폴더 생성 다이얼로그 */} + + + + 새 폴더 만들기 + + 폴더 이름과 접근 권한 카테고리를 설정하세요. + + + +
+
+ + setDialogValue(e.target.value)} + placeholder="폴더 이름 입력" + /> +
+ +
+ + +
+
+ + + + + +
+
+ + {/* 파일 공유 다이얼로그 */} + + + + 파일 공유 + + {selectedFile?.name}을(를) 공유합니다. + + + + + + 링크 공유 + 권한 설정 + + + +
+ + +
+ +
+ + setShareSettings({...shareSettings, password: e.target.value})} + placeholder="비밀번호 입력" + /> +
+ +
+ + setShareSettings({...shareSettings, expiresAt: e.target.value})} + /> +
+ +
+ + setShareSettings({...shareSettings, maxDownloads: e.target.value})} + placeholder="무제한" + /> +
+
+ + +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + + + + +
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3