diff --git a/components/4nk/FolderModal.tsx b/components/4nk/FolderModal.tsx index 67f517f..9c6fe5f 100644 --- a/components/4nk/FolderModal.tsx +++ b/components/4nk/FolderModal.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, memo } from 'react'; import Modal from './Modal'; -import type { FolderData } from '@/lib/4nk/models/FolderData'; +import type { FolderData, AttachedFile } from '@/lib/4nk/models/FolderData'; import { MemberAutocomplete } from '../ui/member-autocomplete'; type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre'; @@ -30,7 +30,8 @@ const defaultFolder: FolderData = { updated_at: new Date().toISOString(), notes: [], messages: [], - messages_owner: [] + messages_owner: [], + attachedFiles: [] }; function capitalize(s?: string) { @@ -38,6 +39,46 @@ function capitalize(s?: string) { return s.charAt(0).toUpperCase() + s.slice(1); } +// Types de fichiers autorisés +const ALLOWED_FILE_TYPES = { + 'application/pdf': '.pdf', + 'image/png': '.png', + 'image/jpeg': '.jpg,.jpeg', + 'image/gif': '.gif', + 'image/webp': '.webp', + 'text/plain': '.txt', + 'application/msword': '.doc', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx', + 'application/vnd.ms-excel': '.xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx' +}; + +const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB + +// Fonction pour convertir un fichier en base64 +const fileToBase64 = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + const result = reader.result as string; + // Enlever le préfixe "data:type/subtype;base64," + const base64 = result.split(',')[1]; + resolve(base64); + }; + reader.onerror = error => reject(error); + }); +}; + +// Fonction pour formater la taille des fichiers +const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + // Mapping des couleurs const folderColors: Record = { contrat: { bg: 'bg-blue-50 dark:bg-blue-900', border: 'border-blue-300 dark:border-blue-700', focus: 'focus:ring-blue-400 dark:focus:ring-blue-600', button: 'bg-blue-500 hover:bg-blue-600' }, @@ -64,6 +105,9 @@ function FolderModal({ const [currentNote, setCurrentNote] = useState(''); // --- NOUVEAU: État pour les membres sélectionnés --- const [selectedMembers, setSelectedMembers] = useState([]); + // --- NOUVEAU: États pour la gestion des fichiers --- + const [isUploading, setIsUploading] = useState(false); + const [uploadError, setUploadError] = useState(null); useEffect(() => { if (isOpen) { @@ -99,6 +143,74 @@ function FolderModal({ setFolderData(prev => ({ ...prev, notes: (prev.notes || []).filter(n => n !== note) })); }; + // --- NOUVEAU: Fonctions pour la gestion des fichiers --- + const handleFileUpload = async (event: React.ChangeEvent) => { + const files = event.target.files; + if (!files || files.length === 0) return; + + setIsUploading(true); + setUploadError(null); + + try { + const newFiles: AttachedFile[] = []; + + for (const file of Array.from(files)) { + // Vérifier le type de fichier + if (!Object.keys(ALLOWED_FILE_TYPES).includes(file.type)) { + throw new Error(`Type de fichier non autorisé: ${file.type}`); + } + + // Vérifier la taille + if (file.size > MAX_FILE_SIZE) { + throw new Error(`Fichier trop volumineux: ${file.name} (${formatFileSize(file.size)}). Taille maximale: ${formatFileSize(MAX_FILE_SIZE)}`); + } + + // Convertir en base64 + const base64Data = await fileToBase64(file); + + const attachedFile: AttachedFile = { + id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + name: file.name, + type: file.type, + size: file.size, + base64Data, + uploadedAt: new Date().toISOString() + }; + + newFiles.push(attachedFile); + } + + // Ajouter les nouveaux fichiers + setFolderData(prev => ({ + ...prev, + attachedFiles: [...(prev.attachedFiles || []), ...newFiles] + })); + + } catch (error) { + setUploadError(error instanceof Error ? error.message : 'Erreur lors du téléchargement'); + } finally { + setIsUploading(false); + // Réinitialiser l'input + event.target.value = ''; + } + }; + + const removeFile = (fileId: string) => { + setFolderData(prev => ({ + ...prev, + attachedFiles: (prev.attachedFiles || []).filter(f => f.id !== fileId) + })); + }; + + const downloadFile = (file: AttachedFile) => { + const link = document.createElement('a'); + link.href = `data:${file.type};base64,${file.base64Data}`; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSave?.({ ...folderData, updated_at: new Date().toISOString() }, selectedMembers); @@ -197,6 +309,110 @@ function FolderModal({ )} + {/* Fichiers attachés */} +
+

Fichiers attachés

+ + {/* Liste des fichiers */} +
+ {(folderData.attachedFiles || []).map((file) => ( +
+
+
+ {file.type.startsWith('image/') ? ( + + + + ) : file.type === 'application/pdf' ? ( + + + + ) : ( + + + + )} +
+
+

{file.name}

+

{formatFileSize(file.size)}

+
+
+
+ + {!readOnly && ( + + )} +
+
+ ))} +
+ + {/* Zone de téléchargement */} + {!readOnly && ( +
+
+ + +
+ + {/* Message d'erreur */} + {uploadError && ( +
+ {uploadError} +
+ )} + + {/* Informations sur les types de fichiers autorisés */} +
+ Types autorisés: PDF, Images (PNG, JPG, GIF, WebP), Documents (DOC, DOCX, XLS, XLSX), TXT +
+ Taille maximale: {formatFileSize(MAX_FILE_SIZE)} +
+
+ )} +
+ {/* Informations système */}

Informations système