2025-10-23 16:43:10 +02:00

960 lines
39 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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<string[]>(["Racine"])
const [folderType, setFolderType] = useState<FolderType | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
// 4NK Integration states
const [isConnected, setIsConnected] = useState(false)
const [showAuthModal, setShowAuthModal] = useState(false)
const [processes, setProcesses] = useState<any>(null)
const [myProcesses, setMyProcesses] = useState<string[]>([])
const [userPairingId, setUserPairingId] = useState<string | null>(null)
const [folderProcesses, setFolderProcesses] = useState<any>(null)
const [myFolderProcesses, setMyFolderProcesses] = useState<string[]>([])
const [folderPrivateData, setFolderPrivateData] = useState<Record<string, Record<string, any>>>({})
const [loadingFolders, setLoadingFolders] = useState(false)
// Modal states
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
const [folders, setFolders] = useState<FolderData[]>([])
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 (
<div className="flex h-screen bg-gray-900">
{/* Sidebar */}
<div className="w-64 bg-gray-800 border-r border-gray-700 flex flex-col">
<div className="p-6 border-b border-gray-700">
<h1 className="text-xl font-semibold text-gray-100">Dossiers</h1>
<p className="text-sm text-gray-400 mt-1">Gérez vos dossiers 4NK</p>
</div>
)}
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-100">Dossiers</h1>
<p className="text-gray-400 mt-1">Organisez vos documents par dossiers</p>
</div>
<div className="flex items-center space-x-3 mt-4 sm:mt-0">
<Button variant="outline" size="sm">
<Upload className="h-4 w-4 mr-2 text-gray-100" />
Importer
</Button>
{isConnected ? (
<div className="flex gap-2">
{/* Nouveau dossier avec menu */}
<div className="relative">
<Button size="sm" onClick={() => setMenuOpen(!menuOpen)}>
<FolderPlus className="h-4 w-4 mr-2" />
Nouveau dossier
</Button>
{menuOpen && (
<div className="absolute mt-1 right-0 w-48 bg-white border border-gray-200 rounded shadow-lg z-50">
<button
className="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => handleOpenModal("general")}
>
Nouveau dossier
</button>
</div>
)}
</div>
{/* Déconnexion */}
<Button variant="outline" size="sm" onClick={handleLogout}>
<X className="h-4 w-4 mr-2" />
Déconnexion 4NK
</Button>
</div>
) : (
<Button variant="outline" size="sm" onClick={handleLogin}>
<Shield className="h-4 w-4 mr-2" />
Connexion 4NK
</Button>
)}
</div>
</div>
{/* Breadcrumb */}
<div className="flex items-center space-x-2 text-sm text-gray-400">
{currentPath.map((path, index) => (
<div key={index} className="flex items-center space-x-2">
{index > 0 && <ChevronRight className="h-4 w-4 text-gray-400" />}
<button
className={`hover:text-gray-100 ${index === currentPath.length - 1 ? "font-medium text-gray-100" : ""}`}
onClick={() => setCurrentPath(currentPath.slice(0, index + 1))}
>
{path}
</button>
</div>
))}
</div>
{/* Search and Filters */}
<Card className="bg-gray-900 border-gray-700 text-gray-100">
<CardContent className="p-4">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
<div className="flex flex-col sm:flex-row sm:items-center space-y-3 sm:space-y-0 sm:space-x-4">
<Button
variant="outline"
onClick={() => setShowFilters(!showFilters)}
className={showFilters ? "bg-blue-700 text-white border-blue-600" : ""}
>
<Filter className="h-4 w-4 mr-2 text-gray-100" />
Filtres
</Button>
</div>
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2">
<Label htmlFor="sort" className="text-sm text-gray-300">
Trier par:
</Label>
<Select value={sortBy} onValueChange={setSortBy}>
<SelectTrigger className="w-32 bg-gray-800 text-gray-100 border-gray-700">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
<SelectItem value="modified">Modifié</SelectItem>
<SelectItem value="name">Nom</SelectItem>
<SelectItem value="size">Taille</SelectItem>
<SelectItem value="owner">Propriétaire</SelectItem>
<SelectItem value="documents">Documents</SelectItem>
</SelectContent>
</Select>
<Button variant="ghost" size="sm" onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")}>
{sortOrder === "asc" ? <SortAsc className="h-4 w-4 text-gray-100" /> : <SortDesc className="h-4 w-4 text-gray-100" />}
</Button>
</div>
</div>
</div>
{/* Advanced Filters */}
{showFilters && (
<div className="mt-4 pt-4 border-t border-gray-700">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<Label htmlFor="filterAccess" className="text-sm font-medium text-gray-300">
Accès
</Label>
<Select value={filterAccess} onValueChange={setFilterAccess}>
<SelectTrigger className="bg-gray-800 text-gray-100 border-gray-700">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
<SelectItem value="all">Tous les accès</SelectItem>
<SelectItem value="shared">Partagés</SelectItem>
<SelectItem value="private">Privés</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="filterOwner" className="text-sm font-medium text-gray-300">
Propriétaire
</Label>
<Select value={filterOwner} onValueChange={setFilterOwner}>
<SelectTrigger className="bg-gray-800 text-gray-100 border-gray-700">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
<SelectItem value="all">Tous les propriétaires</SelectItem>
<SelectItem value="Marie Dubois">Marie Dubois</SelectItem>
<SelectItem value="Sophie Laurent">Sophie Laurent</SelectItem>
<SelectItem value="Jean Martin">Jean Martin</SelectItem>
<SelectItem value="Pierre Durand">Pierre Durand</SelectItem>
<SelectItem value="Admin Système">Admin Système</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="filterStorage" className="text-sm font-medium text-gray-300">
Type de stockage
</Label>
<Select value={filterStorage} onValueChange={setFilterStorage}>
<SelectTrigger className="bg-gray-800 text-gray-100 border-gray-700">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-800 text-gray-100 border-gray-700">
<SelectItem value="all">Tous les stockages</SelectItem>
<SelectItem value="temporary">
<div className="flex items-center space-x-2">
<HardDrive className="h-4 w-4 text-gray-100" />
<span>Temporaire</span>
</div>
</SelectItem>
<SelectItem value="permanent">
<div className="flex items-center space-x-2">
<Cloud className="h-4 w-4 text-gray-100" />
<span>Permanent</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<Button
variant="outline"
onClick={() => {
setFilterAccess("all")
setFilterOwner("all")
setFilterStorage("all")
setSearchTerm("")
}}
>
Réinitialiser
</Button>
</div>
</div>
</div>
)}
</CardContent>
</Card>
{/* Bulk Actions minimalistes: certificats et rôles uniquement */}
{selectedFolders.length > 0 && (
<Card className="bg-gray-800 border-gray-700 text-gray-100">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Checkbox
checked={selectedFolders.length === filteredFolders.length}
onCheckedChange={selectAllFolders}
/>
<span className="text-sm font-medium">
{selectedFolders.length} dossier{selectedFolders.length > 1 ? "s" : ""} sélectionné
{selectedFolders.length > 1 ? "s" : ""}
</span>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
>
<Download className="h-4 w-4 mr-2 text-gray-100" />
Télécharger
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
const selected = folders.filter((f) => selectedFolders.includes(f.id))
selected.forEach((folder) => {
setActionModal({ type: "certificate", folder, folders: [] })
})
}}
>
<CheckCircle className="h-4 w-4 mr-2 text-gray-100" />
Valider
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
showNotification("info", "Déplacement groupé de dossiers (mock)")
}}
>
<FolderOpen className="h-4 w-4 mr-2 text-gray-100" />
Déplacer
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
const selected = folders.filter((f) => selectedFolders.includes(f.id))
selected.forEach((folder) => {
setActionModal({ type: "storage_config", folder, folders: [] })
})
}}
>
<HardDrive className="h-4 w-4 mr-2 text-gray-100" />
Conservation
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
const selected = folders.filter((f) => selectedFolders.includes(f.id))
const withCerts = selected.filter((f) => f.documents && f.documents.some((d) => d.hasCertificate))
if (withCerts.length === 0) {
setNotification({ type: "info", message: "Aucun certificat à télécharger pour la sélection" })
return
}
withCerts.forEach((f) => handleViewDocumentsCertificates(f))
}}
>
<ShieldCheck className="h-4 w-4 mr-2 text-gray-100" />
Certificats
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
const first = folders.find((f) => selectedFolders.includes(f.id))
if (first) {
handleManageRoles(first)
} else {
setNotification({ type: "info", message: "Sélectionnez au moins un dossier" })
}
}}
>
<Users className="h-4 w-4 mr-2 text-gray-100" />
Rôles
</Button>
</div>
</div>
</CardContent>
</Card>
)}
{/* Folders List/Grid */}
<Card>
<CardContent className="p-0">
{viewMode === "list" ? (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-800">
<tr>
<th className="text-left py-3 px-4 w-8">
<Checkbox
checked={selectedFolders.length === filteredFolders.length}
onCheckedChange={selectAllFolders}
/>
</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Nom</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Taille</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Modifié</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Propriétaire</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Accès</th>
<th className="text-left py-3 px-4 font-medium text-gray-100">Statut</th>
</tr>
</thead>
<tbody>
{filteredFolders.map((folder) => (
<tr
key={folder.id}
className="border-b border-gray-700 hover:bg-gray-800"
>
<td className="py-3 px-4">
<Checkbox
checked={selectedFolders.includes(folder.id)}
onCheckedChange={() => toggleFolderSelection(folder.id)}
/>
</td>
<td className="py-3 px-4">
<div className="flex items-center space-x-3">
<div className={`p-2 rounded-lg ${getFolderColor(folder.color)}`}>
<Folder className="h-5 w-5 text-black" />
</div>
<div>
<div className="flex items-center space-x-2">
<span
className="font-medium text-gray-100 cursor-pointer hover:underline"
onClick={() => handleOpenFolder(folder)}
>
{folder.name}
</span>
{isNewFolder(folder) && (
<Badge className="bg-blue-700 text-blue-100 border-blue-600">NEW</Badge>
)}
{getStorageIcon(folder.storageType)}
{folder.access === "private" && (
<Lock className="h-4 w-4 text-gray-400" />
)}
</div>
<p className="text-sm text-gray-400 truncate max-w-xs">{folder.description}</p>
</div>
</div>
</td>
<td className="py-3 px-4 text-gray-400">{folder.size}</td>
<td className="py-3 px-4 text-gray-400">{formatDate(folder.modified)}</td>
<td className="py-3 px-4 text-gray-400">{folder.owner}</td>
<td className="py-3 px-4">
<Badge
variant="outline"
className={
folder.access === "shared"
? "bg-green-700 text-green-100 border-green-600"
: "bg-orange-700 text-orange-100 border-orange-600"
}
>
{folder.access === "shared" ? "Partagé" : "Privé"}
</Badge>
</td>
<td className="py-3 px-4">{getStatusBadge(folder.status)}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{filteredFolders.map((folder) => (
<div
key={folder.id}
className={`relative group border rounded-lg p-6 hover:shadow-md transition-shadow cursor-pointer ${selectedFolders.includes(folder.id)
? "bg-blue-900 border-blue-700"
: "bg-gray-800 border-gray-700"
}`}
onClick={() => handleOpenFolder(folder)}
>
<div className="absolute top-4 left-4" onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedFolders.includes(folder.id)}
onCheckedChange={() => toggleFolderSelection(folder.id)}
/>
</div>
<div
className="absolute top-4 right-4 flex items-center space-x-1"
onClick={(e) => e.stopPropagation()}
>
{isNewFolder(folder) && (
<Badge className="bg-blue-700 text-blue-100 border-blue-600">NEW</Badge>
)}
{folder.access === "private" && <Lock className="h-4 w-4 text-gray-400" />}
{folder.storageType === "temporary" && (
<Button
variant="ghost"
size="sm"
onClick={() => handleStorageConfig(folder)}
className="h-8 w-8 p-0"
title="Configurer le stockage temporaire"
>
<Timer className="h-4 w-4 text-gray-100" />
</Button>
)}
{folder.status === "validated" && (
<Button
variant="ghost"
size="sm"
onClick={() => handleDownloadCertificate(folder)}
className="h-8 w-8 p-0"
title="Télécharger le certificat blockchain"
>
<ShieldCheck className="h-4 w-4 text-green-400" />
</Button>
)}
{folder.documents && folder.documents.some((doc) => doc.hasCertificate) && (
<Button
variant="ghost"
size="sm"
onClick={() => handleViewDocumentsCertificates(folder)}
className="h-8 w-8 p-0"
title="Certificats des documents"
>
<FileCheck className="h-4 w-4 text-blue-400" />
</Button>
)}
<Button
variant="ghost"
size="sm"
onClick={() => handleManageRoles(folder)}
className="h-8 w-8 p-0"
title="Gérer les rôles"
>
<Users className="h-4 w-4 text-gray-100" />
</Button>
</div>
<div className="flex flex-col items-center space-y-4 mt-8">
<div className={`p-4 rounded-xl ${getFolderColor(folder.color)}`}>
<Folder className="h-12 w-12 text-gray-100" />
</div>
<div className="text-center space-y-2 w-full">
<h3
className="font-semibold text-gray-100 text-lg truncate"
title={folder.name}
>
{folder.name}
</h3>
<p className="text-sm text-gray-400 line-clamp-2">{folder.description}</p>
<div className="text-xs text-gray-400">
<p>{folder.size}</p>
<p>{formatDate(folder.modified)}</p>
<div className="flex items-center justify-center space-x-1 mt-1">
{getStorageIcon(folder.storageType)}
<span>{folder.storageType === "permanent" ? "Permanent" : "Temporaire"}</span>
</div>
{folder.temporaryStorageConfig && folder.storageType === "temporary" && (
<div className="text-xs text-blue-400 mt-1">
Durée: {folder.temporaryStorageConfig.duration} jours
</div>
)}
</div>
<div className="flex justify-center">{getStatusBadge(folder.status)}</div>
<Badge
variant="outline"
className={
folder.access === "shared"
? "bg-green-700 text-green-100 border-green-600"
: "bg-orange-700 text-orange-100 border-orange-600"
}
>
{folder.access === "shared" ? "Partagé" : "Privé"}
</Badge>
</div>
</div>
{/* Recent Activity */}
<div className="mt-4 pt-4 border-t border-gray-700">
<h4 className="text-xs font-medium text-gray-300 mb-2">Activité récente</h4>
<div className="space-y-1">
{folder.activity.slice(0, 2).map((activity, index) => (
<div key={index} className="text-xs text-gray-400">
<span className="font-medium">{activity.user}</span> a {activity.action}{" "}
<span className="font-medium">{activity.item}</span>
<div className="text-gray-500">{activity.time}</div>
</div>
))}
</div>
</div>
</div>
))}
</div>
</div>
)}
{loadingFolders && isConnected && (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<h3 className="text-lg font-medium text-gray-100 mb-2">Chargement des dossiers...</h3>
<p className="text-gray-400">Récupération des données privées depuis 4NK</p>
</div>
)}
{!loadingFolders && filteredFolders.length === 0 && (
<div className="text-center py-12">
<Folder className="h-12 w-12 mx-auto text-gray-500 mb-4" />
<h3 className="text-lg font-medium text-gray-100 mb-2">Aucun dossier trouvé</h3>
<p className="text-gray-400 mb-4">
{searchTerm || filterAccess !== "all" || filterOwner !== "all" || filterStorage !== "all"
? "Essayez de modifier vos critères de recherche"
: "Commencez par créer votre premier dossier"}
</p>
</div>
)}
</CardContent>
</Card>
{/* ProcessesViewer Card */}
<Card className="mt-6 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
<CardContent className="p-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Processus Blockchain</h3>
{/* Intégration du ProcessesViewer */}
<div className="w-full h-[500px]">
<ProcessesViewer
processes={processes}
myProcesses={myProcesses}
onProcessesUpdate={setProcesses}
/>
</div>
</CardContent>
</Card>
{/* Modals */}
{actionModal.type && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-gray-900 text-gray-100 rounded-lg p-6 w-full max-w-4xl mx-4 max-h-[90vh] overflow-y-auto">
{/* Documents Certificates Modal */}
{actionModal.type === "documents_certificates" && actionModal.folder && (
<>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-100">
Certificats des documents - {actionModal.folder.name}
</h3>
<Button
variant="ghost"
size="sm"
onClick={() => setActionModal({ type: null, folder: null, folders: [] })}
>
<X className="h-4 w-4 text-gray-100" />
</Button>
)}
</div>
</div>
) : (
<div className="space-y-4">
{/* Folder list */}
<div className="space-y-2">
{sortedFolders.map((folder) => (
<Card key={folder.folderNumber} className="hover:shadow-md transition-shadow bg-gray-800 border border-gray-700">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Folder className="h-5 w-5 text-blue-600" />
<div>
<h3 className="font-medium text-gray-100">{folder.name}</h3>
<p className="text-sm text-gray-400">{folder.description}</p>
<div className="flex items-center space-x-4 mt-1">
<span className="text-xs text-gray-400">#{folder.folderNumber}</span>
<span className="text-xs text-gray-400">
<Clock className="h-3 w-3 inline mr-1" />
{new Date(folder.updated_at).toLocaleDateString()}
</span>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
{folder.notes.length > 0 && (
<Badge variant="outline">{folder.notes.length} notes</Badge>
)}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)}
</div>
</div>
{/* Modals */}
{isModalOpen && (
<FolderModal
isOpen={isModalOpen}
onClose={handleCloseModal}
onSave={handleSaveNewFolder}
onCancel={handleCloseModal}
folderType={folderType || "autre"}
/>
)}
{showAuthModal && (
<AuthModal
isOpen={showAuthModal}
onClose={handleAuthClose}
onConnect={handleAuthConnect}
iframeUrl={iframeUrl}
/>
)}
{/* Notification */}
{notification && (
<div className="fixed top-4 right-4 z-50">
<div className={`p-4 rounded-md shadow-lg ${
notification.type === "success" ? "bg-green-50 text-green-800 border border-green-200" :
notification.type === "error" ? "bg-red-50 text-red-800 border border-red-200" :
"bg-blue-50 text-blue-800 border border-blue-200"
}`}>
<div className="flex items-center justify-between">
<span>{notification.message}</span>
<Button
variant="ghost"
size="sm"
onClick={() => setNotification(null)}
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</div>
)}
{/* 4NK Iframe - only show when connected */}
{isConnected && <Iframe iframeUrl={iframeUrl} />}
</div>
)
}