import React, { useState, useCallback, useRef, useMemo } 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'; import { useLanguage } from '../../providers/language/LanguageContext'; interface FilesTabProps { context: UdbContext; onFileSelect?: (fileId: string, fileName?: string) => void; onSendToChat?: (items: Array<{ id: string; type: 'file' | 'folder'; name: string }>) => void; } const FilesTab: React.FC = ({ context, onFileSelect, onSendToChat }) => { const { t } = useLanguage(); 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, treeFileNodes, treeFilesLoading, refreshTreeFiles, updateTreeFileNode, expandedFolderIds, toggleFolderExpanded, handleCreateFolder, handleRenameFolder, handleDeleteFolder, handleMoveFolder, handleMoveFolders, handleMoveFile, handleMoveFiles: contextMoveFiles, handleFileDelete, handleDownloadFolder, } = useFileContext(); const _folderNodes = useMemo(() => { return folders.map(f => ({ id: f.id, name: f.name, parentId: f.parentId ?? null, fileCount: f.fileCount ?? 0, neutralize: f.neutralize ?? false, scope: f.scope ?? 'personal', })); }, [folders]); const _fileNodes: FileNode[] = useMemo(() => { let result = treeFileNodes; if (searchQuery.trim()) { const q = searchQuery.toLowerCase(); result = result.filter(f => f.fileName.toLowerCase().includes(q), ); } return result; }, [treeFileNodes, searchQuery]); const _refreshAll = useCallback(async () => { await Promise.all([refreshTreeFiles(), refreshFolders()]); }, [refreshTreeFiles, 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' }, }); } await _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); }, [handleMoveFile]); const _onMoveFiles = useCallback(async (fileIds: string[], targetFolderId: string | null) => { await contextMoveFiles(fileIds, targetFolderId); }, [contextMoveFiles]); const _onDeleteFolder = useCallback(async (folderId: string) => { await handleDeleteFolder(folderId); if (selectedFolderId === folderId) setSelectedFolderId(null); }, [handleDeleteFolder, selectedFolderId]); const _onRenameFile = useCallback(async (fileId: string, newName: string) => { await api.put(`/api/files/${fileId}`, { fileName: newName }); await refreshTreeFiles(); }, [refreshTreeFiles]); const _onDeleteFile = useCallback(async (fileId: string) => { await handleFileDelete(fileId); }, [handleFileDelete]); const _onDeleteFiles = useCallback(async (fileIds: string[]) => { await api.post('/api/files/batch-delete', { fileIds }); await Promise.all([refreshTreeFiles(), refreshFolders()]); }, [refreshTreeFiles, refreshFolders]); const _onDeleteFolders = useCallback(async (folderIds: string[]) => { await api.post('/api/files/batch-delete', { folderIds, recursiveFolders: true }); await Promise.all([refreshFolders(), refreshTreeFiles()]); }, [refreshFolders, refreshTreeFiles]); const _onScopeChange = useCallback(async (fileId: string, newScope: string) => { updateTreeFileNode(fileId, { scope: newScope }); try { await api.patch(`/api/files/${fileId}/scope`, { scope: newScope }); } catch (err) { console.error('Failed to update scope:', err); await refreshTreeFiles(); } }, [updateTreeFileNode, refreshTreeFiles]); const _onNeutralizeToggle = useCallback(async (fileId: string, newValue: boolean) => { updateTreeFileNode(fileId, { neutralize: newValue }); try { await api.patch(`/api/files/${fileId}/neutralize`, { neutralize: newValue }); } catch (err) { console.error('Failed to toggle neutralize:', err); await refreshTreeFiles(); } }, [updateTreeFileNode, refreshTreeFiles]); const _onFolderNeutralizeToggle = useCallback(async (folderId: string, newValue: boolean) => { try { await api.patch(`/api/files/folders/${folderId}/neutralize`, { neutralize: newValue }); await refreshFolders(); await refreshTreeFiles(); } catch (err) { console.error('Failed to toggle folder neutralize:', err); } }, [refreshFolders, refreshTreeFiles]); const _onFolderScopeChange = useCallback(async (folderId: string, newScope: string) => { try { await api.patch(`/api/files/folders/${folderId}/scope`, { scope: newScope }); await refreshFolders(); await refreshTreeFiles(); } catch (err) { console.error('Failed to change folder scope:', err); } }, [refreshFolders, refreshTreeFiles]); if (treeFilesLoading && treeFileNodes.length === 0) { return
{t('Dateien laden')}
; } return (
{isDragOver && (
{t('Dateien hier ablegen')}
)}
{t('Dateien')}
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', }} />
{ const file = treeFileNodes.find(f => f.id === fileId); onFileSelect(fileId, file?.fileName); } : undefined} expandedIds={expandedFolderIds} onToggleExpand={toggleFolderExpanded} onRefresh={_refreshAll} onCreateFolder={handleCreateFolder} onRenameFolder={handleRenameFolder} onDeleteFolder={_onDeleteFolder} onMoveFolder={handleMoveFolder} onMoveFolders={handleMoveFolders} onMoveFile={_onMoveFile} onMoveFiles={_onMoveFiles} onRenameFile={_onRenameFile} onDeleteFile={_onDeleteFile} onDeleteFiles={_onDeleteFiles} onDeleteFolders={_onDeleteFolders} onDownloadFolder={handleDownloadFolder} onScopeChange={_onScopeChange} onNeutralizeToggle={_onNeutralizeToggle} onFolderScopeChange={_onFolderScopeChange} onFolderNeutralizeToggle={_onFolderNeutralizeToggle} onSendToChat={onSendToChat} /> {_fileNodes.length === 0 && (
{searchQuery ? t('Keine Dateien gefunden') : t('Keine Dateien. Drag & Drop zum Hochladen.')}
)}
{'\uD83D\uDC64'} {t('Persönlich')} {'\uD83D\uDC65'} {t('Instanz')} {'\uD83C\uDFE2'} {t('Mandant')} {'\uD83D\uDD12'} {t('Neutralisiert')}
); }; export default FilesTab;