816 lines
20 KiB
Markdown
816 lines
20 KiB
Markdown
# File Management Frontend System
|
|
|
|
## Overview
|
|
|
|
Complete frontend file management system with Vue.js 3, TypeScript, and Pinia for state management. This system provides file upload, organization, filtering, and management capabilities.
|
|
|
|
## Architecture
|
|
|
|
- **FileService**: API communication and data transformation
|
|
- **FileStore**: Pinia store for state management
|
|
- **TypeScript Interfaces**: Type safety for all data structures
|
|
|
|
## Installation
|
|
|
|
The file service and store are ready to use. Import them in your components:
|
|
|
|
```typescript
|
|
import { useFileStore } from "@/stores/fileStore";
|
|
import FileService from "@/services/file";
|
|
```
|
|
|
|
## FileStore Usage
|
|
|
|
### Basic Setup
|
|
|
|
```typescript
|
|
import { useFileStore } from "@/stores/fileStore";
|
|
|
|
const fileStore = useFileStore();
|
|
|
|
// Reactive state
|
|
const {
|
|
files,
|
|
isLoading,
|
|
hasError,
|
|
getError,
|
|
getPagination,
|
|
totalSizeFormatted,
|
|
} = storeToRefs(fileStore);
|
|
|
|
// Actions
|
|
const { fetchFiles, uploadFile, deleteFile, searchFiles } = fileStore;
|
|
```
|
|
|
|
### Store State Properties
|
|
|
|
#### Files Management
|
|
|
|
- `files` - Array of all files
|
|
- `currentFile` - Currently selected/viewed file
|
|
- `selectedFiles` - Array of selected file IDs for bulk operations
|
|
- `filters` - Current filtering criteria
|
|
- `pagination` - Pagination metadata
|
|
|
|
#### Loading States
|
|
|
|
- `loading` - General loading state
|
|
- `uploadProgress` - Upload progress (0-100%)
|
|
- `error` - Error message
|
|
- `hasError` - Boolean error state
|
|
|
|
#### Statistics
|
|
|
|
- `organizedFiles` - Files grouped by category/subcategory
|
|
- `storageStats` - Storage usage statistics
|
|
|
|
### Store Actions
|
|
|
|
#### File Retrieval
|
|
|
|
```typescript
|
|
// Get all files with pagination and filters
|
|
await fileStore.fetchFiles({
|
|
page: 1,
|
|
per_page: 15,
|
|
search: "document",
|
|
category: "devis",
|
|
sort_by: "uploaded_at",
|
|
sort_direction: "desc",
|
|
});
|
|
|
|
// Get specific file by ID
|
|
const file = await fileStore.fetchFile(123);
|
|
|
|
// Search files
|
|
await fileStore.searchFiles("invoice", { page: 1, per_page: 10 });
|
|
|
|
// Get files by category
|
|
await fileStore.fetchFilesByCategory("devis", { per_page: 20 });
|
|
|
|
// Get files by client
|
|
await fileStore.fetchFilesByClient(456, { per_page: 15 });
|
|
```
|
|
|
|
#### File Upload
|
|
|
|
```typescript
|
|
// Upload a single file
|
|
const fileInput = ref<HTMLInputElement>();
|
|
|
|
const handleFileUpload = async (event: Event) => {
|
|
const target = event.target as HTMLInputElement;
|
|
const file = target.files?.[0];
|
|
|
|
if (file) {
|
|
try {
|
|
await fileStore.uploadFile({
|
|
file,
|
|
category: "devis",
|
|
client_id: 123,
|
|
subcategory: "annual",
|
|
description: "Annual quote document",
|
|
tags: ["quote", "annual"],
|
|
is_public: false,
|
|
});
|
|
|
|
console.log("File uploaded successfully");
|
|
} catch (error) {
|
|
console.error("Upload failed:", error);
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
#### File Management
|
|
|
|
```typescript
|
|
// Update file metadata
|
|
await fileStore.updateFile({
|
|
id: 123,
|
|
file_name: "updated_filename.pdf",
|
|
description: "Updated description",
|
|
tags: ["updated", "tag"],
|
|
category: "facture",
|
|
});
|
|
|
|
// Delete single file
|
|
await fileStore.deleteFile(123);
|
|
|
|
// Delete multiple files
|
|
await fileStore.deleteMultipleFiles([123, 124, 125]);
|
|
|
|
// Download file
|
|
await fileStore.downloadFile(123);
|
|
|
|
// Generate download URL
|
|
const downloadUrl = await fileStore.generateDownloadUrl(123);
|
|
```
|
|
|
|
#### Filtering and Organization
|
|
|
|
```typescript
|
|
// Set filters
|
|
fileStore.setFilters({
|
|
category: "devis",
|
|
client_id: 123,
|
|
date_from: "2024-01-01",
|
|
date_to: "2024-12-31",
|
|
mime_type: "application/pdf",
|
|
});
|
|
|
|
// Clear filters
|
|
fileStore.clearFilters();
|
|
|
|
// Get organized structure
|
|
await fileStore.fetchOrganizedStructure();
|
|
|
|
// Get storage statistics
|
|
await fileStore.fetchStorageStatistics();
|
|
```
|
|
|
|
#### Selection Management
|
|
|
|
```typescript
|
|
// Select/deselect files
|
|
fileStore.selectFile(123);
|
|
fileStore.deselectFile(123);
|
|
|
|
// Select all/none
|
|
fileStore.selectAllFiles();
|
|
fileStore.deselectAllFiles();
|
|
|
|
// Get selected files
|
|
const selectedFiles = computed(() => fileStore.getSelectedFiles.value);
|
|
```
|
|
|
|
### Computed Properties
|
|
|
|
```typescript
|
|
// Basic getters
|
|
const allFiles = computed(() => fileStore.allFiles);
|
|
const isLoading = computed(() => fileStore.isLoading);
|
|
const hasError = computed(() => fileStore.hasError);
|
|
const totalSize = computed(() => fileStore.totalSizeFormatted);
|
|
|
|
// Filtered views
|
|
const imageFiles = computed(() => fileStore.imageFiles);
|
|
const pdfFiles = computed(() => fileStore.pdfFiles);
|
|
const recentFiles = computed(() => fileStore.recentFiles);
|
|
|
|
// Grouped views
|
|
const filesByCategory = computed(() => fileStore.filesByCategory);
|
|
const filesByClient = computed(() => fileStore.filesByClient);
|
|
|
|
// Pagination
|
|
const pagination = computed(() => fileStore.getPagination);
|
|
```
|
|
|
|
## FileService Usage
|
|
|
|
### Direct Service Methods
|
|
|
|
```typescript
|
|
import FileService from "@/services/file";
|
|
|
|
// File validation before upload
|
|
const validation = FileService.validateFile(file, 10 * 1024 * 1024); // 10MB
|
|
if (!validation.valid) {
|
|
console.error(validation.error);
|
|
return;
|
|
}
|
|
|
|
// Format file size
|
|
const sizeFormatted = FileService.formatFileSize(1024000); // "1000.00 KB"
|
|
|
|
// Get file icon
|
|
const icon = FileService.getFileIcon("application/pdf"); // "📄"
|
|
|
|
// Check file type
|
|
const isImage = FileService.isImageFile("image/jpeg"); // true
|
|
const isPdf = FileService.isPdfFile("application/pdf"); // true
|
|
|
|
// Get file extension
|
|
const extension = FileService.getFileExtension("document.pdf"); // "pdf"
|
|
```
|
|
|
|
## Component Examples
|
|
|
|
### File List Component
|
|
|
|
```vue
|
|
<template>
|
|
<div class="file-management">
|
|
<!-- Header with actions -->
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2>Fichiers ({{ fileStore.files.length }})</h2>
|
|
<div class="flex gap-2">
|
|
<SoftButton @click="refreshFiles" :loading="fileStore.isLoading">
|
|
Actualiser
|
|
</SoftButton>
|
|
<SoftButton @click="showUploadModal = true" color="primary">
|
|
Télécharger
|
|
</SoftButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="filters mb-4">
|
|
<input
|
|
v-model="searchQuery"
|
|
@input="handleSearch"
|
|
placeholder="Rechercher des fichiers..."
|
|
class="form-control"
|
|
/>
|
|
|
|
<select v-model="selectedCategory" @change="handleCategoryChange">
|
|
<option value="">Toutes les catégories</option>
|
|
<option value="devis">Devis</option>
|
|
<option value="facture">Factures</option>
|
|
<option value="contrat">Contrats</option>
|
|
<option value="document">Documents</option>
|
|
<option value="image">Images</option>
|
|
<option value="autre">Autres</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- File list -->
|
|
<div v-if="fileStore.isLoading" class="text-center">
|
|
<div class="spinner">Chargement...</div>
|
|
</div>
|
|
|
|
<div v-else-if="fileStore.hasError" class="alert alert-danger">
|
|
{{ fileStore.getError }}
|
|
</div>
|
|
|
|
<div v-else class="file-grid">
|
|
<div
|
|
v-for="file in fileStore.files"
|
|
:key="file.id"
|
|
class="file-card"
|
|
:class="{ selected: fileStore.selectedFiles.includes(file.id) }"
|
|
@click="toggleFileSelection(file.id)"
|
|
>
|
|
<div class="file-icon">
|
|
{{ FileService.getFileIcon(file.mime_type) }}
|
|
</div>
|
|
<div class="file-info">
|
|
<h4>{{ file.file_name }}</h4>
|
|
<p>{{ file.size_formatted }} • {{ file.category }}</p>
|
|
<small>{{ formatDate(file.uploaded_at) }}</small>
|
|
</div>
|
|
<div class="file-actions">
|
|
<button @click.stop="downloadFile(file.id)" class="btn btn-sm">
|
|
Télécharger
|
|
</button>
|
|
<button @click.stop="editFile(file.id)" class="btn btn-sm">
|
|
Modifier
|
|
</button>
|
|
<button
|
|
@click.stop="confirmDelete(file.id)"
|
|
class="btn btn-sm text-danger"
|
|
>
|
|
Supprimer
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="pagination mt-4">
|
|
<SoftPagination
|
|
:current-page="fileStore.getPagination.current_page"
|
|
:last-page="fileStore.getPagination.last_page"
|
|
@page-changed="handlePageChange"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Upload Modal -->
|
|
<FileUploadModal
|
|
v-if="showUploadModal"
|
|
@close="showUploadModal = false"
|
|
@uploaded="handleFileUploaded"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from "vue";
|
|
import { useFileStore } from "@/stores/fileStore";
|
|
import FileService from "@/services/file";
|
|
|
|
const fileStore = useFileStore();
|
|
const showUploadModal = ref(false);
|
|
const searchQuery = ref("");
|
|
const selectedCategory = ref("");
|
|
|
|
onMounted(() => {
|
|
fileStore.fetchFiles();
|
|
});
|
|
|
|
const handleSearch = () => {
|
|
fileStore.setFilters({ search: searchQuery.value });
|
|
fileStore.fetchFiles({ page: 1 });
|
|
};
|
|
|
|
const handleCategoryChange = () => {
|
|
fileStore.setFilters({ category: selectedCategory.value });
|
|
fileStore.fetchFiles({ page: 1 });
|
|
};
|
|
|
|
const handlePageChange = (page: number) => {
|
|
fileStore.fetchFiles({ page });
|
|
};
|
|
|
|
const toggleFileSelection = (fileId: number) => {
|
|
if (fileStore.selectedFiles.includes(fileId)) {
|
|
fileStore.deselectFile(fileId);
|
|
} else {
|
|
fileStore.selectFile(fileId);
|
|
}
|
|
};
|
|
|
|
const downloadFile = async (fileId: number) => {
|
|
try {
|
|
await fileStore.downloadFile(fileId);
|
|
} catch (error) {
|
|
console.error("Download failed:", error);
|
|
}
|
|
};
|
|
|
|
const editFile = (fileId: number) => {
|
|
// Navigate to file edit page or open modal
|
|
router.push(`/files/${fileId}/edit`);
|
|
};
|
|
|
|
const confirmDelete = (fileId: number) => {
|
|
if (confirm("Êtes-vous sûr de vouloir supprimer ce fichier ?")) {
|
|
fileStore.deleteFile(fileId);
|
|
}
|
|
};
|
|
|
|
const handleFileUploaded = () => {
|
|
showUploadModal.value = false;
|
|
fileStore.fetchFiles({ page: 1 });
|
|
};
|
|
|
|
const refreshFiles = () => {
|
|
fileStore.fetchFiles();
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return new Date(dateString).toLocaleDateString("fr-FR");
|
|
};
|
|
</script>
|
|
```
|
|
|
|
### File Upload Component
|
|
|
|
```vue
|
|
<template>
|
|
<div class="upload-modal">
|
|
<div class="modal-header">
|
|
<h3>Télécharger un fichier</h3>
|
|
<button @click="$emit('close')" class="close-btn">×</button>
|
|
</div>
|
|
|
|
<form @submit.prevent="handleUpload" class="upload-form">
|
|
<!-- File selection -->
|
|
<div class="form-group">
|
|
<label>Fichier *</label>
|
|
<input
|
|
type="file"
|
|
@change="handleFileSelect"
|
|
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif"
|
|
required
|
|
/>
|
|
<div v-if="selectedFile" class="file-preview">
|
|
<p>
|
|
{{ selectedFile.name }} ({{
|
|
FileService.formatFileSize(selectedFile.size)
|
|
}})
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category -->
|
|
<div class="form-group">
|
|
<label>Catégorie *</label>
|
|
<select v-model="form.category" required>
|
|
<option value="">Sélectionner une catégorie</option>
|
|
<option value="devis">Devis</option>
|
|
<option value="facture">Facture</option>
|
|
<option value="contrat">Contrat</option>
|
|
<option value="document">Document</option>
|
|
<option value="image">Image</option>
|
|
<option value="autre">Autre</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Client ID -->
|
|
<div class="form-group">
|
|
<label>Client (optionnel)</label>
|
|
<input
|
|
v-model.number="form.client_id"
|
|
type="number"
|
|
placeholder="ID du client"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Subcategory -->
|
|
<div class="form-group">
|
|
<label>Sous-catégorie (optionnel)</label>
|
|
<input
|
|
v-model="form.subcategory"
|
|
type="text"
|
|
placeholder="Ex: annual, monthly"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div class="form-group">
|
|
<label>Description</label>
|
|
<textarea
|
|
v-model="form.description"
|
|
placeholder="Description du fichier"
|
|
rows="3"
|
|
></textarea>
|
|
</div>
|
|
|
|
<!-- Tags -->
|
|
<div class="form-group">
|
|
<label>Étiquettes</label>
|
|
<input
|
|
v-model="tagInput"
|
|
@keydown.enter.prevent="addTag"
|
|
placeholder="Ajouter une étiquette et appuyer sur Entrée"
|
|
/>
|
|
<div class="tags">
|
|
<span v-for="(tag, index) in form.tags" :key="index" class="tag">
|
|
{{ tag }}
|
|
<button type="button" @click="removeTag(index)">×</button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Public checkbox -->
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input v-model="form.is_public" type="checkbox" />
|
|
Fichier public
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Progress bar -->
|
|
<div v-if="fileStore.isLoading" class="progress-bar">
|
|
<div
|
|
class="progress-fill"
|
|
:style="{ width: fileStore.getUploadProgress + '%' }"
|
|
></div>
|
|
</div>
|
|
|
|
<!-- Error message -->
|
|
<div v-if="fileStore.hasError" class="alert alert-danger">
|
|
{{ fileStore.getError }}
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="modal-actions">
|
|
<button
|
|
type="button"
|
|
@click="$emit('close')"
|
|
:disabled="fileStore.isLoading"
|
|
>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
:disabled="!selectedFile || !form.category || fileStore.isLoading"
|
|
class="btn-primary"
|
|
>
|
|
{{ fileStore.isLoading ? "Téléchargement..." : "Télécharger" }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive } from "vue";
|
|
import { useFileStore } from "@/stores/fileStore";
|
|
import FileService from "@/services/file";
|
|
|
|
const emit = defineEmits<{
|
|
close: [];
|
|
uploaded: [];
|
|
}>();
|
|
|
|
const fileStore = useFileStore();
|
|
|
|
const selectedFile = ref<File | null>(null);
|
|
const tagInput = ref("");
|
|
|
|
const form = reactive({
|
|
category: "",
|
|
client_id: undefined as number | undefined,
|
|
subcategory: "",
|
|
description: "",
|
|
tags: [] as string[],
|
|
is_public: false,
|
|
});
|
|
|
|
const handleFileSelect = (event: Event) => {
|
|
const target = event.target as HTMLInputElement;
|
|
const file = target.files?.[0];
|
|
|
|
if (file) {
|
|
// Validate file
|
|
const validation = FileService.validateFile(file);
|
|
if (!validation.valid) {
|
|
alert(validation.error);
|
|
target.value = "";
|
|
return;
|
|
}
|
|
|
|
selectedFile.value = file;
|
|
}
|
|
};
|
|
|
|
const addTag = () => {
|
|
const tag = tagInput.value.trim();
|
|
if (tag && !form.tags.includes(tag) && form.tags.length < 10) {
|
|
form.tags.push(tag);
|
|
tagInput.value = "";
|
|
}
|
|
};
|
|
|
|
const removeTag = (index: number) => {
|
|
form.tags.splice(index, 1);
|
|
};
|
|
|
|
const handleUpload = async () => {
|
|
if (!selectedFile.value || !form.category) return;
|
|
|
|
try {
|
|
await fileStore.uploadFile({
|
|
file: selectedFile.value,
|
|
...form,
|
|
});
|
|
|
|
emit("uploaded");
|
|
} catch (error) {
|
|
console.error("Upload failed:", error);
|
|
}
|
|
};
|
|
</script>
|
|
```
|
|
|
|
### File Statistics Component
|
|
|
|
```vue
|
|
<template>
|
|
<div class="file-statistics">
|
|
<div class="stats-header">
|
|
<h3>Statistiques de Stockage</h3>
|
|
<button @click="refreshStats" :disabled="fileStore.isLoading">
|
|
Actualiser
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="fileStore.storageStats" class="stats-content">
|
|
<!-- Overview cards -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<h4>Total des Fichiers</h4>
|
|
<p class="stat-number">{{ fileStore.storageStats.total_files }}</p>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h4>Espace Utilisé</h4>
|
|
<p class="stat-number">
|
|
{{ fileStore.storageStats.total_size_formatted }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Files by type -->
|
|
<div class="stats-section">
|
|
<h4>Par Type de Fichier</h4>
|
|
<div class="type-grid">
|
|
<div
|
|
v-for="(typeData, mimeType) in fileStore.storageStats.by_type"
|
|
:key="mimeType"
|
|
class="type-card"
|
|
>
|
|
<div class="type-icon">{{ FileService.getFileIcon(mimeType) }}</div>
|
|
<div class="type-info">
|
|
<p class="type-name">{{ getTypeDisplayName(mimeType) }}</p>
|
|
<p class="type-count">{{ typeData.count }} fichiers</p>
|
|
<p class="type-size">
|
|
{{ FileService.formatFileSize(typeData.total_size) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Files by category -->
|
|
<div class="stats-section">
|
|
<h4>Par Catégorie</h4>
|
|
<div class="category-grid">
|
|
<div
|
|
v-for="(categoryData, category) in fileStore.storageStats
|
|
.by_category"
|
|
:key="category"
|
|
class="category-card"
|
|
>
|
|
<h5>{{ getCategoryDisplayName(category) }}</h5>
|
|
<div class="category-stats">
|
|
<span>{{ categoryData.count }} fichiers</span>
|
|
<span>{{
|
|
FileService.formatFileSize(categoryData.total_size)
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else-if="fileStore.isLoading" class="loading">
|
|
Chargement des statistiques...
|
|
</div>
|
|
|
|
<div v-else-if="fileStore.hasError" class="error">
|
|
{{ fileStore.getError }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted } from "vue";
|
|
import { useFileStore } from "@/stores/fileStore";
|
|
import FileService from "@/services/file";
|
|
|
|
const fileStore = useFileStore();
|
|
|
|
onMounted(() => {
|
|
fileStore.fetchStorageStatistics();
|
|
});
|
|
|
|
const refreshStats = () => {
|
|
fileStore.fetchStorageStatistics();
|
|
};
|
|
|
|
const getTypeDisplayName = (mimeType: string): string => {
|
|
const typeMap: Record<string, string> = {
|
|
"application/pdf": "PDF",
|
|
"image/jpeg": "Image JPEG",
|
|
"image/png": "Image PNG",
|
|
"application/msword": "Document Word",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
"Document Word",
|
|
};
|
|
|
|
return typeMap[mimeType] || mimeType;
|
|
};
|
|
|
|
const getCategoryDisplayName = (category: string): string => {
|
|
const categoryMap: Record<string, string> = {
|
|
devis: "Devis",
|
|
facture: "Factures",
|
|
contrat: "Contrats",
|
|
document: "Documents",
|
|
image: "Images",
|
|
autre: "Autres",
|
|
};
|
|
|
|
return categoryMap[category] || category;
|
|
};
|
|
</script>
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Error Handling
|
|
|
|
```typescript
|
|
try {
|
|
await fileStore.uploadFile(payload);
|
|
// Success handling
|
|
} catch (error: any) {
|
|
// Display user-friendly error message
|
|
const message = error.response?.data?.message || error.message;
|
|
// Show in toast/notification
|
|
}
|
|
```
|
|
|
|
### File Validation
|
|
|
|
```typescript
|
|
// Always validate files before upload
|
|
const validation = FileService.validateFile(file);
|
|
if (!validation.valid) {
|
|
showError(validation.error);
|
|
return;
|
|
}
|
|
```
|
|
|
|
### Progress Tracking
|
|
|
|
```vue
|
|
<template>
|
|
<div v-if="fileStore.isLoading" class="upload-progress">
|
|
<div class="progress-bar">
|
|
<div
|
|
class="progress-fill"
|
|
:style="{ width: fileStore.getUploadProgress + '%' }"
|
|
></div>
|
|
</div>
|
|
<p>{{ fileStore.getUploadProgress }}%</p>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Bulk Operations
|
|
|
|
```typescript
|
|
// Delete multiple files
|
|
const deleteSelected = async () => {
|
|
if (fileStore.selectedFiles.length === 0) return;
|
|
|
|
if (confirm(`Supprimer ${fileStore.selectedFiles.length} fichiers ?`)) {
|
|
await fileStore.deleteMultipleFiles(fileStore.selectedFiles);
|
|
fileStore.deselectAllFiles();
|
|
}
|
|
};
|
|
```
|
|
|
|
### Performance Optimization
|
|
|
|
```typescript
|
|
// Use computed properties for filtered views
|
|
const pdfFiles = computed(() => fileStore.pdfFiles);
|
|
const imageFiles = computed(() => fileStore.imageFiles);
|
|
|
|
// Cache expensive operations
|
|
const fileStats = computed(() => {
|
|
return {
|
|
totalSize: fileStore.totalSizeFormatted,
|
|
fileCount: fileStore.files.length,
|
|
};
|
|
});
|
|
```
|
|
|
|
## Integration with Backend
|
|
|
|
The frontend system integrates with the Laravel backend API through the FileService. All API endpoints are mapped:
|
|
|
|
- `GET /api/files` → `FileService.getAllFiles()`
|
|
- `POST /api/files` → `FileService.uploadFile()`
|
|
- `GET /api/files/{id}` → `FileService.getFile()`
|
|
- `PUT /api/files/{id}` → `FileService.updateFile()`
|
|
- `DELETE /api/files/{id}` → `FileService.deleteFile()`
|
|
- And specialized endpoints for categories, clients, statistics, etc.
|
|
|
|
This ensures full compatibility with the backend file management system while providing a rich, type-safe frontend experience.
|