From bbc3094e932e3d193d3223448c789461f4afc058 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 29 Oct 2025 07:46:57 +0000 Subject: (대표님) 데이터룸 관련 변경사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/file-manager/FileManager.tsx | 156 +++++++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 25 deletions(-) (limited to 'components/file-manager/FileManager.tsx') diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx index a95d8c06..c56bb16a 100644 --- a/components/file-manager/FileManager.tsx +++ b/components/file-manager/FileManager.tsx @@ -95,6 +95,7 @@ import { decryptWithServerAction } from '@/components/drm/drmUtils'; import { Progress } from '@/components/ui/progress'; // Import the secure viewer component import { SecurePDFViewer } from './SecurePDFViewer'; +import { CreateSubfolderForm } from './CreateSubfolderForm'; interface FileItem { id: string; @@ -153,6 +154,7 @@ const TreeItem: React.FC<{ onShare: (item: FileItem) => void; onRename: (item: FileItem) => void; onCategoryChange: (item: FileItem) => void; + onCreateSubfolder: (item: FileItem) => void; // 추가 isInternalUser: boolean; }> = ({ item, @@ -169,6 +171,7 @@ const TreeItem: React.FC<{ onShare, onRename, onCategoryChange, + onCreateSubfolder, // 추가 isInternalUser }) => { const hasChildren = item.type === 'folder' && item.children && item.children.length > 0; @@ -263,10 +266,19 @@ const TreeItem: React.FC<{ )} {item.type === 'folder' && ( - onDownloadFolder(item)}> - - Download Folder - + <> + {/* Create Sub-folder 추가 */} + {isInternalUser && ( + onCreateSubfolder(item)}> + + Create Sub-folder + + )} + onDownloadFolder(item)}> + + Download Folder + + )} {isInternalUser && ( @@ -320,6 +332,13 @@ const TreeItem: React.FC<{ {item.type === 'folder' && ( <> + {/* Create Sub-folder 추가 (Context Menu) */} + {isInternalUser && ( + onCreateSubfolder(item)}> + + Create Sub-folder + + )} onDoubleClick(item)}> {isExpanded ? 'Collapse' : 'Expand'} @@ -381,6 +400,7 @@ const TreeItem: React.FC<{ onShare={onShare} onRename={onRename} onCategoryChange={onCategoryChange} + onCreateSubfolder={onCreateSubfolder} // 추가 isInternalUser={isInternalUser} /> ))} @@ -402,6 +422,59 @@ export function FileManager({ projectId }: FileManagerProps) { const [searchQuery, setSearchQuery] = useState(''); const [loading, setLoading] = useState(false); + const [subfolderDialogOpen, setSubfolderDialogOpen] = useState(false); + const [selectedParentFolder, setSelectedParentFolder] = useState(null); + const [newFolderName, setNewFolderName] = useState(''); + const [newFolderCategory, setNewFolderCategory] = useState('general'); + + const handleCreateSubfolder = (parentFolder) => { + setSelectedParentFolder(parentFolder); + setSubfolderDialogOpen(true); + }; + + const createSubfolder = async (name, category) => { + try { + const response = await fetch(`/api/data-room/${projectId}/folders`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name, + parentId: selectedParentFolder?.id || null, + category: category || selectedParentFolder?.category || 'general', + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Failed to create folder'); + } + + const newFolder = await response.json(); + + // 기존 fetchItems 함수를 재사용하여 목록 새로고침 + await fetchItems(); // 기존에 있던 fetchItems 함수 재사용 + + toast({ + title: "Folder Created", + description: `Folder "${name}" has been created successfully.`, + }); + + setSubfolderDialogOpen(false); + setSelectedParentFolder(null); + + return newFolder; + } catch (error) { + toast({ + title: "Failed to Create Folder", + description: error.message || "An error occurred while creating the folder.", + variant: "destructive", + }); + throw error; + } + }; + // Upload states const [uploadDialogOpen, setUploadDialogOpen] = useState(false); const [uploadingFiles, setUploadingFiles] = useState([]); @@ -419,6 +492,10 @@ export function FileManager({ projectId }: FileManagerProps) { const [categoryDialogOpen, setCategoryDialogOpen] = useState(false); const [applyToChildren, setApplyToChildren] = useState(false); const [newCategory, setNewCategory] = useState('confidential'); + const [subfolderDialog, setSubfolderDialog] = useState<{ + open: boolean; + parentFolder: FileItem | null; + }>({ open: false, parentFolder: null }); // Dialog data const [dialogValue, setDialogValue] = useState(''); @@ -564,22 +641,22 @@ export function FileManager({ projectId }: FileManagerProps) { if (preserveFolderStructure && fileArray.some((file: any) => file.webkitRelativePath)) { // 폴더 구조를 먼저 생성 const folderMap = new Map(); // 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 { @@ -593,7 +670,7 @@ export function FileManager({ projectId }: FileManagerProps) { parentId: parentId, }), }); - + if (response.ok) { const newFolder = await response.json(); folderMap.set(currentFolderPath, newFolder.id); @@ -606,7 +683,7 @@ export function FileManager({ projectId }: FileManagerProps) { parentId = folderMap.get(currentFolderPath) || null; } } - + // 폴더가 생성되었으면 해당 폴더에 파일 업로드 await uploadSingleFile(file, i, parentId); } else { @@ -851,7 +928,7 @@ export function FileManager({ projectId }: FileManagerProps) { // View file with PDFTron const viewFile = async (file: FileItem) => { - try { + try { setViewerFileUrl(file.filePath || ''); setSelectedFile(file); setViewerDialogOpen(true); @@ -1051,17 +1128,17 @@ export function FileManager({ projectId }: FileManagerProps) { // 재귀적으로 트리 항목 검색 const searchTreeItems = (items: FileItem[], query: string): FileItem[] => { const result: FileItem[] = []; - + for (const item of items) { // 현재 항목이 검색어와 일치하는지 확인 const matches = item.name.toLowerCase().includes(query.toLowerCase()); - + // 하위 항목 재귀적으로 검색 let childrenMatches: FileItem[] = []; if (item.children && item.children.length > 0) { childrenMatches = searchTreeItems(item.children, query); } - + // 현재 항목이나 하위 항목 중 하나라도 일치하면 결과에 추가 if (matches || childrenMatches.length > 0) { // 하위 항목이 일치하는 경우 현재 항목도 표시하기 위해 확장된 상태로 복제 @@ -1072,7 +1149,7 @@ export function FileManager({ projectId }: FileManagerProps) { result.push(clonedItem); } } - + return result; }; @@ -1362,6 +1439,12 @@ export function FileManager({ projectId }: FileManagerProps) { setSelectedFile(item); setShareDialogOpen(true); }} + onCreateSubfolder={(item) => { + setSelectedParentFolder(item); + setNewFolderName(''); + setNewFolderCategory(item.category || 'general'); + setSubfolderDialogOpen(true); // setCreateSubfolderDialogOpen 대신 setSubfolderDialogOpen 사용 + }} onRename={(item) => { setSelectedFile(item); setDialogValue(item.name); @@ -1465,21 +1548,21 @@ export function FileManager({ projectId }: FileManagerProps) { e.preventDefault(); e.stopPropagation(); e.currentTarget.classList.remove('border-primary', 'bg-accent'); - + const items = e.dataTransfer.items; const files: File[] = []; const filePromises: Promise[] = []; - + // 폴더 구조 감지를 위한 플래그 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(async (resolve) => { @@ -1509,7 +1592,7 @@ export function FileManager({ projectId }: FileManagerProps) { }); } }; - + await traverseFileTree(entry); resolve(); }) @@ -1517,10 +1600,10 @@ export function FileManager({ projectId }: FileManagerProps) { } } } - + // 모든 파일 처리 완료 대기 await Promise.all(filePromises); - + // 파일이 없으면 일반 파일 처리 (폴더가 아닌 경우) if (files.length === 0) { const droppedFiles = Array.from(e.dataTransfer.files); @@ -1778,6 +1861,29 @@ export function FileManager({ projectId }: FileManagerProps) { + + + + Create Sub-folder + + {selectedParentFolder ? ( + <> + Create a new sub-folder under {selectedParentFolder.name} + + ) : ( + "Create a new folder in the current directory" + )} + + + + setSubfolderDialogOpen(false)} + /> + + + {/* Rename Dialog */} @@ -1908,7 +2014,7 @@ export function FileManager({ projectId }: FileManagerProps) {