"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(() => { 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(); } }, [folderProcesses, myFolderProcesses, loadFoldersFrom4NK]); // Update folders when private data changes useEffect(() => { if (folderProcesses && Object.keys(folderPrivateData).length > 0) { loadFoldersFrom4NK(); } }, [folderPrivateData, loadFoldersFrom4NK, folderProcesses]); // 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()) return matchesSearch }) 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 } }) // 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); 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); }, []); return (
{/* Sidebar */}

Dossiers

Gérez vos dossiers 4NK

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