/** * FilesPage * * Page for file management using FormGeneratorTable. * Follows the pattern established in AdminUsersPage/WorkflowsPage. */ import React, { useState, useMemo, useEffect, useRef } from 'react'; import { useUserFiles, useFileOperations } from '../../hooks/useFiles'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm'; import { FaSync, FaFolder, FaUpload, FaDownload, FaEye } from 'react-icons/fa'; import { useToast } from '../../contexts/ToastContext'; import styles from '../admin/Admin.module.css'; interface UserFile { id: string; fileName: string; mimeType?: string; fileSize?: number; [key: string]: any; } export const FilesPage: React.FC = () => { const fileInputRef = useRef(null); const { showSuccess, showError } = useToast(); // Data hook const { data: files, attributes, permissions, pagination, loading, error, refetch, fetchFileById, updateFileOptimistically, } = useUserFiles(); // Operations hook const { handleFileDownload, handleFileDelete, handleFileDeleteMultiple, handleFileUpload, handleFileUpdate, handleFilePreview, handleInlineUpdate, deletingFiles, downloadingFiles, uploadingFile, previewingFiles, } = useFileOperations(); const [editingFile, setEditingFile] = useState(null); // Initial fetch useEffect(() => { refetch(); }, []); // Generate columns from attributes - hide internal fields const columns = useMemo(() => { const hiddenColumns = ['id', 'mandateId', 'featureInstanceId', 'fileHash']; const cols = (attributes || []) .filter(attr => !hiddenColumns.includes(attr.name)) .map(attr => ({ key: attr.name, label: attr.label || attr.name, type: attr.type as any, sortable: attr.sortable !== false, filterable: attr.filterable !== false, searchable: attr.searchable !== false, width: attr.width || 150, minWidth: attr.minWidth || 100, maxWidth: attr.maxWidth || 400, })); // Add _createdBy column with FK resolution to show username cols.push({ key: '_createdBy', label: 'Created By', type: 'text' as any, sortable: true, filterable: false, searchable: false, width: 150, minWidth: 100, maxWidth: 250, fkSource: '/api/users/', fkDisplayField: 'username', }); return cols; }, [attributes]); // Check permissions const canCreate = permissions?.create !== 'n'; const canUpdate = permissions?.update !== 'n'; const canDelete = permissions?.delete !== 'n'; // Handle edit click const handleEditClick = async (file: UserFile) => { const fullFile = await fetchFileById(file.id); if (fullFile) { setEditingFile(fullFile as UserFile); } }; // Handle edit submit const handleEditSubmit = async (data: Partial) => { if (!editingFile) return; const result = await handleFileUpdate(editingFile.id, { fileName: data.fileName || editingFile.fileName }, editingFile); if (result.success) { setEditingFile(null); refetch(); } }; // Handle delete single file (confirmation handled by DeleteActionButton) const handleDelete = async (file: UserFile) => { const success = await handleFileDelete(file.id); if (success) { refetch(); } }; // Handle delete multiple files (confirmation handled by FormGenerator) const handleDeleteMultiple = async (filesToDelete: UserFile[]) => { const ids = filesToDelete.map(f => f.id); const success = await handleFileDeleteMultiple(ids); if (success) { refetch(); } }; // Handle download const handleDownload = async (file: UserFile) => { await handleFileDownload(file.id, file.fileName); }; // Handle preview const handlePreview = async (file: UserFile) => { const result = await handleFilePreview(file.id, file.fileName, file.mimeType); if (result.success && result.previewUrl) { window.open(result.previewUrl, '_blank'); } }; // Handle upload click const handleUploadClick = () => { fileInputRef.current?.click(); }; // Handle file selection const handleFileSelect = async (e: React.ChangeEvent) => { const selectedFiles = e.target.files; if (selectedFiles && selectedFiles.length > 0) { let successCount = 0; let errorCount = 0; for (const file of Array.from(selectedFiles)) { const result = await handleFileUpload(file); if (result?.success) { successCount++; } else { errorCount++; } } // Reset input first if (fileInputRef.current) { fileInputRef.current.value = ''; } // Refresh table to show new files await refetch(); // Show feedback if (successCount > 0) { showSuccess( 'Upload erfolgreich', `${successCount} Datei(en) hochgeladen${errorCount > 0 ? `, ${errorCount} fehlgeschlagen` : ''}` ); } else if (errorCount > 0) { showError('Upload fehlgeschlagen', `${errorCount} Datei(en) konnten nicht hochgeladen werden`); } } }; // Form attributes for edit modal const formAttributes = useMemo(() => { const excludedFields = ['id', 'mandateId', 'fileHash', '_createdBy', '_createdAt', '_modifiedAt', 'creationDate', 'source']; return (attributes || []) .filter(attr => !excludedFields.includes(attr.name)); }, [attributes]); if (error) { return (
⚠️

Fehler beim Laden der Dateien: {error}

); } return (
{/* Hidden file input */}

Dateien

Dateiverwaltung

{canCreate && ( )}
{loading && (!files || files.length === 0) ? (
Lade Dateien...
) : !files || files.length === 0 ? (

Keine Dateien vorhanden

Laden Sie eine Datei hoch, um loszulegen.

{canCreate && ( )}
) : ( deletingFiles.has(row.id), }] : []), ]} customActions={[ { id: 'download', icon: , onClick: handleDownload, title: 'Herunterladen', loading: (row: UserFile) => downloadingFiles.has(row.id), }, { id: 'preview', icon: , onClick: handlePreview, title: 'Vorschau', loading: (row: UserFile) => previewingFiles.has(row.id), }, ]} onDelete={handleDelete} onDeleteMultiple={handleDeleteMultiple} hookData={{ refetch, permissions, pagination, handleDelete: handleFileDelete, handleInlineUpdate, updateOptimistically: updateFileOptimistically, }} emptyMessage="Keine Dateien gefunden" /> )}
{/* Edit Modal */} {editingFile && (
setEditingFile(null)}>
e.stopPropagation()}>

Datei bearbeiten

{formAttributes.length === 0 ? (
Lade Formular...
) : ( setEditingFile(null)} submitButtonText="Speichern" cancelButtonText="Abbrechen" /> )}
)}
); }; export default FilesPage;