frontend_nyla/src/hooks/useFiles.ts

235 lines
No EOL
6.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useApiRequest } from './useApi';
// File interfaces
export interface FileInfo {
id: number;
name: string;
mimeType: string;
size?: number;
creationDate: string;
fileHash?: string;
mandateId?: number;
userId?: number;
workflowId?: string;
source?: string; // 'user_uploaded', 'agent_created', or 'shared_with_me'
}
export interface UserFile {
id: number;
file_name: string;
action: string;
created_at: string;
size?: number;
source?: string; // 'user_uploaded', 'agent_created', or 'shared_with_me'
}
// Files list hook
export function useUserFiles() {
const [files, setFiles] = useState<UserFile[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, FileInfo[]>();
const fetchFiles = async () => {
try {
const data = await request({
url: '/api/files',
method: 'get'
});
// Map API response to our frontend model
const mappedFiles = data.map((apiFile: FileInfo): UserFile => {
// Derive a simplified action from the MIME type
let action = 'Document';
if (apiFile.mimeType) {
const mimePrefix = apiFile.mimeType.split('/')[0];
switch (mimePrefix) {
case 'image':
action = 'Bild';
break;
case 'application':
if (apiFile.mimeType.includes('pdf')) {
action = 'PDF';
} else if (apiFile.mimeType.includes('word') || apiFile.mimeType.includes('office')) {
action = 'Dokument';
} else if (apiFile.mimeType.includes('excel') || apiFile.mimeType.includes('spreadsheet')) {
action = 'Tabelle';
} else {
action = 'Datei';
}
break;
case 'text':
action = 'Text';
break;
case 'video':
action = 'Video';
break;
case 'audio':
action = 'Audio';
break;
default:
action = 'Datei';
}
}
return {
id: apiFile.id,
file_name: apiFile.name,
action: action,
created_at: apiFile.creationDate,
size: apiFile.size,
source: apiFile.source
};
});
setFiles(mappedFiles);
} catch (error) {
// Error is already handled by useApiRequest
}
};
// Optimistically remove a file from the local state
const removeFileOptimistically = (fileId: number) => {
setFiles(prevFiles => prevFiles.filter(file => file.id !== fileId));
};
// Add a file to the local state (for when upload completes)
const addFileOptimistically = (newFile: UserFile) => {
setFiles(prevFiles => [newFile, ...prevFiles]);
};
useEffect(() => {
fetchFiles();
}, []);
return {
files,
loading,
error,
refetch: fetchFiles,
removeFileOptimistically,
addFileOptimistically
};
}
// File operations hook
export function useFileOperations() {
const [downloadingFiles, setDownloadingFiles] = useState<Set<number>>(new Set());
const [deletingFiles, setDeletingFiles] = useState<Set<number>>(new Set());
const [uploadingFile, setUploadingFile] = useState(false);
const { request, error: apiError, isLoading } = useApiRequest();
const [downloadError, setDownloadError] = useState<string | null>(null);
const [deleteError, setDeleteError] = useState<string | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
const handleFileDownload = async (fileId: number, fileName: string) => {
setDownloadError(null);
setDownloadingFiles(prev => new Set(prev).add(fileId));
try {
const blob = await request({
url: `/api/files/${fileId}`,
method: 'get',
// Override axios config for blob response
additionalConfig: { responseType: 'blob' }
});
// Create a download link and trigger the download
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
return true;
} catch (error: any) {
setDownloadError(error.message);
return false;
} finally {
setDownloadingFiles(prev => {
const newSet = new Set(prev);
newSet.delete(fileId);
return newSet;
});
}
};
const handleFileDelete = async (fileId: number, onOptimisticDelete?: () => void) => {
setDeleteError(null);
setDeletingFiles(prev => new Set(prev).add(fileId));
// Optimistically remove from UI if callback provided
if (onOptimisticDelete) {
onOptimisticDelete();
}
try {
await request({
url: `/api/files/${fileId}`,
method: 'delete'
});
// Add a small delay to ensure backend has time to process
await new Promise(resolve => setTimeout(resolve, 300));
return true;
} catch (error: any) {
setDeleteError(error.message);
// If deletion failed and we optimistically removed it, we should refetch to restore the file
return false;
} finally {
setDeletingFiles(prev => {
const newSet = new Set(prev);
newSet.delete(fileId);
return newSet;
});
}
};
const handleFileUpload = async (file: globalThis.File, workflowId?: string) => {
setUploadError(null);
setUploadingFile(true);
try {
const formData = new FormData();
formData.append('file', file);
if (workflowId) {
formData.append('workflowId', workflowId);
}
const fileData = await request({
url: '/api/files/upload',
method: 'post',
data: formData,
// Override axios config for form data
additionalConfig: {
headers: {
'Content-Type': 'multipart/form-data',
}
}
});
return { success: true, fileData };
} catch (error: any) {
setUploadError(error.message);
return { success: false, error: error.message };
} finally {
setUploadingFile(false);
}
};
return {
downloadingFiles,
deletingFiles,
uploadingFile,
downloadError,
deleteError,
uploadError,
handleFileDownload,
handleFileDelete,
handleFileUpload,
isLoading
};
}