diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 1c82c2a..47c95d3 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -309,25 +309,79 @@ export default function DashboardPage() { Fichiers - {/* - {selectedFolder.files && selectedFolder.files.length > 0 ? ( + + {(() => { + const files = selectedFolder?.attachedFiles; + if (!files) return false; + + if (typeof files === 'object') { + return Object.keys(files).length > 0; + } + return false; + })() ? (
- {selectedFolder.files.map((file, index) => ( -
-
- + {Object.entries(selectedFolder.attachedFiles || {}).map(([key, file]: [string, any]) => { + + return ( +
+
+
+ {(file instanceof Map ? file.get('type') : file?.type)?.startsWith('image/') ? ( + + + + ) : (file instanceof Map ? file.get('type') : file?.type) === 'application/pdf' ? ( + + + + ) : ( + + + + )} +
-

{file.name || 'Fichier'}

-

{formatBytes(file.size || 0)}

+

+ {(file instanceof Map ? file.get('name') : file?.name) || 'Fichier'} +

+

+ {(() => { + const size = file instanceof Map ? file.get('size') : file?.size; + return size ? formatBytes(size) : 'Taille inconnue'; + })()} +

+
- ))} + ); + })}
) : (

Aucun fichier dans ce dossier.

)} - */} +
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

diff --git a/lib/4nk/models/FolderData.ts b/lib/4nk/models/FolderData.ts index dbe9ba5..39935ad 100644 --- a/lib/4nk/models/FolderData.ts +++ b/lib/4nk/models/FolderData.ts @@ -22,6 +22,15 @@ export interface FolderChatData { data?: FolderChatAttachment[]; } +export interface AttachedFile { + id: string; + name: string; + type: string; // MIME type + size: number; // taille en bytes + base64Data: string; // contenu du fichier en base64 + uploadedAt: string; // timestamp ISO +} + export interface FolderData { folderNumber: string; name: string; @@ -31,6 +40,7 @@ export interface FolderData { notes: string[]; messages: FolderChatData[]; messages_owner: FolderChatData[]; + attachedFiles?: AttachedFile[]; } export function isFolderData(data: any): data is FolderData { @@ -65,6 +75,7 @@ const emptyFolderData: FolderData = { notes: [], messages: [], messages_owner: [], + attachedFiles: [], }; const folderDataFields: string[] = Object.keys(emptyFolderData); diff --git a/lib/contexts/FourNKContext.tsx b/lib/contexts/FourNKContext.tsx index eb084ae..eb3aa97 100644 --- a/lib/contexts/FourNKContext.tsx +++ b/lib/contexts/FourNKContext.tsx @@ -126,6 +126,7 @@ export function FourNKProvider({ children }: { children: ReactNode }) { notes: basePrivateData.notes || [], messages: mergedMessages || [], messages_owner: mergedMessagesOwner || [], + attachedFiles: basePrivateData.attachedFiles || [], }); });