427 lines
9.1 KiB
TypeScript
427 lines
9.1 KiB
TypeScript
import { request } from "./http";
|
|
|
|
export interface FileUser {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
export interface FileResource {
|
|
id: number;
|
|
file_name: string;
|
|
mime_type: string | null;
|
|
size_bytes: number | null;
|
|
size_formatted: string;
|
|
extension: string;
|
|
storage_uri: string;
|
|
organized_path: string;
|
|
sha256: string | null;
|
|
uploaded_by: number;
|
|
uploader_name: string;
|
|
uploaded_at: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
is_image: boolean;
|
|
is_pdf: boolean;
|
|
url?: string;
|
|
user: FileUser;
|
|
category: string;
|
|
subcategory: string;
|
|
}
|
|
|
|
export interface FileListResponse {
|
|
data: FileResource[];
|
|
pagination: {
|
|
current_page: number;
|
|
from: number;
|
|
last_page: number;
|
|
per_page: number;
|
|
to: number;
|
|
total: number;
|
|
has_more_pages: boolean;
|
|
};
|
|
summary: {
|
|
total_files: number;
|
|
total_size: number;
|
|
total_size_formatted: string;
|
|
categories: Record<
|
|
string,
|
|
{
|
|
count: number;
|
|
total_size: number;
|
|
total_size_formatted: string;
|
|
}
|
|
>;
|
|
};
|
|
}
|
|
|
|
export interface FileResponse {
|
|
data: FileResource;
|
|
}
|
|
|
|
export interface FileCollectionResponse {
|
|
data: FileResource[];
|
|
pagination: {
|
|
current_page: number;
|
|
from: number;
|
|
last_page: number;
|
|
per_page: number;
|
|
to: number;
|
|
total: number;
|
|
has_more_pages: boolean;
|
|
};
|
|
}
|
|
|
|
export interface OrganizedFileStructure {
|
|
[key: string]: {
|
|
category: string;
|
|
subcategory: string;
|
|
files: FileResource[];
|
|
count: number;
|
|
};
|
|
}
|
|
|
|
export interface StorageStatistics {
|
|
total_files: number;
|
|
total_size_bytes: number;
|
|
total_size_formatted: string;
|
|
by_type: Record<
|
|
string,
|
|
{
|
|
count: number;
|
|
total_size: number;
|
|
}
|
|
>;
|
|
by_category: Record<
|
|
string,
|
|
{
|
|
count: number;
|
|
total_size: number;
|
|
}
|
|
>;
|
|
}
|
|
|
|
export interface DownloadResponse {
|
|
data: {
|
|
download_url: string;
|
|
file_name: string;
|
|
mime_type: string;
|
|
};
|
|
message: string;
|
|
}
|
|
|
|
export interface CreateFilePayload {
|
|
file: File;
|
|
file_name?: string;
|
|
category: "devis" | "facture" | "contrat" | "document" | "image" | "autre";
|
|
client_id?: number;
|
|
subcategory?: string;
|
|
description?: string;
|
|
tags?: string[];
|
|
is_public?: boolean;
|
|
}
|
|
|
|
export interface UpdateFilePayload {
|
|
id: number;
|
|
file_name?: string;
|
|
description?: string;
|
|
tags?: string[];
|
|
is_public?: boolean;
|
|
category?: "devis" | "facture" | "contrat" | "document" | "image" | "autre";
|
|
client_id?: number;
|
|
subcategory?: string;
|
|
}
|
|
|
|
export interface FileListParams {
|
|
page?: number;
|
|
per_page?: number;
|
|
search?: string;
|
|
mime_type?: string;
|
|
uploaded_by?: number;
|
|
category?: string;
|
|
client_id?: number;
|
|
date_from?: string;
|
|
date_to?: string;
|
|
sort_by?: string;
|
|
sort_direction?: "asc" | "desc";
|
|
}
|
|
|
|
export const FileService = {
|
|
/**
|
|
* Get all files with pagination and filters
|
|
*/
|
|
async getAllFiles(params?: FileListParams): Promise<FileListResponse> {
|
|
const response = await request<FileListResponse>({
|
|
url: "/api/files",
|
|
method: "get",
|
|
params,
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Get a specific file by ID
|
|
*/
|
|
async getFile(id: number): Promise<FileResponse> {
|
|
const response = await request<FileResponse>({
|
|
url: `/api/files/${id}`,
|
|
method: "get",
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Upload a new file
|
|
*/
|
|
async uploadFile(payload: CreateFilePayload): Promise<FileResponse> {
|
|
const formData = new FormData();
|
|
|
|
formData.append("file", payload.file);
|
|
|
|
if (payload.file_name) {
|
|
formData.append("file_name", payload.file_name);
|
|
}
|
|
|
|
formData.append("category", payload.category);
|
|
|
|
if (payload.client_id) {
|
|
formData.append("client_id", payload.client_id.toString());
|
|
}
|
|
|
|
if (payload.subcategory) {
|
|
formData.append("subcategory", payload.subcategory);
|
|
}
|
|
|
|
if (payload.description) {
|
|
formData.append("description", payload.description);
|
|
}
|
|
|
|
if (payload.tags && payload.tags.length > 0) {
|
|
payload.tags.forEach((tag, index) => {
|
|
formData.append(`tags[${index}]`, tag);
|
|
});
|
|
}
|
|
|
|
if (payload.is_public !== undefined) {
|
|
formData.append("is_public", payload.is_public.toString());
|
|
}
|
|
|
|
const response = await request<FileResponse>({
|
|
url: "/api/files",
|
|
method: "post",
|
|
data: formData,
|
|
headers: {
|
|
"Content-Type": "multipart/form-data",
|
|
},
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Update file metadata
|
|
*/
|
|
async updateFile(payload: UpdateFilePayload): Promise<FileResponse> {
|
|
const { id, ...updateData } = payload;
|
|
|
|
const response = await request<FileResponse>({
|
|
url: `/api/files/${id}`,
|
|
method: "put",
|
|
data: updateData,
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Delete a file
|
|
*/
|
|
async deleteFile(id: number): Promise<{ message: string }> {
|
|
const response = await request<{ message: string }>({
|
|
url: `/api/files/${id}`,
|
|
method: "delete",
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Get files by category
|
|
*/
|
|
async getFilesByCategory(
|
|
category: string,
|
|
params?: { page?: number; per_page?: number }
|
|
): Promise<FileCollectionResponse> {
|
|
const response = await request<FileCollectionResponse>({
|
|
url: `/api/files/by-category/${category}`,
|
|
method: "get",
|
|
params,
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Get files by client
|
|
*/
|
|
async getFilesByClient(
|
|
clientId: number,
|
|
params?: { page?: number; per_page?: number }
|
|
): Promise<FileCollectionResponse> {
|
|
const response = await request<FileCollectionResponse>({
|
|
url: `/api/files/by-client/${clientId}`,
|
|
method: "get",
|
|
params,
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Get organized file structure
|
|
*/
|
|
async getOrganizedStructure(): Promise<{
|
|
data: OrganizedFileStructure;
|
|
message: string;
|
|
}> {
|
|
const response = await request<{
|
|
data: OrganizedFileStructure;
|
|
message: string;
|
|
}>({
|
|
url: "/api/files/organized",
|
|
method: "get",
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Get storage statistics
|
|
*/
|
|
async getStorageStatistics(): Promise<{
|
|
data: StorageStatistics;
|
|
message: string;
|
|
}> {
|
|
const response = await request<{
|
|
data: StorageStatistics;
|
|
message: string;
|
|
}>({
|
|
url: "/api/files/statistics",
|
|
method: "get",
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Generate download URL for a file
|
|
*/
|
|
async generateDownloadUrl(id: number): Promise<DownloadResponse> {
|
|
const response = await request<DownloadResponse>({
|
|
url: `/api/files/${id}/download`,
|
|
method: "get",
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Format file size for display
|
|
*/
|
|
formatFileSize(bytes: number | null): string {
|
|
if (!bytes || bytes === 0) return "0 B";
|
|
|
|
const k = 1024;
|
|
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
},
|
|
|
|
/**
|
|
* Get file icon based on MIME type
|
|
*/
|
|
getFileIcon(mimeType: string | null): string {
|
|
if (!mimeType) return "📄";
|
|
|
|
if (mimeType.startsWith("image/")) return "🖼️";
|
|
if (mimeType === "application/pdf") return "📄";
|
|
if (mimeType.includes("word") || mimeType.includes("document")) return "📝";
|
|
if (mimeType.includes("sheet") || mimeType.includes("excel")) return "📊";
|
|
if (mimeType.includes("presentation") || mimeType.includes("powerpoint"))
|
|
return "📋";
|
|
if (mimeType.includes("zip") || mimeType.includes("archive")) return "🗜️";
|
|
|
|
return "📄";
|
|
},
|
|
|
|
/**
|
|
* Check if file is an image
|
|
*/
|
|
isImageFile(mimeType: string | null): boolean {
|
|
return mimeType ? mimeType.startsWith("image/") : false;
|
|
},
|
|
|
|
/**
|
|
* Check if file is a PDF
|
|
*/
|
|
isPdfFile(mimeType: string | null): boolean {
|
|
return mimeType === "application/pdf";
|
|
},
|
|
|
|
/**
|
|
* Get file extension from filename
|
|
*/
|
|
getFileExtension(filename: string): string {
|
|
return filename.split(".").pop()?.toLowerCase() || "";
|
|
},
|
|
|
|
/**
|
|
* Validate file before upload
|
|
*/
|
|
validateFile(
|
|
file: File,
|
|
maxSize: number = 10 * 1024 * 1024
|
|
): { valid: boolean; error?: string } {
|
|
if (file.size > maxSize) {
|
|
return {
|
|
valid: false,
|
|
error: `La taille du fichier ne doit pas dépasser ${this.formatFileSize(
|
|
maxSize
|
|
)}`,
|
|
};
|
|
}
|
|
|
|
// Allow all file types for now, but you can add restrictions here
|
|
const allowedTypes = [
|
|
"application/pdf",
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/webp",
|
|
"application/msword",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/vnd.ms-excel",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"application/vnd.ms-powerpoint",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"text/plain",
|
|
"application/zip",
|
|
"application/x-zip-compressed",
|
|
];
|
|
|
|
if (!allowedTypes.includes(file.type)) {
|
|
return {
|
|
valid: false,
|
|
error: "Type de fichier non autorisé",
|
|
};
|
|
}
|
|
|
|
return { valid: true };
|
|
},
|
|
};
|
|
|
|
export default FileService;
|