"use client" import { useState, useEffect, useCallback } from "react" import { Card, CardContent } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Folder, Search, FolderPlus, Clock, ChevronRight, SortAsc, SortDesc, X, } from "lucide-react" import { FolderData, FolderCreated, FolderPrivateFields, setDefaultFolderRoles } from "@/lib/4nk/models/FolderData" import MessageBus from "@/lib/4nk/MessageBus" import { iframeUrl } from "@/app/page" import UserStore from "@/lib/4nk/UserStore" import FolderModal from "@/components/4nk/FolderModal" import AuthModal from "@/components/4nk/AuthModal" import Iframe from "@/components/4nk/Iframe" type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre'; export default function FoldersPage() { const [searchTerm, setSearchTerm] = useState("") const [sortBy, setSortBy] = useState("updated_at") const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc") const [currentPath, setCurrentPath] = useState(["Racine"]) const [folderType, setFolderType] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); // 4NK Integration states const [isConnected, setIsConnected] = useState(false) const [showAuthModal, setShowAuthModal] = useState(false) const [processes, setProcesses] = useState(null) const [myProcesses, setMyProcesses] = useState([]) const [userPairingId, setUserPairingId] = useState(null) const [folderProcesses, setFolderProcesses] = useState(null) const [myFolderProcesses, setMyFolderProcesses] = useState([]) const [folderPrivateData, setFolderPrivateData] = useState>>({}) const [loadingFolders, setLoadingFolders] = useState(false) // Modal states const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null) const [folders, setFolders] = useState([]) const [stats, setStats] = useState({ total: 0 }) // Notification system const showNotification = (type: "success" | "error" | "info", message: string) => { setNotification({ type, message }) setTimeout(() => setNotification(null), 5000) } // Function to fetch folder private data const fetchFolderPrivateData = async (processId: string, stateId: string) => { if (!myFolderProcesses.includes(processId)) return; try { const messageBus = MessageBus.getInstance(iframeUrl); await messageBus.isReady(); const data = await messageBus.getData(processId, stateId); setFolderPrivateData(prev => ({ ...prev, [stateId]: data })); } catch (err) { console.error('Error fetching folder private data:', err); } }; // Function to load folders from 4NK processes (adapted to new FolderData model) const loadFoldersFrom4NK = useCallback(() => { if (!folderProcesses) return; const folderData: FolderData[] = []; let hasAllPrivateData = true; let hasFoldersToLoad = false; Object.entries(folderProcesses).forEach(([processId, process]: [string, any]) => { // Only include processes that belong to the user (myFolderProcesses) if (!myFolderProcesses.includes(processId)) return; // Check if this process has a folderNumber in pcd_commitment const latestState = process.states[0]; if (!latestState) return; const folderNumber = latestState.pcd_commitment?.folderNumber; if (!folderNumber) return; // Skip processes without folderNumber hasFoldersToLoad = true; // We have at least one folder to load // Get private data for this state if available const privateData = folderPrivateData[latestState.state_id]; // If we don't have private data yet, trigger fetch and mark as incomplete if (!privateData) { hasAllPrivateData = false; setTimeout(() => fetchFolderPrivateData(processId, latestState.state_id), 0); return; // Skip creating folder until we have private data } // Create folder with new simplified model const folder: FolderData = { folderNumber: folderNumber, name: privateData.name || `Dossier ${folderNumber}`, description: privateData.description || '', created_at: privateData.created_at || new Date().toISOString(), updated_at: privateData.updated_at || new Date().toISOString(), notes: privateData.notes || [] }; folderData.push(folder); }); // Manage loading state if (hasFoldersToLoad && !hasAllPrivateData) { setLoadingFolders(true); } else if (hasAllPrivateData) { setLoadingFolders(false); setFolders(folderData); // Update stats setStats({ total: folderData.length }); } }, [folderProcesses, myFolderProcesses, folderPrivateData, fetchFolderPrivateData]); // 4NK Integration useEffects useEffect(() => { // Load folders from 4NK when folder processes are available if (folderProcesses && myFolderProcesses.length >= 0) { loadFoldersFrom4NK(); } else { // Fallback: load empty folders if not connected to 4NK const mockFolders: FolderData[] = [] setFolders(mockFolders) setStats({ total: mockFolders.length, shared: mockFolders.filter((folder) => folder.access === "shared").length, private: mockFolders.filter((folder) => folder.access === "private").length, thisWeek: mockFolders.filter((folder) => { const weekAgo = new Date() weekAgo.setDate(weekAgo.getDate() - 7) return folder.modified > weekAgo }).length, permanent: mockFolders.filter((folder) => folder.storageType === "permanent").length, temporary: mockFolders.filter((folder) => folder.storageType === "temporary").length, }) } }, [folderProcesses, myFolderProcesses, loadFoldersFrom4NK]) // Update folders when private data changes useEffect(() => { if (folderProcesses && Object.keys(folderPrivateData).length > 0) { loadFoldersFrom4NK(); } }, [folderPrivateData, folderProcesses, loadFoldersFrom4NK]) // 4NK Authentication handlers const handleLogin = useCallback(() => { setShowAuthModal(true); }, []); const handleLogout = useCallback(() => { UserStore.getInstance().disconnect(); setIsConnected(false); setProcesses(null); setMyProcesses([]); setUserPairingId(null); // Clear folder-related states setFolderProcesses(null); setMyFolderProcesses([]); setFolderPrivateData({}); setFolders([]); setLoadingFolders(false); // Émettre un événement pour vider les messages locaux EventBus.getInstance().emit('CLEAR_CONSOLE'); showNotification("info", "Déconnexion réussie"); }, []); const handleAuthConnect = useCallback(() => { setIsConnected(true); setShowAuthModal(false); console.log('Auth Connect - Connexion établie, le useEffect se chargera de récupérer le userPairingId'); showNotification("success", "Connexion 4NK réussie"); }, []); const handleAuthClose = useCallback(() => { setShowAuthModal(false); }, []); // Fonction pour envoyer une notification dans le chat du dossier const sendFolderChatNotification = (folderId: string, message: string, actionType: string) => { const folderUsers = users.filter((user) => user.folderRoles[folderId]) console.log("Notification envoyée dans le chat du dossier:", { folderId, recipients: folderUsers.map((u) => ({ name: u.name, role: u.folderRoles[folderId]?.role })), message, actionType, timestamp: new Date().toISOString(), }) folderUsers.forEach((user) => { console.log(`📱 Notification push envoyée à ${user.name} (${user.email})`) }) } // Fonction pour organiser les utilisateurs par rôles const organizeUsersForInvitation = (currentFolderId: string) => { const organized = { folderRoles: {} as { [role: string]: UserWithRoles[] }, spaceRoles: {} as { [role: string]: UserWithRoles[] }, otherSpaces: {} as { [spaceName: string]: UserWithRoles[] }, } users.forEach((user) => { if (user.folderRoles[currentFolderId]) { const role = user.folderRoles[currentFolderId].role if (!organized.folderRoles[role]) organized.folderRoles[role] = [] organized.folderRoles[role].push(user) } const spaceRole = user.spaceRole if (!organized.spaceRoles[spaceRole]) organized.spaceRoles[spaceRole] = [] organized.spaceRoles[spaceRole].push(user) Object.values(user.spaceRoles).forEach((spaceInfo) => { if (spaceInfo.spaceName !== "Espace Principal") { if (!organized.otherSpaces[spaceInfo.spaceName]) organized.otherSpaces[spaceInfo.spaceName] = [] organized.otherSpaces[spaceInfo.spaceName].push(user) } }) }) return organized } // Folder actions const handleOpenFolder = (folder: FolderData) => { // Rediriger vers la page documents avec le filtre du dossier router.push(`/dashboard/documents?folder=${encodeURIComponent(folder.name)}`) } const handleInviteFolder = (folder: FolderData) => { setInviteMessage("") setSelectedUser("") setSelectedRole("") setInviteScope("user") setActionModal({ type: "invite", folder, folders: [] }) } const handleArchiveFolder = (folder: FolderData) => { setArchiveReason("") setRetentionPeriod("5") setActionModal({ type: "archive", folder, folders: [] }) } const handleStorageConfig = (folder: FolderData) => { setStorageDuration(folder.temporaryStorageConfig?.duration.toString() || "30") setDataUsage(folder.temporaryStorageConfig?.dataUsage || "") setThirdPartyAccess(folder.temporaryStorageConfig?.thirdPartyAccess || "") setActionModal({ type: "storage_config", folder, folders: [] }) } const handleAIAnalysis = (folder: FolderData) => { showNotification("info", `Analyse IA en cours pour ${folder.name}...`) // Simuler une analyse IA setTimeout( () => { const analysisResults = [ `📊 **Analyse du dossier "${folder.name}"**\n\n` + `**Contenu :** ${folder.documentsCount} documents analysés (${folder.size})\n` + `**Thématiques principales :** ${folder.tags.join(", ")}\n` + `**Niveau d'activité :** ${folder.activity.length > 2 ? "Élevé" : "Modéré"} (dernière modification ${formatDate(folder.modified)})\n\n` + `**Recommandations :**\n` + `• ${folder.storageType === "temporary" ? "Considérer l'archivage vers le stockage permanent" : "Dossier déjà archivé de manière optimale"}\n` + `• ${folder.access === "private" ? "Évaluer les possibilités de partage avec l'équipe" : "Partage actuel avec " + folder.members.length + " membre(s)"}\n` + `• Dernière activité significative détectée il y a ${Math.floor(Math.random() * 7) + 1} jour(s)\n\n` + `**Score de pertinence :** ${Math.floor(Math.random() * 30) + 70}/100`, `🔍 **Analyse approfondie du dossier "${folder.name}"**\n\n` + `**Structure documentaire :**\n` + `• ${Math.floor(folder.documentsCount * 0.4)} documents principaux\n` + `• ${Math.floor(folder.documentsCount * 0.3)} documents de support\n` + `• ${Math.floor(folder.documentsCount * 0.3)} documents annexes\n\n` + `**Analyse temporelle :**\n` + `• Création : ${folder.created.toLocaleDateString("fr-FR")}\n` + `• Pic d'activité détecté en ${new Date().toLocaleDateString("fr-FR", { month: "long", year: "numeric" })}\n` + `• Tendance : ${Math.random() > 0.5 ? "Croissante" : "Stable"}\n\n` + `**Recommandations stratégiques :**\n` + `• ${folder.documentsCount > 50 ? "Envisager une réorganisation en sous-dossiers" : "Structure actuelle optimale"}\n` + `• ${folder.members.length < 3 ? "Potentiel de collaboration à explorer" : "Équipe collaborative active"}\n` + `• Prochaine révision recommandée : ${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString("fr-FR")}`, `🎯 **Insights IA pour "${folder.name}"**\n\n` + `**Analyse sémantique :**\n` + `• Cohérence thématique : ${Math.floor(Math.random() * 20) + 80}%\n` + `• Mots-clés dominants : ${folder.tags.slice(0, 3).join(", ")}\n` + `• Complexité moyenne : ${["Faible", "Modérée", "Élevée"][Math.floor(Math.random() * 3)]}\n\n` + `**Patterns détectés :**\n` + `• ${Math.random() > 0.5 ? "Cycle de révision régulier identifié" : "Activité sporadique détectée"}\n` + `• ${Math.random() > 0.5 ? "Collaboration inter-équipes active" : "Usage principalement individuel"}\n` + `• ${folder.storageType === "permanent" ? "Archivage conforme aux bonnes pratiques" : "Optimisation de stockage possible"}\n\n` + `**Actions suggérées :**\n` + `• ${Math.random() > 0.5 ? "Créer un template basé sur ce dossier" : "Standardiser la nomenclature"}\n` + `• ${Math.random() > 0.5 ? "Planifier une session de nettoyage" : "Maintenir la structure actuelle"}\n` + `• Prochaine analyse automatique : ${new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toLocaleDateString("fr-FR")}`, ] const randomAnalysis = analysisResults[Math.floor(Math.random() * analysisResults.length)] // Envoyer l'analyse dans le chat du dossier sendFolderChatNotification(folder.id.toString(), `🤖 ${randomAnalysis}`, "ai_analysis") showNotification("success", `Analyse IA terminée pour ${folder.name}. Redirection vers le chat...`) // Rediriger vers le chat après 1.5 secondes setTimeout(() => { router.push("/dashboard/chat") }, 1500) }, 2000 + Math.random() * 3000, ) } const handleViewCertificate = (folder: FolderData) => { setActionModal({ type: "certificate", folder, folders: [] }) } const handleViewDocumentsCertificates = (folder: FolderData) => { setActionModal({ type: "documents_certificates", folder, folders: [] }) } const handleDownloadCertificate = (folder: FolderData) => { if (folder.status === "validated") { showNotification("info", `Téléchargement du certificat blockchain pour le dossier ${folder.name}...`) sendFolderChatNotification( folder.id.toString(), `🔗 Certificat blockchain du dossier téléchargé`, "folder_blockchain_certificate_download", ) setTimeout(() => { showNotification("success", `Certificat blockchain du dossier ${folder.name} téléchargé avec succès`) }, 2000) } } const handleManageRoles = (folder: FolderData) => { // Rediriger vers la gestion des rôles du dossier router.push(`/dashboard/folders/${folder.id}/roles`) } const handleRequestDocument = (folder: FolderData) => { setSelectedDocument("") setRequestMessage("") setActionModal({ type: "request_document", folder, folders: [] }) } const handleDeleteFolder = (folder: FolderData) => { setActionModal({ type: "delete", folder, folders: [] }) } const handleOpenModal = (type: FolderType) => { setFolderType(type); setIsModalOpen(true); setMenuOpen(false); }; const handleCloseModal = () => { setIsModalOpen(false); setFolderType(null); }; const handleSaveNewFolder = useCallback( (folderData: SDKFolderData) => { if (!isConnected || !userPairingId) { console.error('Conditions non remplies:', { isConnected, userPairingId }); showNotification( "error", `Vous devez être connecté à 4NK pour créer un dossier (Connected: ${isConnected}, PairingId: ${userPairingId ? 'OK' : 'NULL'})` ); return; } // Ajout du type dans les données du dossier const folderToCreate = { ...folderData, type: folderType }; const roles = setDefaultFolderRoles(userPairingId, [], []); const folderPrivateFields = FolderPrivateFields; MessageBus.getInstance(iframeUrl) .createFolder(folderToCreate, folderPrivateFields, roles) .then((_folderCreated: FolderCreated) => { const firstStateId = _folderCreated.process.states[0].state_id; MessageBus.getInstance(iframeUrl) .notifyProcessUpdate(_folderCreated.processId, firstStateId) .then(() => MessageBus.getInstance(iframeUrl) .validateState(_folderCreated.processId, firstStateId) .then(() => MessageBus.getInstance(iframeUrl) .getProcesses() .then(async (processes: any) => { setProcesses(processes) setFolderProcesses(processes) // Update folder processes as well }) ) ); setShowCreateFolderModal(false); showNotification("success", `Dossier "${folderData.name}" créé avec succès sur 4NK`); }) .catch((error) => { console.error('Erreur lors de la création du dossier 4NK:', error); showNotification("error", "Erreur lors de la création du dossier"); }); }, [userPairingId, isConnected, iframeUrl, folderType] ); const handleToggleFavorite = (folderId: number) => { const folder = folders.find((f) => f.id === folderId) if (!folder) return setFolders((prev) => prev.map((f) => (f.id === folderId ? { ...f, favorite: !f.favorite } : f))) const action = folder.favorite ? "retiré des" : "ajouté aux" showNotification("success", `${folder.name} ${action} favoris`) sendFolderChatNotification(folderId.toString(), `⭐ Le dossier a été ${action} favoris`, "favorite") } // Bulk actions const handleBulkDownload = () => { const selectedFolderData = folders.filter((folder) => selectedFolders.includes(folder.id)) showNotification("info", `Téléchargement de ${selectedFolderData.length} dossier(s)...`) selectedFolderData.forEach((folder) => { sendFolderChatNotification(folder.id.toString(), `📥 Le dossier a été téléchargé`, "download") }) setTimeout(() => { showNotification("success", `${selectedFolderData.length} dossier(s) téléchargé(s) avec succès`) setSelectedFolders([]) }, 2000) } const handleBulkInvite = () => { const selectedFolderData = folders.filter( (folder) => selectedFolders.includes(folder.id) && folder.permissions.canInvite, ) if (selectedFolderData.length === 0) { showNotification("error", "Aucun dossier sélectionné ne peut être partagé") return } setInviteMessage("") setSelectedUser("") setSelectedRole("") setInviteScope("user") setActionModal({ type: "invite", folder: null, folders: selectedFolderData }) } const handleBulkArchive = () => { const selectedFolderData = folders.filter( (folder) => selectedFolders.includes(folder.id) && folder.permissions.canArchive, ) if (selectedFolderData.length === 0) { showNotification("error", "Aucun dossier sélectionné ne peut être archivé") return } setArchiveReason("") setRetentionPeriod("5") setActionModal({ type: "archive", folder: null, folders: selectedFolderData }) } const handleBulkAIAnalysis = () => { const selectedFolderData = folders.filter( (folder) => selectedFolders.includes(folder.id) && folder.permissions.canAnalyze, ) if (selectedFolderData.length === 0) { showNotification("error", "Aucun dossier sélectionné ne peut être analysé") return } showNotification("info", `Analyse IA en cours pour ${selectedFolderData.length} dossier(s)...`) // Analyser chaque dossier avec un délai échelonné selectedFolderData.forEach((folder, index) => { setTimeout(() => { const bulkAnalysis = `📊 **Analyse IA groupée - Dossier "${folder.name}"**\n\n` + `**Position dans l'analyse :** ${index + 1}/${selectedFolderData.length}\n` + `**Contenu :** ${folder.documentsCount} documents (${folder.size})\n` + `**Tags :** ${folder.tags.join(", ")}\n\n` + `**Analyse comparative :**\n` + `• Taille relative : ${folder.documentsCount > 40 ? "Au-dessus de la moyenne" : "Dans la moyenne"}\n` + `• Activité : ${folder.activity.length > 1 ? "Active" : "Modérée"}\n` + `• Collaboration : ${folder.members.length} membre(s)\n\n` + `**Recommandation :** ${folder.storageType === "temporary" ? "Candidat à l'archivage" : "Archivage optimal"}\n` + `**Score global :** ${Math.floor(Math.random() * 30) + 70}/100` sendFolderChatNotification(folder.id.toString(), `🤖 ${bulkAnalysis}`, "bulk_ai_analysis") }, index * 1500) // Échelonner les analyses }) setTimeout( () => { const totalDocs = selectedFolderData.reduce((sum, folder) => sum + folder.documentsCount, 0) showNotification( "success", `Analyse IA terminée pour ${selectedFolderData.length} dossier(s) (${totalDocs} documents). Redirection vers le chat...`, ) setSelectedFolders([]) // Rediriger vers le chat après l'analyse groupée setTimeout(() => { router.push("/dashboard/chat") }, 1500) }, selectedFolderData.length * 1500 + 1000, ) } const handleBulkDelete = () => { const selectedFolderData = folders.filter( (folder) => selectedFolders.includes(folder.id) && folder.permissions.canDelete, ) if (selectedFolderData.length === 0) { showNotification("error", "Aucun dossier sélectionné ne peut être supprimé") return } setActionModal({ type: "delete", folder: null, folders: selectedFolderData }) } // Modal actions const confirmInvite = () => { const recipient = inviteScope === "user" ? users.find((u) => u.id === selectedUser)?.name : roles.find((r) => r.id === selectedRole)?.name if (actionModal.folder) { showNotification("success", `${actionModal.folder.name} partagé avec ${recipient}. Un message a été envoyé.`) sendFolderChatNotification( actionModal.folder.id.toString(), `👥 Le dossier a été partagé avec ${recipient}. Message: ${inviteMessage}`, "invite", ) } else if (actionModal.folders.length > 0) { actionModal.folders.forEach((folder) => { sendFolderChatNotification( folder.id.toString(), `👥 Le dossier a été partagé avec ${recipient}. Message: ${inviteMessage}`, "bulk_invite", ) }) showNotification( "success", `${actionModal.folders.length} dossier(s) partagé(s) avec ${recipient}. Messages envoyés.`, ) setSelectedFolders([]) } setActionModal({ type: null, folder: null, folders: [] }) } const confirmRequestDocument = () => { if (actionModal.folder && selectedDocument) { const document = actionModal.folder.expectedDocuments.find((doc) => doc.name === selectedDocument) if (document) { // Trouver l'utilisateur avec le rôle assigné const assignedUser = users.find( (user) => user.folderRoles[actionModal.folder!.id.toString()]?.role === document.assignedRole, ) if (assignedUser) { // Préparer les données pour le chat const messageData = { userName: assignedUser.name, subject: `Demande de document - ${selectedDocument}`, content: `Bonjour ${assignedUser.name},\n\nPouvez-vous fournir le document "${selectedDocument}" pour le dossier "${actionModal.folder.name}" ?\n\n${requestMessage}\n\nMerci !`, } // Stocker dans sessionStorage pour le chat sessionStorage.setItem("newMessage", JSON.stringify(messageData)) showNotification("success", `Demande envoyée à ${assignedUser.name}. Redirection vers le chat...`) // Rediriger vers le chat avec l'utilisateur setTimeout(() => { router.push(`/dashboard/chat?user=${assignedUser.id}&message=new`) }, 1500) } else { showNotification("error", "Aucun utilisateur trouvé avec le rôle requis pour ce document") } } } setActionModal({ type: null, folder: null, folders: [] }) } const confirmStorageConfig = () => { if (actionModal.folder) { const updatedFolder = { ...actionModal.folder, temporaryStorageConfig: { duration: Number.parseInt(storageDuration), dataUsage: dataUsage, thirdPartyAccess: thirdPartyAccess, }, modified: new Date(), } setFolders((prev) => prev.map((f) => (f.id === updatedFolder.id ? updatedFolder : f))) showNotification("success", `Configuration du stockage temporaire mise à jour pour ${actionModal.folder.name}`) // Notification dans le chat du dossier const message = `⚙️ Configuration du stockage temporaire mise à jour :\n• Durée : ${storageDuration} jours\n• Usage : ${dataUsage}\n• Accès tiers : ${thirdPartyAccess}` sendFolderChatNotification(actionModal.folder.id.toString(), message, "storage_config") } setActionModal({ type: null, folder: null, folders: [] }) } const confirmArchive = () => { if (actionModal.folder) { const updatedFolder = { ...actionModal.folder, storageType: "permanent" as const, modified: new Date(), } setFolders((prev) => prev.map((f) => (f.id === updatedFolder.id ? updatedFolder : f))) showNotification( "success", `${actionModal.folder.name} et tous ses documents archivés vers le stockage permanent`, ) // Notification dans le chat du dossier let message = `📦 Le dossier et tous ses ${actionModal.folder.documentsCount} document(s) ont été archivés vers le stockage permanent (conservation: ${retentionPeriod} ans)` if (archiveReason.trim()) { message += ` - Raison: ${archiveReason}` } sendFolderChatNotification(actionModal.folder.id.toString(), message, "archive") } else if (actionModal.folders.length > 0) { const folderIds = actionModal.folders.map((f) => f.id) setFolders((prev) => prev.map((f) => folderIds.includes(f.id) ? { ...f, storageType: "permanent" as const, modified: new Date(), } : f, ), ) actionModal.folders.forEach((folder) => { let message = `📦 Le dossier et tous ses ${folder.documentsCount} document(s) ont été archivés vers le stockage permanent (conservation: ${retentionPeriod} ans)` if (archiveReason.trim()) { message += ` - Raison: ${archiveReason}` } sendFolderChatNotification(folder.id.toString(), message, "bulk_archive") }) const totalDocuments = actionModal.folders.reduce((sum, folder) => sum + folder.documentsCount, 0) showNotification( "success", `${actionModal.folders.length} dossier(s) et ${totalDocuments} document(s) archivés vers le stockage permanent`, ) setSelectedFolders([]) } setActionModal({ type: null, folder: null, folders: [] }) } const confirmCreate = () => { const newFolder: FolderData = { id: Math.max(...folders.map((f) => f.id)) + 1, name: folderName, description: folderDescription, documentsCount: 0, subfoldersCount: 0, size: "0 MB", created: new Date(), modified: new Date(), owner: "Utilisateur actuel", access: folderAccess, members: ["Utilisateur actuel"], tags: folderTags .split(",") .map((tag) => tag.trim()) .filter((tag) => tag), color: folderColor, favorite: false, storageType: "temporary", status: "active", type: "general", expectedDocuments: [], activity: [], permissions: { canView: true, canEdit: true, canDelete: true, canInvite: true, canArchive: true, canAnalyze: true, }, documents: [], } setFolders((prev) => [...prev, newFolder]) showNotification("success", `Dossier "${folderName}" créé avec succès`) setActionModal({ type: null, folder: null, folders: [] }) } const confirmDelete = () => { if (actionModal.folder) { sendFolderChatNotification(actionModal.folder.id.toString(), `🗑️ Le dossier a été supprimé`, "delete") setFolders((prev) => prev.filter((f) => f.id !== actionModal.folder!.id)) showNotification("success", `${actionModal.folder.name} supprimé`) } else if (actionModal.folders.length > 0) { actionModal.folders.forEach((folder) => { sendFolderChatNotification(folder.id.toString(), `🗑️ Le dossier a été supprimé`, "bulk_delete") }) const folderIds = actionModal.folders.map((f) => f.id) setFolders((prev) => prev.filter((f) => !folderIds.includes(f.id))) showNotification("success", `${actionModal.folders.length} dossier(s) supprimé(s)`) setSelectedFolders([]) } setActionModal({ type: null, folder: null, folders: [] }) } const typeFilter = searchParams.get("type") const filteredFolders = folders .filter((folder) => { if (searchTerm && !folder.name.toLowerCase().includes(searchTerm.toLowerCase())) { return false } if (typeFilter && folder.type !== typeFilter) { return false } if (filterAccess !== "all" && folder.access !== filterAccess) { return false } if (filterOwner !== "all" && folder.owner !== filterOwner) { return false } if (filterStorage !== "all" && folder.storageType !== filterStorage) { return false } return true }) .sort((a, b) => { let aValue, bValue switch (sortBy) { case "name": aValue = a.name.toLowerCase() bValue = b.name.toLowerCase() break case "size": aValue = Number.parseFloat(a.size.replace(/[^\d.]/g, "")) bValue = Number.parseFloat(b.size.replace(/[^\d.]/g, "")) break case "owner": aValue = a.owner.toLowerCase() bValue = b.owner.toLowerCase() break case "documents": aValue = a.documentsCount bValue = b.documentsCount break case "modified": default: aValue = a.modified.getTime() bValue = b.modified.getTime() break } if (sortOrder === "asc") { return aValue > bValue ? 1 : -1 } else { return aValue < bValue ? 1 : -1 } }) const getFolderColor = (color: string) => { const colorObj = colors.find((c) => c.id === color) return colorObj?.class || "text-gray-600 bg-gray-100" } const isNewFolder = (folder: FolderData) => { const now = Date.now() const diffMs = now - folder.modified.getTime() const twoDaysMs = 2 * 24 * 60 * 60 * 1000 return diffMs <= twoDaysMs } const getStorageIcon = (storageType: string) => { return storageType === "permanent" ? ( ) : ( ) } const getRoleIcon = (role: string) => { switch (role) { case "owner": return case "editor": return case "validator": return case "contributor": return case "viewer": return case "admin": return case "manager": return case "user": return case "guest": return default: return } } const getStatusBadge = (status: string) => { switch (status) { case "active": return Actif case "pending": return En attente case "completed": return Terminé case "archived": return Archivé case "validated": return Validé default: return Inconnu } } const formatDate = (date: Date) => { const now = new Date() const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60) if (diffInHours < 1) { return "Il y a quelques minutes" } else if (diffInHours < 24) { return `Il y a ${Math.floor(diffInHours)} heure${Math.floor(diffInHours) > 1 ? "s" : ""}` } else if (diffInHours < 48) { return "Hier" } else { return date.toLocaleDateString("fr-FR") } } const toggleFolderSelection = (folderId: number) => { setSelectedFolders((prev) => (prev.includes(folderId) ? prev.filter((id) => id !== folderId) : [...prev, folderId])) } const selectAllFolders = () => { if (selectedFolders.length === filteredFolders.length) { setSelectedFolders([]) } else { setSelectedFolders(filteredFolders.map((folder) => folder.id)) } } return (
{/* Notification */} {notification && (
{notification.type === "success" && } {notification.type === "error" && } {notification.type === "info" && } {notification.message}
)} {/* Header */}

Dossiers

Organisez vos documents par dossiers

{isConnected ? (
{/* Nouveau dossier avec menu */}
{menuOpen && (
)}
{/* Déconnexion */}
) : ( )}
{/* Breadcrumb */}
{currentPath.map((path, index) => (
{index > 0 && }
))}
{/* Search and Filters */}
{/* Advanced Filters */} {showFilters && (
)}
{/* Bulk Actions minimalistes: certificats et rôles uniquement */} {selectedFolders.length > 0 && (
{selectedFolders.length} dossier{selectedFolders.length > 1 ? "s" : ""} sélectionné {selectedFolders.length > 1 ? "s" : ""}
)} {/* Folders List/Grid */} {viewMode === "list" ? (
{filteredFolders.map((folder) => ( ))}
Nom Taille Modifié Propriétaire Accès Statut
toggleFolderSelection(folder.id)} />
handleOpenFolder(folder)} > {folder.name} {isNewFolder(folder) && ( NEW )} {getStorageIcon(folder.storageType)} {folder.access === "private" && ( )}

{folder.description}

{folder.size} {formatDate(folder.modified)} {folder.owner} {folder.access === "shared" ? "Partagé" : "Privé"} {getStatusBadge(folder.status)}
) : (
{filteredFolders.map((folder) => (
handleOpenFolder(folder)} >
e.stopPropagation()}> toggleFolderSelection(folder.id)} />
e.stopPropagation()} > {isNewFolder(folder) && ( NEW )} {folder.access === "private" && } {folder.storageType === "temporary" && ( )} {folder.status === "validated" && ( )} {folder.documents && folder.documents.some((doc) => doc.hasCertificate) && ( )}

{folder.name}

{folder.description}

{folder.size}

{formatDate(folder.modified)}

{getStorageIcon(folder.storageType)} {folder.storageType === "permanent" ? "Permanent" : "Temporaire"}
{folder.temporaryStorageConfig && folder.storageType === "temporary" && (
Durée: {folder.temporaryStorageConfig.duration} jours
)}
{getStatusBadge(folder.status)}
{folder.access === "shared" ? "Partagé" : "Privé"}
{/* Recent Activity */}

Activité récente

{folder.activity.slice(0, 2).map((activity, index) => (
{activity.user} a {activity.action}{" "} {activity.item}
{activity.time}
))}
))}
)} {loadingFolders && isConnected && (

Chargement des dossiers...

Récupération des données privées depuis 4NK

)} {!loadingFolders && filteredFolders.length === 0 && (

Aucun dossier trouvé

{searchTerm || filterAccess !== "all" || filterOwner !== "all" || filterStorage !== "all" ? "Essayez de modifier vos critères de recherche" : "Commencez par créer votre premier dossier"}

)}
{/* ProcessesViewer Card */}

Processus Blockchain

{/* Intégration du ProcessesViewer */}
{/* Modals */} {actionModal.type && (
{/* Documents Certificates Modal */} {actionModal.type === "documents_certificates" && actionModal.folder && ( <>

Certificats des documents - {actionModal.folder.name}

Certificats des documents

Téléchargez les certificats blockchain individuels des documents de ce dossier

{actionModal.folder.documents?.map((doc) => (
{doc.name}
{doc.hasCertificate && doc.certificateId && (

ID: {doc.certificateId}

)}
{doc.hasCertificate ? ( <> Certifié ) : ( Non certifié )}
))}
Actions groupées
)} {/* Storage Config Modal */} {actionModal.type === "storage_config" && actionModal.folder && ( <>

Configuration du stockage temporaire

Configuration du stockage temporaire

Configurez la durée de conservation et les informations d'usage pour le dossier{" "} {actionModal.folder.name} en stockage temporaire.

Durée pendant laquelle les données seront conservées en stockage temporaire avant archivage automatique