Restore complete 4NK integration workflow

This commit is contained in:
omaroughriss 2025-10-23 16:38:43 +02:00
parent 6d65014a45
commit 8b6b62e643

View File

@ -131,61 +131,166 @@ export default function FoldersPage() {
// 4NK Integration useEffects
useEffect(() => {
// Load folders from 4NK when folder processes are available
const userStore = UserStore.getInstance();
const connected = userStore.isConnected();
const pairingId = userStore.getUserPairingId();
console.log('Initial 4NK state:', { connected, pairingId });
setIsConnected(connected);
setUserPairingId(pairingId);
}, []);
useEffect(() => {
const handleConnectionFlow = async () => {
if (!isConnected) return;
try {
const messageBus = MessageBus.getInstance(iframeUrl);
await messageBus.isReady();
const userStore = UserStore.getInstance();
let pairingId = userStore.getUserPairingId();
// 1⃣ Créer ou récupérer le pairing
if (!pairingId) {
pairingId = await messageBus.createUserPairing();
console.log("✅ Pairing created:", pairingId);
if (pairingId) {
userStore.pair(pairingId);
setUserPairingId(pairingId);
}
} else {
console.log("🔗 Already paired with ID:", pairingId);
}
// 2⃣ Charger les processes
const processes = await messageBus.getProcesses();
setProcesses(processes);
setFolderProcesses(processes);
// 3⃣ Charger les myProcesses
const myProcesses = await messageBus.getMyProcesses();
setMyProcesses(myProcesses);
setMyFolderProcesses(myProcesses);
} catch (err) {
console.error("❌ Error during pairing or process loading:", err);
}
};
handleConnectionFlow();
}, [isConnected, iframeUrl]);
// Load folders from 4NK when folder processes are available
useEffect(() => {
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])
}, [folderProcesses, myFolderProcesses, loadFoldersFrom4NK]);
// Update folders when private data changes
useEffect(() => {
if (folderProcesses && Object.keys(folderPrivateData).length > 0) {
loadFoldersFrom4NK();
}
}, [folderPrivateData, folderProcesses, loadFoldersFrom4NK])
}, [folderPrivateData, loadFoldersFrom4NK, folderProcesses]);
// 4NK Authentication handlers
const handleLogin = useCallback(() => {
setShowAuthModal(true);
}, []);
const handleLogout = useCallback(() => {
UserStore.getInstance().disconnect();
setIsConnected(false);
setProcesses(null);
setMyProcesses([]);
setUserPairingId(null);
// Filter and sort folders
const filteredFolders = folders.filter(folder => {
const matchesSearch = folder.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
folder.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
folder.folderNumber.toLowerCase().includes(searchTerm.toLowerCase())
// Clear folder-related states
setFolderProcesses(null);
setMyFolderProcesses([]);
setFolderPrivateData({});
setFolders([]);
setLoadingFolders(false);
return matchesSearch
})
// Émettre un événement pour vider les messages locaux
EventBus.getInstance().emit('CLEAR_CONSOLE');
const sortedFolders = [...filteredFolders].sort((a, b) => {
let aValue: any, bValue: any
switch (sortBy) {
case "name":
aValue = a.name.toLowerCase()
bValue = b.name.toLowerCase()
break
case "created_at":
aValue = new Date(a.created_at)
bValue = new Date(b.created_at)
break
case "updated_at":
aValue = new Date(a.updated_at)
bValue = new Date(b.updated_at)
break
default:
aValue = a.name.toLowerCase()
bValue = b.name.toLowerCase()
}
if (sortOrder === "asc") {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0
} else {
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0
}
})
showNotification("info", "Déconnexion réussie");
}, []);
// Modal handlers
const handleOpenModal = (type: FolderType) => {
setFolderType(type);
setIsModalOpen(true);
};
const handleCloseModal = () => {
setIsModalOpen(false);
setFolderType(null);
};
const handleSaveNewFolder = useCallback(
(folderData: FolderData) => {
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;
}
const roles = setDefaultFolderRoles(userPairingId);
const folderPrivateFields = FolderPrivateFields;
MessageBus.getInstance(iframeUrl)
.createFolder(folderData, folderPrivateFields, roles)
.then((_folderCreated: FolderCreated) => {
const firstStateId = _folderCreated.process.states[0].state_id;
MessageBus.getInstance(iframeUrl)
.notifyProcessUpdate(_folderCreated.processId, firstStateId)
.then(async () => {
// Recharger les processes et myProcesses
const messageBus = MessageBus.getInstance(iframeUrl);
const [processes, myProcesses] = await Promise.all([
messageBus.getProcesses(),
messageBus.getMyProcesses()
]);
setProcesses(processes);
setFolderProcesses(processes);
setMyProcesses(myProcesses);
setMyFolderProcesses(myProcesses);
showNotification("success", "Dossier créé avec succès !");
handleCloseModal();
});
})
.catch((error: any) => {
console.error('Erreur lors de la création du dossier:', error);
showNotification("error", "Erreur lors de la création du dossier");
});
},
[isConnected, userPairingId, loadFoldersFrom4NK]
);
// Auth connection handler
const handleAuthConnect = useCallback(() => {
setIsConnected(true);
setShowAuthModal(false);
@ -198,694 +303,6 @@ export default function FoldersPage() {
}, []);
// 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" ? (
<Cloud className="h-4 w-4 text-blue-600" />
) : (
<HardDrive className="h-4 w-4 text-gray-600" />
)
}
const getRoleIcon = (role: string) => {
switch (role) {
case "owner":
return <Crown className="h-4 w-4 text-yellow-600" />
case "editor":
return <FileText className="h-4 w-4 text-blue-600" />
case "validator":
return <Shield className="h-4 w-4 text-green-600" />
case "contributor":
return <UserPlus className="h-4 w-4 text-purple-600" />
case "viewer":
return <FileText className="h-4 w-4 text-gray-600" />
case "admin":
return <Shield className="h-4 w-4 text-red-600" />
case "manager":
return <Users className="h-4 w-4 text-orange-600" />
case "user":
return <User className="h-4 w-4 text-blue-600" />
case "guest":
return <User className="h-4 w-4 text-gray-400" />
default:
return <User className="h-4 w-4 text-gray-600" />
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "active":
return <Badge className="bg-green-100 text-green-800 border-green-200">Actif</Badge>
case "pending":
return <Badge className="bg-orange-100 text-orange-800 border-orange-200">En attente</Badge>
case "completed":
return <Badge className="bg-blue-100 text-blue-800 border-blue-200">Terminé</Badge>
case "archived":
return <Badge className="bg-gray-100 text-gray-800 border-gray-200">Archivé</Badge>
case "validated":
return <Badge className="bg-green-300 text-green-800 border-green-400">Validé</Badge>
default:
return <Badge className="bg-gray-100 text-gray-800 border-gray-200">Inconnu</Badge>
}
}
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 (
<div className="space-y-6">
{/* Notification */}