import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; import type { UdbContext } from './UnifiedDataBar'; import api from '../../api'; import FolderTree from '../../components/FolderTree/FolderTree'; import type { FileNode } from '../../components/FolderTree/FolderTree'; import { useFileContext } from '../../contexts/FileContext'; import styles from './FilesTab.module.css'; interface FileEntry { id: string; fileName: string; mimeType?: string; fileSize?: number; folderId?: string | null; tags?: string[]; scope: string; neutralize: boolean; } interface FilesTabProps { context: UdbContext; onFileSelect?: (fileId: string) => void; } const FilesTab: React.FC = ({ context, onFileSelect }) => { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [isDragOver, setIsDragOver] = useState(false); const [uploading, setUploading] = useState(false); const [selectedFolderId, setSelectedFolderId] = useState(null); const fileInputRef = useRef(null); const { folders, refreshFolders, handleCreateFolder, handleRenameFolder, handleDeleteFolder, handleMoveFolder, handleMoveFolders, handleMoveFile, handleMoveFiles: contextMoveFiles, handleFileDelete, handleDownloadFolder, expandedFolderIds, toggleFolderExpanded, } = useFileContext(); const _loadFiles = useCallback(async () => { setLoading(true); try { const response = await api.get(`/api/workspace/${context.instanceId}/files`); const body = response.data; const rawList = (Array.isArray(body?.files) && body.files) || (Array.isArray(body?.data) && body.data) || (Array.isArray(body) ? body : []); setFiles( rawList.map((f: any) => ({ id: f.id, fileName: f.fileName || f.name || 'unknown', mimeType: f.mimeType, fileSize: f.fileSize, folderId: f.folderId ?? null, tags: f.tags || [], scope: f.scope || 'personal', neutralize: f.neutralize || false, })), ); } catch (err) { console.error('Failed to load files:', err); } finally { setLoading(false); } }, [context.instanceId]); useEffect(() => { _loadFiles(); }, [_loadFiles]); const _folderNodes = useMemo(() => folders.map(f => ({ id: f.id, name: f.name, parentId: f.parentId ?? null, })), [folders], ); const _fileNodes: FileNode[] = useMemo(() => { let result = files; if (searchQuery.trim()) { const q = searchQuery.toLowerCase(); result = result.filter(f => f.fileName.toLowerCase().includes(q) || (f.tags || []).some((t: string) => t.toLowerCase().includes(q)), ); } return result .sort((a, b) => a.fileName.localeCompare(b.fileName)) .map(f => ({ id: f.id, fileName: f.fileName, mimeType: f.mimeType, fileSize: f.fileSize, folderId: f.folderId ?? null, scope: f.scope, neutralize: f.neutralize, })); }, [files, searchQuery]); const _refreshAll = useCallback(() => { _loadFiles(); refreshFolders(); }, [_loadFiles, refreshFolders]); const _uploadFiles = useCallback(async (fileList: FileList | File[]) => { if (!context.instanceId || uploading) return; setUploading(true); try { for (const file of Array.from(fileList)) { const formData = new FormData(); formData.append('file', file); formData.append('featureInstanceId', context.instanceId); await api.post('/api/files/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); } _refreshAll(); } catch (err) { console.error('File upload failed:', err); } finally { setUploading(false); } }, [context.instanceId, uploading, _refreshAll]); const _handleDragOver = useCallback((e: React.DragEvent) => { if (e.dataTransfer.types.includes('Files')) { e.preventDefault(); e.stopPropagation(); setIsDragOver(true); } }, []); const _handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); }, []); const _handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); if (e.dataTransfer.files.length > 0) { _uploadFiles(e.dataTransfer.files); } }, [_uploadFiles]); const _handleFileInputChange = useCallback((e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { _uploadFiles(e.target.files); e.target.value = ''; } }, [_uploadFiles]); const _onMoveFile = useCallback(async (fileId: string, targetFolderId: string | null) => { await handleMoveFile(fileId, targetFolderId); _loadFiles(); }, [handleMoveFile, _loadFiles]); const _onMoveFiles = useCallback(async (fileIds: string[], targetFolderId: string | null) => { await contextMoveFiles(fileIds, targetFolderId); _loadFiles(); }, [contextMoveFiles, _loadFiles]); const _onDeleteFolder = useCallback(async (folderId: string) => { await handleDeleteFolder(folderId); if (selectedFolderId === folderId) setSelectedFolderId(null); _loadFiles(); }, [handleDeleteFolder, selectedFolderId, _loadFiles]); const _onRenameFile = useCallback(async (fileId: string, newName: string) => { await api.put(`/api/files/${fileId}`, { fileName: newName }); _loadFiles(); }, [_loadFiles]); const _onDeleteFile = useCallback(async (fileId: string) => { await handleFileDelete(fileId); _loadFiles(); }, [handleFileDelete, _loadFiles]); const _onDeleteFiles = useCallback(async (fileIds: string[]) => { await api.post('/api/files/batch-delete', { fileIds }); _loadFiles(); }, [_loadFiles]); const _onDeleteFolders = useCallback(async (folderIds: string[]) => { await api.post('/api/files/batch-delete', { folderIds, recursiveFolders: true }); refreshFolders(); _loadFiles(); }, [refreshFolders, _loadFiles]); const _onScopeChange = useCallback(async (fileId: string, newScope: string) => { setFiles(prev => prev.map(f => (f.id === fileId ? { ...f, scope: newScope } : f))); try { await api.patch(`/api/files/${fileId}/scope`, { scope: newScope }); } catch (err) { console.error('Failed to update scope:', err); _loadFiles(); } }, [_loadFiles]); const _onNeutralizeToggle = useCallback(async (fileId: string, newValue: boolean) => { setFiles(prev => prev.map(f => (f.id === fileId ? { ...f, neutralize: newValue } : f))); try { await api.patch(`/api/files/${fileId}/neutralize`, { neutralize: newValue }); } catch (err) { console.error('Failed to toggle neutralize:', err); _loadFiles(); } }, [_loadFiles]); if (loading) return
Lade Dateien...
; return (
{isDragOver && (
Dateien hier ablegen
)}
Files
setSearchQuery(e.target.value)} style={{ width: '100%', padding: '6px 10px', fontSize: 12, borderRadius: 4, border: '1px solid #ddd', boxSizing: 'border-box', margin: '0 0 4px', }} />
{_fileNodes.length === 0 && (
{searchQuery ? 'Keine Dateien gefunden' : 'Keine Dateien. Drag & Drop zum Hochladen.'}
)}
{'\uD83D\uDC64'} Persönlich {'\uD83D\uDC65'} Instanz {'\uD83C\uDFE2'} Mandant {'\uD83D\uDD12'} Neutralisiert
); }; export default FilesTab;