From bea9853efe30c393b0d030bc552c1f5bbb838835 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 17 Oct 2025 08:09:35 +0000
Subject: (대표님) 데이터룸 관련 개발사항
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/file-manager/FileManager.tsx | 874 +++++++++++++++++++++++---------
 1 file changed, 626 insertions(+), 248 deletions(-)

(limited to 'components/file-manager/FileManager.tsx')

diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx
index f92f6b04..1af29e74 100644
--- a/components/file-manager/FileManager.tsx
+++ b/components/file-manager/FileManager.tsx
@@ -115,6 +115,7 @@ interface FileItem {
   viewCount?: number;
   parentId?: string | null;
   children?: FileItem[];
+  filePath?: string;
 }
 
 interface UploadingFile {
@@ -151,6 +152,7 @@ const TreeItem: React.FC<{
   onDelete: (ids: string[]) => void;
   onShare: (item: FileItem) => void;
   onRename: (item: FileItem) => void;
+  onCategoryChange: (item: FileItem) => void;
   isInternalUser: boolean;
 }> = ({
   item,
@@ -166,6 +168,7 @@ const TreeItem: React.FC<{
   onDelete,
   onShare,
   onRename,
+  onCategoryChange,
   isInternalUser
 }) => {
     const hasChildren = item.type === 'folder' && item.children && item.children.length > 0;
@@ -184,113 +187,180 @@ const TreeItem: React.FC<{
 
     return (
       <>
-        <div
-          className={cn(
-            "flex items-center p-2 rounded-lg cursor-pointer transition-colors",
-            "hover:bg-accent",
-            isSelected && "bg-accent"
-          )}
-          style={{ paddingLeft: `${level * 24 + 8}px` }}
-          onClick={() => onSelectItem(item.id)}
-          onDoubleClick={() => onDoubleClick(item)}
-        >
-          <div className="flex items-center mr-2">
-            {item.type === 'folder' && (
-              <button
-                onClick={(e) => {
-                  e.stopPropagation();
-                  onToggleExpand(item.id);
-                }}
-                className="p-0.5 hover:bg-gray-200 rounded"
-              >
-                {isExpanded ? (
-                  <ChevronDown className="h-4 w-4" />
-                ) : (
-                  <ChevronRight className="h-4 w-4" />
+        <ContextMenu>
+          <ContextMenuTrigger>
+            <div
+              className={cn(
+                "flex items-center p-2 rounded-lg cursor-pointer transition-colors",
+                "hover:bg-accent",
+                isSelected && "bg-accent"
+              )}
+              style={{ paddingLeft: `${level * 24 + 8}px` }}
+              onClick={() => onSelectItem(item.id)}
+              onDoubleClick={() => onDoubleClick(item)}
+            >
+              <div className="flex items-center mr-2">
+                {item.type === 'folder' && (
+                  <button
+                    onClick={(e) => {
+                      e.stopPropagation();
+                      onToggleExpand(item.id);
+                    }}
+                    className="p-0.5 hover:bg-gray-200 rounded"
+                  >
+                    {isExpanded ? (
+                      <ChevronDown className="h-4 w-4" />
+                    ) : (
+                      <ChevronRight className="h-4 w-4" />
+                    )}
+                  </button>
                 )}
-              </button>
-            )}
-            {item.type === 'file' && (
-              <div className="w-5" />
-            )}
-          </div>
+                {item.type === 'file' && (
+                  <div className="w-5" />
+                )}
+              </div>
 
-          {item.type === 'folder' ? (
-            <Folder className="h-5 w-5 text-blue-500 mr-2" />
-          ) : (
-            <File className="h-5 w-5 text-gray-500 mr-2" />
-          )}
-
-          <span className="flex-1">{item.name}</span>
-
-          <Badge variant="outline" className="mr-2">
-            <CategoryIcon className={cn("h-3 w-3 mr-1", categoryColor)} />
-            {categoryLabel}
-          </Badge>
-
-          <span className="text-sm text-muted-foreground mr-4">
-            {formatFileSize(item.size)}
-          </span>
-          <span className="text-sm text-muted-foreground mr-2">
-            {new Date(item.updatedAt).toLocaleDateString()}
-          </span>
-
-          <DropdownMenu>
-            <DropdownMenuTrigger asChild>
-              <Button variant="ghost" size="sm">
-                <MoreVertical className="h-4 w-4" />
-              </Button>
-            </DropdownMenuTrigger>
-            <DropdownMenuContent>
-              {item.type === 'file' && (
-                <>
-                  <DropdownMenuItem onClick={() => onView(item)}>
-                    <Eye className="h-4 w-4 mr-2" />
-                    View
-                  </DropdownMenuItem>
-                  {item.permissions?.canDownload && (
-                    <DropdownMenuItem onClick={() => onDownload(item)}>
+              {item.type === 'folder' ? (
+                <Folder className="h-5 w-5 text-blue-500 mr-2" />
+              ) : (
+                <File className="h-5 w-5 text-gray-500 mr-2" />
+              )}
+
+              <span className="flex-1">{item.name}</span>
+
+              <Badge variant="outline" className="mr-2">
+                <CategoryIcon className={cn("h-3 w-3 mr-1", categoryColor)} />
+                {categoryLabel}
+              </Badge>
+
+              <span className="text-sm text-muted-foreground mr-4">
+                {formatFileSize(item.size)}
+              </span>
+              <span className="text-sm text-muted-foreground mr-2">
+                {new Date(item.updatedAt).toLocaleDateString()}
+              </span>
+
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button variant="ghost" size="sm">
+                    <MoreVertical className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent>
+                  {item.type === 'file' && (
+                    <>
+                      <DropdownMenuItem onClick={() => onView(item)}>
+                        <Eye className="h-4 w-4 mr-2" />
+                        View
+                      </DropdownMenuItem>
+                      {item.permissions?.canDownload && (
+                        <DropdownMenuItem onClick={() => onDownload(item)}>
+                          <Download className="h-4 w-4 mr-2" />
+                          Download
+                        </DropdownMenuItem>
+                      )}
+                    </>
+                  )}
+
+                  {item.type === 'folder' && (
+                    <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
                       <Download className="h-4 w-4 mr-2" />
-                      Download
+                      Download Folder
                     </DropdownMenuItem>
                   )}
-                </>
-              )}
 
-              {item.type === 'folder' && (
-                <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
+                  {isInternalUser && (
+                    <>
+                      <DropdownMenuSeparator />
+                      {item.permissions?.canEdit && (
+                        <DropdownMenuItem onClick={() => onRename(item)}>
+                          <Edit2 className="h-4 w-4 mr-2" />
+                          Rename
+                        </DropdownMenuItem>
+                      )}
+                      <DropdownMenuItem onClick={() => onCategoryChange(item)}>
+                        <Shield className="h-4 w-4 mr-2" />
+                        Change Category
+                      </DropdownMenuItem>
+                    </>
+                  )}
+
+                  {item.permissions?.canDelete && (
+                    <>
+                      <DropdownMenuSeparator />
+                      <DropdownMenuItem
+                        className="text-destructive"
+                        onClick={() => onDelete([item.id])}
+                      >
+                        <Trash2 className="h-4 w-4 mr-2" />
+                        Delete
+                      </DropdownMenuItem>
+                    </>
+                  )}
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          </ContextMenuTrigger>
+
+          <ContextMenuContent>
+            {item.type === 'file' && (
+              <>
+                <ContextMenuItem onClick={() => onView(item)}>
+                  <Eye className="h-4 w-4 mr-2" />
+                  View
+                </ContextMenuItem>
+                {item.permissions?.canDownload && (
+                  <ContextMenuItem onClick={() => onDownload(item)}>
+                    <Download className="h-4 w-4 mr-2" />
+                    Download
+                  </ContextMenuItem>
+                )}
+              </>
+            )}
+
+            {item.type === 'folder' && (
+              <>
+                <ContextMenuItem onClick={() => onDoubleClick(item)}>
+                  <Folder className="h-4 w-4 mr-2" />
+                  {isExpanded ? 'Collapse' : 'Expand'}
+                </ContextMenuItem>
+                <ContextMenuItem onClick={() => onDownloadFolder(item)}>
                   <Download className="h-4 w-4 mr-2" />
                   Download Folder
-                </DropdownMenuItem>
-              )}
-
-              {isInternalUser && (
-                <>
+                </ContextMenuItem>
+              </>
+            )}
 
-                  {item.permissions?.canEdit && (
-                    <DropdownMenuItem onClick={() => onRename(item)}>
-                      <Edit2 className="h-4 w-4 mr-2" />
-                      Rename
-                    </DropdownMenuItem>
-                  )}
-                </>
-              )}
+            {isInternalUser && (
+              <>
+                <ContextMenuSeparator />
+                {item.permissions?.canEdit && (
+                  <ContextMenuItem onClick={() => onRename(item)}>
+                    <Edit2 className="h-4 w-4 mr-2" />
+                    Rename
+                  </ContextMenuItem>
+                )}
+                <ContextMenuItem onClick={() => onCategoryChange(item)}>
+                  <Shield className="h-4 w-4 mr-2" />
+                  Change Category
+                </ContextMenuItem>
+              </>
+            )}
 
-              {item.permissions?.canDelete && (
-                <>
-                  <DropdownMenuSeparator />
-                  <DropdownMenuItem
-                    className="text-destructive"
-                    onClick={() => onDelete([item.id])}
-                  >
-                    <Trash2 className="h-4 w-4 mr-2" />
-                    Delete
-                  </DropdownMenuItem>
-                </>
-              )}
-            </DropdownMenuContent>
-          </DropdownMenu>
-        </div>
+            {item.permissions?.canDelete && (
+              <>
+                <ContextMenuSeparator />
+                <ContextMenuItem
+                  className="text-destructive"
+                  onClick={() => onDelete([item.id])}
+                >
+                  <Trash2 className="h-4 w-4 mr-2" />
+                  Delete
+                </ContextMenuItem>
+              </>
+            )}
+          </ContextMenuContent>
+        </ContextMenu>
 
         {item.type === 'folder' && isExpanded && item.children && (
           <div>
@@ -310,6 +380,7 @@ const TreeItem: React.FC<{
                 onDelete={onDelete}
                 onShare={onShare}
                 onRename={onRename}
+                onCategoryChange={onCategoryChange}
                 isInternalUser={isInternalUser}
               />
             ))}
@@ -331,12 +402,12 @@ export function FileManager({ projectId }: FileManagerProps) {
   const [searchQuery, setSearchQuery] = useState('');
   const [loading, setLoading] = useState(false);
 
-  console.log(items, "items")
-
   // Upload states
   const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
   const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
+  const [selectedFilesForUpload, setSelectedFilesForUpload] = useState<File[]>([]);
   const [uploadCategory, setUploadCategory] = useState<string>('confidential');
+  const [preserveFolderStructure, setPreserveFolderStructure] = useState(false);
 
   // Dialog states
   const [folderDialogOpen, setFolderDialogOpen] = useState(false);
@@ -345,6 +416,9 @@ export function FileManager({ projectId }: FileManagerProps) {
   const [renameDialogOpen, setRenameDialogOpen] = useState(false);
   const [viewerDialogOpen, setViewerDialogOpen] = useState(false);
   const [viewerFileUrl, setViewerFileUrl] = useState<string | null>(null);
+  const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
+  const [applyToChildren, setApplyToChildren] = useState(false);
+  const [newCategory, setNewCategory] = useState('confidential');
 
   // Dialog data
   const [dialogValue, setDialogValue] = useState('');
@@ -473,8 +547,8 @@ export function FileManager({ projectId }: FileManagerProps) {
     }
   };
 
-  // Handle file upload
-  const handleFileUpload = async (files: FileList | File[]) => {
+  // Handle file upload - 실제 업로드 처리
+  const handleFileUpload = async (files: File[]) => {
     const fileArray = Array.from(files);
 
     // Initialize uploading file list
@@ -486,63 +560,64 @@ export function FileManager({ projectId }: FileManagerProps) {
 
     setUploadingFiles(newUploadingFiles);
 
-    // Process each file upload
-    for (let i = 0; i < fileArray.length; i++) {
-      const file = fileArray[i];
-
-      try {
-        // Update status: uploading
-        setUploadingFiles(prev => prev.map((f, idx) =>
-          idx === i ? { ...f, status: 'uploading', progress: 20 } : f
-        ));
-
-        // DRM decryption
-        setUploadingFiles(prev => prev.map((f, idx) =>
-          idx === i ? { ...f, status: 'processing', progress: 40 } : f
-        ));
-
-        const decryptedData = await decryptWithServerAction(file);
-
-        // Create 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()); // Pass file size
-        if (currentParentId) {
-          formData.append('parentId', currentParentId);
-        }
-
-        // Update upload progress
-        setUploadingFiles(prev => prev.map((f, idx) =>
-          idx === i ? { ...f, progress: 60 } : f
-        ));
-
-        // API call
-        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');
+    // 폴더 구조 보존 옵션이 켜져있고 상대 경로가 있는 경우
+    if (preserveFolderStructure && fileArray.some((file: any) => file.webkitRelativePath)) {
+      // 폴더 구조를 먼저 생성
+      const folderMap = new Map<string, string>(); // path -> folderId
+      
+      for (let i = 0; i < fileArray.length; i++) {
+        const file = fileArray[i];
+        const relativePath = (file as any).webkitRelativePath;
+        
+        if (relativePath) {
+          const pathParts = relativePath.split('/');
+          const folders = pathParts.slice(0, -1); // 파일명 제외
+          
+          let currentFolderPath = '';
+          let parentId = currentParentId;
+          
+          // 각 폴더를 순차적으로 생성
+          for (const folderName of folders) {
+            currentFolderPath = currentFolderPath ? `${currentFolderPath}/${folderName}` : folderName;
+            
+            if (!folderMap.has(currentFolderPath)) {
+              // 폴더 생성 API 호출
+              try {
+                const response = await fetch(`/api/data-room/${projectId}`, {
+                  method: 'POST',
+                  headers: { 'Content-Type': 'application/json' },
+                  body: JSON.stringify({
+                    name: folderName,
+                    type: 'folder',
+                    category: uploadCategory,
+                    parentId: parentId,
+                  }),
+                });
+                
+                if (response.ok) {
+                  const newFolder = await response.json();
+                  folderMap.set(currentFolderPath, newFolder.id);
+                  parentId = newFolder.id;
+                }
+              } catch (error) {
+                console.error('Failed to create folder:', folderName);
+              }
+            } else {
+              parentId = folderMap.get(currentFolderPath) || null;
+            }
+          }
+          
+          // 폴더가 생성되었으면 해당 폴더에 파일 업로드
+          await uploadSingleFile(file, i, parentId);
+        } else {
+          // 상대 경로가 없으면 일반 업로드
+          await uploadSingleFile(file, i, currentParentId);
         }
-
-        // Success
-        setUploadingFiles(prev => prev.map((f, idx) =>
-          idx === i ? { ...f, status: 'completed', progress: 100 } : f
-        ));
-
-      } catch (error: any) {
-        // Failure
-        setUploadingFiles(prev => prev.map((f, idx) =>
-          idx === i ? {
-            ...f,
-            status: 'error',
-            error: error.message || 'Upload failed'
-          } : f
-        ));
+      }
+    } else {
+      // 일반 업로드 (폴더 구조 보존 없음)
+      for (let i = 0; i < fileArray.length; i++) {
+        await uploadSingleFile(fileArray[i], i, currentParentId);
       }
     }
 
@@ -550,7 +625,7 @@ export function FileManager({ projectId }: FileManagerProps) {
     await fetchItems();
 
     // Show toast if any files succeeded
-    const successCount = newUploadingFiles.filter(f => f.status === 'completed').length;
+    const successCount = uploadingFiles.filter(f => f.status === 'completed').length;
     if (successCount > 0) {
       toast({
         title: 'Upload Complete',
@@ -559,6 +634,72 @@ export function FileManager({ projectId }: FileManagerProps) {
     }
   };
 
+  // 단일 파일 업로드 함수
+  const uploadSingleFile = async (file: File, index: number, parentId: string | null) => {
+    try {
+      // Update status: uploading
+      setUploadingFiles(prev => prev.map((f, idx) =>
+        idx === index ? { ...f, status: 'uploading', progress: 20 } : f
+      ));
+
+      // DRM decryption
+      setUploadingFiles(prev => prev.map((f, idx) =>
+        idx === index ? { ...f, status: 'processing', progress: 40 } : f
+      ));
+
+      const decryptedData = await decryptWithServerAction(file);
+
+      // Create 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 (parentId) {
+        formData.append('parentId', parentId);
+      }
+
+      // Update upload progress
+      setUploadingFiles(prev => prev.map((f, idx) =>
+        idx === index ? { ...f, progress: 60 } : f
+      ));
+
+      // API call
+      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');
+      }
+
+      // Success
+      setUploadingFiles(prev => prev.map((f, idx) =>
+        idx === index ? { ...f, status: 'completed', progress: 100 } : f
+      ));
+
+    } catch (error: any) {
+      // Failure
+      setUploadingFiles(prev => prev.map((f, idx) =>
+        idx === index ? {
+          ...f,
+          status: 'error',
+          error: error.message || 'Upload failed'
+        } : f
+      ));
+    }
+  };
+
+  // 업로드 시작 함수
+  const startUpload = async () => {
+    if (selectedFilesForUpload.length === 0) return;
+    await handleFileUpload(selectedFilesForUpload);
+    // 업로드 완료 후 선택된 파일 초기화
+    setSelectedFilesForUpload([]);
+  };
+
   // Download folder
   const downloadFolder = async (folder: FileItem) => {
     if (folder.type !== 'folder') return;
@@ -620,14 +761,13 @@ export function FileManager({ projectId }: FileManagerProps) {
     }
   };
 
-
   // Download multiple files
   const downloadMultipleFiles = async (itemIds: string[]) => {
     // Filter only actual files (exclude folders) that can be downloaded
     const filesToDownload = items.filter(item =>
       itemIds.includes(item.id) &&
       item.type === 'file' &&
-      item.permissions?.canDownload === 'true'
+      item.permissions?.canDownload
     );
 
     if (filesToDownload.length === 0) {
@@ -712,15 +852,9 @@ export function FileManager({ projectId }: FileManagerProps) {
   // View file with PDFTron
   const viewFile = async (file: FileItem) => {
     try {
-
-
-
-      setViewerFileUrl(file.filePath);
+      setViewerFileUrl(file.filePath || '');
       setSelectedFile(file);
       setViewerDialogOpen(true);
-
-
-
     } catch (error) {
       toast({
         title: 'Error',
@@ -858,11 +992,6 @@ export function FileManager({ projectId }: FileManagerProps) {
     }
   };
 
-  // Category change dialog states
-  const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
-  const [applyToChildren, setApplyToChildren] = useState(false);
-  const [newCategory, setNewCategory] = useState('confidential');
-
   // Handle folder double click
   const handleFolderOpen = (folder: FileItem) => {
     if (viewMode === 'grid') {
@@ -952,7 +1081,7 @@ export function FileManager({ projectId }: FileManagerProps) {
                   onClick={() => {
                     // 현재 폴더의 카테고리를 기본값으로 설정
                     if (currentParentId) {
-                      const currentFolder = items.find(item => item.parentId === currentParentId);
+                      const currentFolder = items.find(item => item.id === currentParentId);
                       if (currentFolder) {
                         setUploadCategory(currentFolder.category);
                       }
@@ -972,7 +1101,7 @@ export function FileManager({ projectId }: FileManagerProps) {
                 {items.filter(item =>
                   selectedItems.has(item.id) &&
                   item.type === 'file' &&
-                  item.permissions?.canDownload === 'true'
+                  item.permissions?.canDownload
                 ).length > 0 && (
                     <Button
                       size="sm"
@@ -1057,7 +1186,7 @@ export function FileManager({ projectId }: FileManagerProps) {
               <div className="flex justify-center items-center h-64">
                 <div className="text-muted-foreground">Loading...</div>
               </div>
-            ) : filteredItems.length === 0 ? (
+            ) : filteredItems.length === 0 && filteredTreeItems.length === 0 ? (
               <div className="flex flex-col items-center justify-center h-64">
                 <Folder className="h-12 w-12 text-muted-foreground mb-2" />
                 <p className="text-muted-foreground">Empty</p>
@@ -1081,6 +1210,8 @@ export function FileManager({ projectId }: FileManagerProps) {
                           onDoubleClick={() => {
                             if (item.type === 'folder') {
                               handleFolderOpen(item);
+                            } else {
+                              viewFile(item);
                             }
                           }}
                         >
@@ -1114,7 +1245,72 @@ export function FileManager({ projectId }: FileManagerProps) {
                         </div>
                       </ContextMenuTrigger>
 
-                      {/* ... ContextMenuContent는 동일 ... */}
+                      <ContextMenuContent>
+                        {item.type === 'file' && (
+                          <>
+                            <ContextMenuItem onClick={() => viewFile(item)}>
+                              <Eye className="h-4 w-4 mr-2" />
+                              View
+                            </ContextMenuItem>
+                            {item.permissions?.canDownload && (
+                              <ContextMenuItem onClick={() => downloadFile(item)}>
+                                <Download className="h-4 w-4 mr-2" />
+                                Download
+                              </ContextMenuItem>
+                            )}
+                          </>
+                        )}
+
+                        {item.type === 'folder' && (
+                          <>
+                            <ContextMenuItem onClick={() => handleFolderOpen(item)}>
+                              <Folder className="h-4 w-4 mr-2" />
+                              Open
+                            </ContextMenuItem>
+                            <ContextMenuItem onClick={() => downloadFolder(item)}>
+                              <Download className="h-4 w-4 mr-2" />
+                              Download Folder
+                            </ContextMenuItem>
+                          </>
+                        )}
+
+                        {isInternalUser && (
+                          <>
+                            <ContextMenuSeparator />
+                            {item.permissions?.canEdit && (
+                              <ContextMenuItem onClick={() => {
+                                setSelectedFile(item);
+                                setDialogValue(item.name);
+                                setRenameDialogOpen(true);
+                              }}>
+                                <Edit2 className="h-4 w-4 mr-2" />
+                                Rename
+                              </ContextMenuItem>
+                            )}
+                            <ContextMenuItem onClick={() => {
+                              setSelectedFile(item);
+                              setNewCategory(item.category);
+                              setCategoryDialogOpen(true);
+                            }}>
+                              <Shield className="h-4 w-4 mr-2" />
+                              Change Category
+                            </ContextMenuItem>
+                          </>
+                        )}
+
+                        {item.permissions?.canDelete && (
+                          <>
+                            <ContextMenuSeparator />
+                            <ContextMenuItem
+                              className="text-destructive"
+                              onClick={() => deleteItems([item.id])}
+                            >
+                              <Trash2 className="h-4 w-4 mr-2" />
+                              Delete
+                            </ContextMenuItem>
+                          </>
+                        )}
+                      </ContextMenuContent>
                     </ContextMenu>
                   );
                 })}
@@ -1145,6 +1341,11 @@ export function FileManager({ projectId }: FileManagerProps) {
                       setDialogValue(item.name);
                       setRenameDialogOpen(true);
                     }}
+                    onCategoryChange={(item) => {
+                      setSelectedFile(item);
+                      setNewCategory(item.category);
+                      setCategoryDialogOpen(true);
+                    }}
                     isInternalUser={isInternalUser}
                   />
                 ))}
@@ -1154,13 +1355,20 @@ export function FileManager({ projectId }: FileManagerProps) {
         </ScrollArea>
       </div>
 
-      {/* Upload Dialog */}
-      <Dialog open={uploadDialogOpen} onOpenChange={setUploadDialogOpen}>
+      {/* Upload Dialog - 수정된 부분 */}
+      <Dialog open={uploadDialogOpen} onOpenChange={(open) => {
+        setUploadDialogOpen(open);
+        if (!open) {
+          setSelectedFilesForUpload([]);
+          setUploadingFiles([]);
+          setPreserveFolderStructure(false);
+        }
+      }}>
         <DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
           <DialogHeader>
             <DialogTitle>Upload Files</DialogTitle>
             <DialogDescription>
-              Drag and drop files or click to select.
+              Drag and drop files or click to select. You can also select entire folders.
             </DialogDescription>
           </DialogHeader>
 
@@ -1176,15 +1384,12 @@ export function FileManager({ projectId }: FileManagerProps) {
                   <SelectContent>
                     {Object.entries(categoryConfig)
                       .filter(([key]) => {
-                        // 현재 폴더가 있는 경우
                         if (currentParentId) {
-                          const currentFolder = items.find(item => item.parentId === currentParentId);
-                          // 현재 폴더가 public이 아니면 public 옵션 제외
+                          const currentFolder = items.find(item => item.id === currentParentId);
                           if (currentFolder && currentFolder.category !== 'public') {
                             return key !== 'public';
                           }
                         }
-                        // 루트 폴더이거나 현재 폴더가 public인 경우 모든 옵션 표시
                         return true;
                       })
                       .map(([key, config]) => (
@@ -1197,57 +1402,223 @@ export function FileManager({ projectId }: FileManagerProps) {
                       ))}
                   </SelectContent>
                 </Select>
-                {/* 현재 폴더 정보 표시 (선택사항) */}
-                {currentParentId && (() => {
-                  const currentFolder = items.find(item => item.parentId === currentParentId);
-                  if (currentFolder && currentFolder.category !== 'public') {
-                    return (
-                      <p className="text-xs text-muted-foreground mt-1 flex items-center">
-                        <AlertCircle className="h-3 w-3 mr-1" />
-                        Current folder is {categoryConfig[currentFolder.category].label}.
-                        Public uploads are not allowed.
-                      </p>
-                    );
-                  }
-                })()}
               </div>
 
-              {/* Dropzone */}
-              <Dropzone
-                onDrop={(acceptedFiles: File[]) => {
-                  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}
-              >
-                <DropzoneZone className="h-48 border-2 border-dashed border-gray-300 rounded-lg">
-                  <DropzoneInput />
-                  <div className="flex flex-col items-center justify-center h-full">
-                    <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" />
-                    <DropzoneTitle>Drag files or click to upload</DropzoneTitle>
-                    <DropzoneDescription>Multiple files can be uploaded simultaneously</DropzoneDescription>
+              {/* Preserve Folder Structure Option - 폴더가 선택된 경우만 표시 */}
+              {selectedFilesForUpload.some((file: any) => file.webkitRelativePath) && (
+                <div className="flex items-center space-x-2 p-3 bg-muted rounded-lg">
+                  <Switch
+                    id="preserve-folder"
+                    checked={preserveFolderStructure}
+                    onCheckedChange={setPreserveFolderStructure}
+                  />
+                  <Label htmlFor="preserve-folder" className="cursor-pointer">
+                    <div className="font-medium">Preserve folder structure</div>
+                    <div className="text-xs text-muted-foreground">
+                      Create folders and maintain the original directory structure
+                    </div>
+                  </Label>
+                </div>
+              )}
+
+              {/* Dropzone - 파일이 선택되지 않았을 때만 표시 */}
+              {selectedFilesForUpload.length === 0 && uploadingFiles.length === 0 && (
+                <div
+                  className="h-48 border-2 border-dashed border-gray-300 rounded-lg flex flex-col items-center justify-center relative"
+                  onDragOver={(e) => {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    e.currentTarget.classList.add('border-primary', 'bg-accent');
+                  }}
+                  onDragLeave={(e) => {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    e.currentTarget.classList.remove('border-primary', 'bg-accent');
+                  }}
+                  onDrop={async (e) => {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    e.currentTarget.classList.remove('border-primary', 'bg-accent');
+                    
+                    const items = e.dataTransfer.items;
+                    const files: File[] = [];
+                    const filePromises: Promise<void>[] = [];
+                    
+                    // 폴더 구조 감지를 위한 플래그
+                    let hasFolderStructure = false;
+                    
+                    // DataTransferItem을 통한 폴더 처리
+                    for (let i = 0; i < items.length; i++) {
+                      const item = items[i];
+                      
+                      if (item.kind === 'file') {
+                        const entry = item.webkitGetAsEntry();
+                        
+                        if (entry) {
+                          filePromises.push(
+                            new Promise<void>(async (resolve) => {
+                              const traverseFileTree = async (item: any, path: string = '') => {
+                                if (item.isFile) {
+                                  await new Promise<void>((fileResolve) => {
+                                    item.file((file: File) => {
+                                      // 파일에 상대 경로 추가
+                                      Object.defineProperty(file, 'webkitRelativePath', {
+                                        value: path + file.name,
+                                        writable: false
+                                      });
+                                      files.push(file);
+                                      fileResolve();
+                                    });
+                                  });
+                                } else if (item.isDirectory) {
+                                  hasFolderStructure = true;
+                                  const dirReader = item.createReader();
+                                  await new Promise<void>((dirResolve) => {
+                                    dirReader.readEntries(async (entries: any[]) => {
+                                      for (const entry of entries) {
+                                        await traverseFileTree(entry, path + item.name + '/');
+                                      }
+                                      dirResolve();
+                                    });
+                                  });
+                                }
+                              };
+                              
+                              await traverseFileTree(entry);
+                              resolve();
+                            })
+                          );
+                        }
+                      }
+                    }
+                    
+                    // 모든 파일 처리 완료 대기
+                    await Promise.all(filePromises);
+                    
+                    // 파일이 없으면 일반 파일 처리 (폴더가 아닌 경우)
+                    if (files.length === 0) {
+                      const droppedFiles = Array.from(e.dataTransfer.files);
+                      if (droppedFiles.length > 0) {
+                        setSelectedFilesForUpload(droppedFiles);
+                      }
+                    } else {
+                      // 폴더 구조가 있으면 자동으로 옵션 활성화
+                      if (hasFolderStructure) {
+                        setPreserveFolderStructure(true);
+                      }
+                      setSelectedFilesForUpload(files);
+                    }
+                  }}
+                >
+                  <input
+                    type="file"
+                    multiple
+                    className="hidden"
+                    id="file-input"
+                    onChange={(e) => {
+                      const files = Array.from(e.target.files || []);
+                      setSelectedFilesForUpload(files);
+                    }}
+                  />
+                  <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" />
+                  <div className="text-center">
+                    <p className="font-medium text-lg">Drag folders or files here</p>
+                    <p className="text-sm text-muted-foreground mt-1">
+                      Folders will maintain their structure
+                    </p>
                   </div>
-                </DropzoneZone>
-              </Dropzone>
+                  {/* <div className="mt-4 flex gap-2">
+                    <Button
+                      variant="outline"
+                      size="sm"
+                      onClick={() => document.getElementById('file-input')?.click()}
+                    >
+                      <File className="h-4 w-4 mr-2" />
+                      Browse Files
+                    </Button>
+                    <label htmlFor="folder-upload-btn" className="cursor-pointer">
+                      <input
+                        id="folder-upload-btn"
+                        type="file"
+                        // @ts-ignore
+                        webkitdirectory=""
+                        directory=""
+                        multiple
+                        className="hidden"
+                        onChange={(e) => {
+                          const files = Array.from(e.target.files || []);
+                          setSelectedFilesForUpload(files);
+                          if (files.length > 0 && (files[0] as any).webkitRelativePath) {
+                            setPreserveFolderStructure(true);
+                          }
+                        }}
+                      />
+                      <Button
+                        variant="outline"
+                        size="sm"
+                        type="button"
+                        as="div"
+                      >
+                        <Folder className="h-4 w-4 mr-2" />
+                        Browse Folder
+                      </Button>
+                    </label>
+                  </div> */}
+                </div>
+              )}
 
-              {/* Uploading File List */}
+              {/* Selected Files List - 업로드 전 */}
+              {selectedFilesForUpload.length > 0 && uploadingFiles.length === 0 && (
+                <div className="border rounded-lg p-4">
+                  <div className="flex items-center justify-between mb-3">
+                    <h4 className="font-medium text-sm">
+                      Selected Files ({selectedFilesForUpload.length})
+                    </h4>
+                    <Button
+                      size="sm"
+                      variant="ghost"
+                      onClick={() => {
+                        setSelectedFilesForUpload([]);
+                        setPreserveFolderStructure(false);
+                      }}
+                    >
+                      <X className="h-4 w-4 mr-1" />
+                      Clear All
+                    </Button>
+                  </div>
+                  <div className="space-y-2 max-h-[300px] overflow-y-auto">
+                    {selectedFilesForUpload.map((file, index) => {
+                      const relativePath = (file as any).webkitRelativePath || file.name;
+                      return (
+                        <div key={index} className="flex items-center gap-3 p-2 bg-muted/50 rounded-md">
+                          <File className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
+                          <div className="flex-1 min-w-0">
+                            <p className="text-sm truncate" title={relativePath}>
+                              {relativePath}
+                            </p>
+                            <span className="text-xs text-muted-foreground">
+                              {formatFileSize(file.size)}
+                            </span>
+                          </div>
+                          <Button
+                            size="sm"
+                            variant="ghost"
+                            onClick={() => {
+                              setSelectedFilesForUpload(prev =>
+                                prev.filter((_, i) => i !== index)
+                              );
+                            }}
+                          >
+                            <X className="h-3 w-3" />
+                          </Button>
+                        </div>
+                      );
+                    })}
+                  </div>
+                </div>
+              )}
+
+              {/* Uploading File List - 업로드 중 */}
               {uploadingFiles.length > 0 && (
                 <div className="border rounded-lg p-4 bg-muted/50">
                   <div className="flex items-center justify-between mb-3">
@@ -1290,19 +1661,6 @@ export function FileManager({ projectId }: FileManagerProps) {
                             <Progress value={uploadFile.progress} className="h-1.5 mt-2" />
                           )}
                         </div>
-                        {uploadFile.status === 'error' && (
-                          <Button
-                            size="sm"
-                            variant="ghost"
-                            onClick={() => {
-                              setUploadingFiles(prev =>
-                                prev.filter((_, i) => i !== index)
-                              );
-                            }}
-                          >
-                            <X className="h-4 w-4" />
-                          </Button>
-                        )}
                       </div>
                     ))}
                   </div>
@@ -1316,11 +1674,30 @@ export function FileManager({ projectId }: FileManagerProps) {
               variant="outline"
               onClick={() => {
                 setUploadDialogOpen(false);
+                setSelectedFilesForUpload([]);
                 setUploadingFiles([]);
+                setPreserveFolderStructure(false);
               }}
             >
-              Close
+              Cancel
             </Button>
+            {/* 파일이 선택되었고 업로드 중이 아닐 때만 Upload 버튼 표시 */}
+            {selectedFilesForUpload.length > 0 && uploadingFiles.length === 0 && (
+              <Button onClick={startUpload}>
+                <Upload className="h-4 w-4 mr-2" />
+                Upload {selectedFilesForUpload.length} File{selectedFilesForUpload.length > 1 ? 's' : ''}
+              </Button>
+            )}
+            {/* 업로드 완료 후 Done 버튼 */}
+            {uploadingFiles.length > 0 && uploadingFiles.every(f => f.status === 'completed' || f.status === 'error') && (
+              <Button onClick={() => {
+                setUploadDialogOpen(false);
+                setUploadingFiles([]);
+                setSelectedFilesForUpload([]);
+              }}>
+                Done
+              </Button>
+            )}
           </DialogFooter>
         </DialogContent>
       </Dialog>
@@ -1375,7 +1752,6 @@ export function FileManager({ projectId }: FileManagerProps) {
         </DialogContent>
       </Dialog>
 
-
       {/* Rename Dialog */}
       <Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
         <DialogContent>
@@ -1429,13 +1805,13 @@ export function FileManager({ projectId }: FileManagerProps) {
         </DialogContent>
       </Dialog>
 
-      {/* Category Change Dialog (for folders) */}
+      {/* Category Change Dialog */}
       <Dialog open={categoryDialogOpen} onOpenChange={setCategoryDialogOpen}>
         <DialogContent>
           <DialogHeader>
             <DialogTitle>Change Category</DialogTitle>
             <DialogDescription>
-              Changing category for {selectedFile?.name} folder.
+              Changing category for {selectedFile?.name}.
             </DialogDescription>
           </DialogHeader>
 
@@ -1506,7 +1882,9 @@ export function FileManager({ projectId }: FileManagerProps) {
             <Button
               onClick={() => {
                 if (selectedFile) {
-                  changeCategory(selectedFile.id, newCategory, applyToChildren);
+                  changeCategory(selectedFile.id, newCategory, 
+                    selectedFile.type === 'folder' && newCategory !== 'public' ? true : applyToChildren
+                  );
                   setCategoryDialogOpen(false);
                   setSelectedFile(null);
                   setApplyToChildren(false);
-- 
cgit v1.2.3

