summaryrefslogtreecommitdiff
path: root/components/file-manager/FileManager.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-29 07:46:57 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-29 07:46:57 +0000
commitbbc3094e932e3d193d3223448c789461f4afc058 (patch)
treef28dd034b191ca78d0af15eccbbdf7a952141153 /components/file-manager/FileManager.tsx
parentd28c43b2d33bac51c69ac7417a14f9fe83f2a25f (diff)
(대표님) 데이터룸 관련 변경사항
Diffstat (limited to 'components/file-manager/FileManager.tsx')
-rw-r--r--components/file-manager/FileManager.tsx156
1 files changed, 131 insertions, 25 deletions
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' && (
- <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
- <Download className="h-4 w-4 mr-2" />
- Download Folder
- </DropdownMenuItem>
+ <>
+ {/* Create Sub-folder 추가 */}
+ {isInternalUser && (
+ <DropdownMenuItem onClick={() => onCreateSubfolder(item)}>
+ <FolderPlus className="h-4 w-4 mr-2" />
+ Create Sub-folder
+ </DropdownMenuItem>
+ )}
+ <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
+ <Download className="h-4 w-4 mr-2" />
+ Download Folder
+ </DropdownMenuItem>
+ </>
)}
{isInternalUser && (
@@ -320,6 +332,13 @@ const TreeItem: React.FC<{
{item.type === 'folder' && (
<>
+ {/* Create Sub-folder 추가 (Context Menu) */}
+ {isInternalUser && (
+ <ContextMenuItem onClick={() => onCreateSubfolder(item)}>
+ <FolderPlus className="h-4 w-4 mr-2" />
+ Create Sub-folder
+ </ContextMenuItem>
+ )}
<ContextMenuItem onClick={() => onDoubleClick(item)}>
<Folder className="h-4 w-4 mr-2" />
{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<UploadingFile[]>([]);
@@ -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<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 {
@@ -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<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) => {
@@ -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) {
</DialogContent>
</Dialog>
+ <Dialog open={subfolderDialogOpen} onOpenChange={setSubfolderDialogOpen}>
+ <DialogContent className="sm:max-w-[425px]">
+ <DialogHeader>
+ <DialogTitle>Create Sub-folder</DialogTitle>
+ <DialogDescription>
+ {selectedParentFolder ? (
+ <>
+ Create a new sub-folder under <span className="font-medium">{selectedParentFolder.name}</span>
+ </>
+ ) : (
+ "Create a new folder in the current directory"
+ )}
+ </DialogDescription>
+ </DialogHeader>
+
+ <CreateSubfolderForm
+ parentFolder={selectedParentFolder}
+ onSubmit={createSubfolder}
+ onCancel={() => setSubfolderDialogOpen(false)}
+ />
+ </DialogContent>
+ </Dialog>
+
{/* Rename Dialog */}
<Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
<DialogContent>
@@ -1908,7 +2014,7 @@ export function FileManager({ projectId }: FileManagerProps) {
<Button
onClick={() => {
if (selectedFile) {
- changeCategory(selectedFile.id, newCategory,
+ changeCategory(selectedFile.id, newCategory,
selectedFile.type === 'folder' && newCategory !== 'public' ? true : applyToChildren
);
setCategoryDialogOpen(false);