625 lines
15 KiB
TypeScript
625 lines
15 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { ref, computed } from "vue";
|
|
import FileService, {
|
|
type FileResource,
|
|
type FileListParams,
|
|
type CreateFilePayload,
|
|
type UpdateFilePayload,
|
|
type FileListResponse,
|
|
type OrganizedFileStructure,
|
|
type StorageStatistics,
|
|
} from "@/services/file";
|
|
|
|
export const useFileStore = defineStore("file", () => {
|
|
// State
|
|
const files = ref<FileResource[]>([]);
|
|
const currentFile = ref<FileResource | null>(null);
|
|
const loading = ref(false);
|
|
const error = ref<string | null>(null);
|
|
const uploadProgress = ref<number>(0);
|
|
const selectedFiles = ref<number[]>([]);
|
|
|
|
// Pagination state
|
|
const pagination = ref({
|
|
current_page: 1,
|
|
last_page: 1,
|
|
per_page: 15,
|
|
total: 0,
|
|
});
|
|
|
|
// Filter state
|
|
const filters = ref<FileListParams>({
|
|
search: '',
|
|
category: '',
|
|
client_id: undefined,
|
|
mime_type: '',
|
|
date_from: '',
|
|
date_to: '',
|
|
sort_by: 'uploaded_at',
|
|
sort_direction: 'desc',
|
|
});
|
|
|
|
// Organization state
|
|
const organizedFiles = ref<OrganizedFileStructure>({});
|
|
const storageStats = ref<StorageStatistics | null>(null);
|
|
|
|
// Getters
|
|
const allFiles = computed(() => files.value);
|
|
|
|
const filesByCategory = computed(() => {
|
|
const grouped: Record<string, FileResource[]> = {};
|
|
files.value.forEach(file => {
|
|
const category = file.category || 'general';
|
|
if (!grouped[category]) {
|
|
grouped[category] = [];
|
|
}
|
|
grouped[category].push(file);
|
|
});
|
|
return grouped;
|
|
});
|
|
|
|
const filesByClient = computed(() => {
|
|
const grouped: Record<number, FileResource[]> = {};
|
|
files.value.forEach(file => {
|
|
const clientId = extractClientIdFromPath(file.storage_uri);
|
|
if (clientId) {
|
|
if (!grouped[clientId]) {
|
|
grouped[clientId] = [];
|
|
}
|
|
grouped[clientId].push(file);
|
|
}
|
|
});
|
|
return grouped;
|
|
});
|
|
|
|
const imageFiles = computed(() =>
|
|
files.value.filter(file => file.is_image)
|
|
);
|
|
|
|
const pdfFiles = computed(() =>
|
|
files.value.filter(file => file.is_pdf)
|
|
);
|
|
|
|
const recentFiles = computed(() =>
|
|
[...files.value]
|
|
.sort((a, b) => new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime())
|
|
.slice(0, 10)
|
|
);
|
|
|
|
const isLoading = computed(() => loading.value);
|
|
const hasError = computed(() => error.value !== null);
|
|
const getError = computed(() => error.value);
|
|
const hasFiles = computed(() => files.value.length > 0);
|
|
const getPagination = computed(() => pagination.value);
|
|
const getFilters = computed(() => filters.value);
|
|
const getUploadProgress = computed(() => uploadProgress.value);
|
|
|
|
const getFileById = computed(() => (id: number) =>
|
|
files.value.find(file => file.id === id)
|
|
);
|
|
|
|
const getSelectedFiles = computed(() =>
|
|
files.value.filter(file => selectedFiles.value.includes(file.id))
|
|
);
|
|
|
|
const totalSizeFormatted = computed(() => {
|
|
const totalSize = files.value.reduce((sum, file) => sum + (file.size_bytes || 0), 0);
|
|
return FileService.formatFileSize(totalSize);
|
|
});
|
|
|
|
// Actions
|
|
const setLoading = (isLoading: boolean) => {
|
|
loading.value = isLoading;
|
|
};
|
|
|
|
const setError = (err: string | null) => {
|
|
error.value = err;
|
|
};
|
|
|
|
const clearError = () => {
|
|
error.value = null;
|
|
};
|
|
|
|
const setFiles = (newFiles: FileResource[]) => {
|
|
files.value = newFiles;
|
|
};
|
|
|
|
const setCurrentFile = (file: FileResource | null) => {
|
|
currentFile.value = file;
|
|
};
|
|
|
|
const setPagination = (meta: any) => {
|
|
if (meta) {
|
|
pagination.value = {
|
|
current_page: meta.current_page || 1,
|
|
last_page: meta.last_page || 1,
|
|
per_page: meta.per_page || 15,
|
|
total: meta.total || 0,
|
|
};
|
|
}
|
|
};
|
|
|
|
const setFilters = (newFilters: Partial<FileListParams>) => {
|
|
filters.value = { ...filters.value, ...newFilters };
|
|
};
|
|
|
|
const clearFilters = () => {
|
|
filters.value = {
|
|
search: '',
|
|
category: '',
|
|
client_id: undefined,
|
|
mime_type: '',
|
|
date_from: '',
|
|
date_to: '',
|
|
sort_by: 'uploaded_at',
|
|
sort_direction: 'desc',
|
|
};
|
|
};
|
|
|
|
const setUploadProgress = (progress: number) => {
|
|
uploadProgress.value = progress;
|
|
};
|
|
|
|
const selectFile = (fileId: number) => {
|
|
if (!selectedFiles.value.includes(fileId)) {
|
|
selectedFiles.value.push(fileId);
|
|
}
|
|
};
|
|
|
|
const deselectFile = (fileId: number) => {
|
|
selectedFiles.value = selectedFiles.value.filter(id => id !== fileId);
|
|
};
|
|
|
|
const selectAllFiles = () => {
|
|
selectedFiles.value = files.value.map(file => file.id);
|
|
};
|
|
|
|
const deselectAllFiles = () => {
|
|
selectedFiles.value = [];
|
|
};
|
|
|
|
/**
|
|
* Récupérer tous les fichiers avec pagination et filtres
|
|
*/
|
|
const fetchFiles = async (params?: FileListParams) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const queryParams = { ...filters.value, ...params };
|
|
const response = await FileService.getAllFiles(queryParams);
|
|
setFiles(response.data);
|
|
setPagination(response.pagination);
|
|
return response;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement des fichiers";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Récupérer un seul fichier par ID
|
|
*/
|
|
const fetchFile = async (id: number) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.getFile(id);
|
|
setCurrentFile(response.data);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement du fichier";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Télécharger un nouveau fichier
|
|
*/
|
|
const uploadFile = async (payload: CreateFilePayload) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
setUploadProgress(0);
|
|
|
|
try {
|
|
// Validate file before upload
|
|
const validation = FileService.validateFile(payload.file);
|
|
if (!validation.valid) {
|
|
throw new Error(validation.error);
|
|
}
|
|
|
|
const response = await FileService.uploadFile(payload);
|
|
|
|
// Add the new file to the beginning of the list
|
|
files.value.unshift(response.data);
|
|
setCurrentFile(response.data);
|
|
|
|
return response.data;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du téléchargement du fichier";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
setUploadProgress(0);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Mettre à jour les métadonnées d'un fichier
|
|
*/
|
|
const updateFile = async (payload: UpdateFilePayload) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.updateFile(payload);
|
|
const updatedFile = response.data;
|
|
|
|
// Update in files list
|
|
const index = files.value.findIndex(file => file.id === updatedFile.id);
|
|
if (index !== -1) {
|
|
files.value[index] = updatedFile;
|
|
}
|
|
|
|
// Update current file if it's the one being edited
|
|
if (currentFile.value && currentFile.value.id === updatedFile.id) {
|
|
setCurrentFile(updatedFile);
|
|
}
|
|
|
|
return updatedFile;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec de la mise à jour du fichier";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Supprimer un fichier
|
|
*/
|
|
const deleteFile = async (id: number) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
await FileService.deleteFile(id);
|
|
|
|
// Remove from files list
|
|
files.value = files.value.filter(file => file.id !== id);
|
|
|
|
// Remove from selection if selected
|
|
deselectFile(id);
|
|
|
|
// Clear current file if it's the one being deleted
|
|
if (currentFile.value && currentFile.value.id === id) {
|
|
setCurrentFile(null);
|
|
}
|
|
|
|
return { success: true, message: "Fichier supprimé avec succès" };
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec de la suppression du fichier";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Supprimer plusieurs fichiers
|
|
*/
|
|
const deleteMultipleFiles = async (fileIds: number[]) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const results = await Promise.allSettled(
|
|
fileIds.map(id => FileService.deleteFile(id))
|
|
);
|
|
|
|
// Filter out successful deletions
|
|
const successfulIds = fileIds.filter((_, index) =>
|
|
results[index].status === 'fulfilled'
|
|
);
|
|
|
|
// Remove successful deletions from the list
|
|
files.value = files.value.filter(file => !successfulIds.includes(file.id));
|
|
|
|
// Clear selections
|
|
successfulIds.forEach(id => deselectFile(id));
|
|
|
|
return {
|
|
success: true,
|
|
deleted: successfulIds.length,
|
|
total: fileIds.length
|
|
};
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec de la suppression des fichiers";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Récupérer les fichiers par catégorie
|
|
*/
|
|
const fetchFilesByCategory = async (category: string, params?: { page?: number; per_page?: number }) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.getFilesByCategory(category, params);
|
|
setFiles(response.data);
|
|
setPagination(response.pagination);
|
|
return response;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement des fichiers par catégorie";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Récupérer les fichiers par client
|
|
*/
|
|
const fetchFilesByClient = async (clientId: number, params?: { page?: number; per_page?: number }) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.getFilesByClient(clientId, params);
|
|
setFiles(response.data);
|
|
setPagination(response.pagination);
|
|
return response;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement des fichiers du client";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Récupérer la structure organisée des fichiers
|
|
*/
|
|
const fetchOrganizedStructure = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.getOrganizedStructure();
|
|
organizedFiles.value = response.data;
|
|
return response.data;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement de la structure organisée";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Récupérer les statistiques de stockage
|
|
*/
|
|
const fetchStorageStatistics = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await FileService.getStorageStatistics();
|
|
storageStats.value = response.data;
|
|
return response.data;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec du chargement des statistiques";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Générer une URL de téléchargement
|
|
*/
|
|
const generateDownloadUrl = async (fileId: number) => {
|
|
try {
|
|
const response = await FileService.generateDownloadUrl(fileId);
|
|
return response.data.download_url;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec de la génération de l'URL de téléchargement";
|
|
setError(errorMessage);
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Rechercher des fichiers
|
|
*/
|
|
const searchFiles = async (query: string, params?: { page?: number; per_page?: number }) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const searchParams = { ...filters.value, search: query, ...params };
|
|
const response = await FileService.getAllFiles(searchParams);
|
|
setFiles(response.data);
|
|
setPagination(response.pagination);
|
|
return response;
|
|
} catch (err: any) {
|
|
const errorMessage =
|
|
err.response?.data?.message ||
|
|
err.message ||
|
|
"Échec de la recherche de fichiers";
|
|
setError(errorMessage);
|
|
throw err;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Télécharger un fichier
|
|
*/
|
|
const downloadFile = async (fileId: number) => {
|
|
try {
|
|
const downloadUrl = await generateDownloadUrl(fileId);
|
|
|
|
// Create a temporary link to trigger download
|
|
const link = document.createElement('a');
|
|
link.href = downloadUrl;
|
|
link.download = '';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
return { success: true };
|
|
} catch (err: any) {
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Extraire l'ID client du chemin de stockage
|
|
*/
|
|
const extractClientIdFromPath = (storageUri: string): number | null => {
|
|
const pathParts = storageUri.split('/');
|
|
if (pathParts.length >= 4 && pathParts[0] === 'client') {
|
|
const clientId = parseInt(pathParts[1]);
|
|
return isNaN(clientId) ? null : clientId;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Réinitialiser l'état
|
|
*/
|
|
const resetState = () => {
|
|
files.value = [];
|
|
currentFile.value = null;
|
|
loading.value = false;
|
|
error.value = null;
|
|
uploadProgress.value = 0;
|
|
selectedFiles.value = [];
|
|
pagination.value = {
|
|
current_page: 1,
|
|
last_page: 1,
|
|
per_page: 15,
|
|
total: 0,
|
|
};
|
|
clearFilters();
|
|
organizedFiles.value = {};
|
|
storageStats.value = null;
|
|
};
|
|
|
|
return {
|
|
// State
|
|
files,
|
|
currentFile,
|
|
loading,
|
|
error,
|
|
uploadProgress,
|
|
selectedFiles,
|
|
pagination,
|
|
filters,
|
|
organizedFiles,
|
|
storageStats,
|
|
|
|
// Getters
|
|
allFiles,
|
|
filesByCategory,
|
|
filesByClient,
|
|
imageFiles,
|
|
pdfFiles,
|
|
recentFiles,
|
|
isLoading,
|
|
hasError,
|
|
getError,
|
|
hasFiles,
|
|
getPagination,
|
|
getFilters,
|
|
getUploadProgress,
|
|
getFileById,
|
|
getSelectedFiles,
|
|
totalSizeFormatted,
|
|
|
|
// Actions
|
|
setLoading,
|
|
setError,
|
|
clearError,
|
|
setFiles,
|
|
setCurrentFile,
|
|
setPagination,
|
|
setFilters,
|
|
clearFilters,
|
|
setUploadProgress,
|
|
selectFile,
|
|
deselectFile,
|
|
selectAllFiles,
|
|
deselectAllFiles,
|
|
fetchFiles,
|
|
fetchFile,
|
|
uploadFile,
|
|
updateFile,
|
|
deleteFile,
|
|
deleteMultipleFiles,
|
|
fetchFilesByCategory,
|
|
fetchFilesByClient,
|
|
fetchOrganizedStructure,
|
|
fetchStorageStatistics,
|
|
generateDownloadUrl,
|
|
searchFiles,
|
|
downloadFile,
|
|
resetState,
|
|
};
|
|
});
|
|
|
|
export default useFileStore; |