Restore complete 4NK integration workflow
This commit is contained in:
parent
6d65014a45
commit
8b6b62e643
@ -131,61 +131,166 @@ export default function FoldersPage() {
|
|||||||
|
|
||||||
// 4NK Integration useEffects
|
// 4NK Integration useEffects
|
||||||
useEffect(() => {
|
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) {
|
if (folderProcesses && myFolderProcesses.length >= 0) {
|
||||||
loadFoldersFrom4NK();
|
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
|
// Update folders when private data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (folderProcesses && Object.keys(folderPrivateData).length > 0) {
|
if (folderProcesses && Object.keys(folderPrivateData).length > 0) {
|
||||||
loadFoldersFrom4NK();
|
loadFoldersFrom4NK();
|
||||||
}
|
}
|
||||||
}, [folderPrivateData, folderProcesses, loadFoldersFrom4NK])
|
}, [folderPrivateData, loadFoldersFrom4NK, folderProcesses]);
|
||||||
|
|
||||||
// 4NK Authentication handlers
|
// Filter and sort folders
|
||||||
const handleLogin = useCallback(() => {
|
const filteredFolders = folders.filter(folder => {
|
||||||
setShowAuthModal(true);
|
const matchesSearch = folder.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
folder.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
}, []);
|
folder.folderNumber.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
|
||||||
const handleLogout = useCallback(() => {
|
|
||||||
UserStore.getInstance().disconnect();
|
|
||||||
setIsConnected(false);
|
|
||||||
setProcesses(null);
|
|
||||||
setMyProcesses([]);
|
|
||||||
setUserPairingId(null);
|
|
||||||
|
|
||||||
// Clear folder-related states
|
return matchesSearch
|
||||||
setFolderProcesses(null);
|
})
|
||||||
setMyFolderProcesses([]);
|
|
||||||
setFolderPrivateData({});
|
|
||||||
setFolders([]);
|
|
||||||
setLoadingFolders(false);
|
|
||||||
|
|
||||||
// Émettre un événement pour vider les messages locaux
|
const sortedFolders = [...filteredFolders].sort((a, b) => {
|
||||||
EventBus.getInstance().emit('CLEAR_CONSOLE');
|
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(() => {
|
const handleAuthConnect = useCallback(() => {
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
setShowAuthModal(false);
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Notification */}
|
{/* Notification */}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user