2025-12-01 17:02:01 +03:00

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;