2025-10-21 11:42:22 +02:00

2280 lines
95 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 { useRouter, useSearchParams } from "next/navigation"
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 { Label } from "@/components/ui/label"
import { Checkbox } from "@/components/ui/checkbox"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Folder,
FolderOpen,
Grid3X3,
List,
Search,
Filter,
Share2,
Trash2,
Users,
FileText,
Clock,
Star,
ChevronRight,
FolderPlus,
Upload,
Download,
Lock,
SortAsc,
SortDesc,
X,
UserPlus,
Crown,
Shield,
User,
CheckCircle,
XCircle,
Info,
CloudUpload,
Cloud,
HardDrive,
FileQuestion,
Timer,
ShieldCheck,
Archive,
FileCheck,
} from "lucide-react"
import ProcessesViewer from "@/components/4nk/ProcessesViewer"
import { FolderData as SDKFolderData, 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 EventBus from "@/lib/4nk/EventBus"
import FolderModal from "@/components/4nk/FolderModal"
import AuthModal from "@/components/4nk/AuthModal"
import Iframe from "@/components/4nk/Iframe"
interface FolderData {
id: number
name: string
description: string
documentsCount: number
subfoldersCount: number
size: string
created: Date
modified: Date
owner: string
access: "shared" | "private"
members: string[]
tags: string[]
color: string
favorite: boolean
storageType: "temporary" | "permanent"
status: "active" | "archived" | "pending" | "completed" | "validated"
type: string
expectedDocuments: Array<{
name: string
required: boolean
assignedRole: "owner" | "editor" | "validator" | "contributor"
status: "missing" | "pending" | "received"
}>
activity: Array<{
user: string
action: string
item: string
time: string
}>
permissions: {
canView: boolean
canEdit: boolean
canDelete: boolean
canInvite: boolean
canArchive: boolean
canAnalyze: boolean
}
temporaryStorageConfig?: {
duration: number // en jours
dataUsage: string
thirdPartyAccess: string
}
documents?: Array<{
id: string
name: string
hasCertificate: boolean
certificateId?: string
}>
}
interface ActionModal {
type:
| "invite"
| "delete"
| "create"
| "edit"
| "archive"
| "request_document"
| "storage_config"
| "certificate"
| "documents_certificates"
| null
folder: FolderData | null
folders: FolderData[]
}
interface UserWithRoles {
id: string
name: string
email: string
avatar: string
folderRoles: {
[folderId: string]: {
role: "owner" | "editor" | "viewer" | "validator" | "contributor"
assignedDate: Date
}
}
spaceRole: "admin" | "manager" | "user" | "guest"
spaceRoles: {
[spaceId: string]: {
role: "admin" | "manager" | "user" | "guest"
spaceName: string
}
}
}
interface Role {
id: string
name: string
description: string
level: "folder" | "space" | "global"
}
type FolderType = string;
export default function FoldersPage() {
const router = useRouter()
const searchParams = useSearchParams()
const [viewMode, setViewMode] = useState<'list'>('list')
const [searchTerm, setSearchTerm] = useState("")
const [selectedFolders, setSelectedFolders] = useState<number[]>([])
const [sortBy, setSortBy] = useState("modified")
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc")
const [filterAccess, setFilterAccess] = useState("all")
const [filterOwner, setFilterOwner] = useState("all")
const [filterStorage, setFilterStorage] = useState("all")
const [showFilters, setShowFilters] = useState(false)
const [currentPath, setCurrentPath] = useState<string[]>(["Racine"])
const [actionModal, setActionModal] = useState<ActionModal>({ type: null, folder: null, folders: [] })
const [showCreateFolderModal, setShowCreateFolderModal] = useState(false)
const [folderType, setFolderType] = useState<FolderType | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [menuOpen, setMenuOpen] = 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)
// Modal states
const [inviteMessage, setInviteMessage] = useState("")
const [selectedUser, setSelectedUser] = useState("")
const [selectedRole, setSelectedRole] = useState("")
const [inviteScope, setInviteScope] = useState<"user" | "role">("user")
const [folderName, setFolderName] = useState("")
const [folderDescription, setFolderDescription] = useState("")
const [folderColor, setFolderColor] = useState("blue")
const [folderTags, setFolderTags] = useState("")
const [folderAccess, setFolderAccess] = useState<"shared" | "private">("private")
const [archiveReason, setArchiveReason] = useState("")
const [retentionPeriod, setRetentionPeriod] = useState("5")
const [selectedDocument, setSelectedDocument] = useState("")
const [requestMessage, setRequestMessage] = useState("")
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
// Storage config modal states
const [storageDuration, setStorageDuration] = useState("30")
const [dataUsage, setDataUsage] = useState("")
const [thirdPartyAccess, setThirdPartyAccess] = useState("")
const [folders, setFolders] = useState<FolderData[]>([])
const [stats, setStats] = useState({
total: 0,
shared: 0,
private: 0,
thisWeek: 0,
permanent: 0,
temporary: 0,
})
const [users] = useState<UserWithRoles[]>([
{
id: "1",
name: "Marie Dubois",
email: "marie.dubois@company.com",
avatar: "MD",
folderRoles: {
"1": { role: "owner", assignedDate: new Date("2024-01-01") },
"4": { role: "editor", assignedDate: new Date("2024-01-05") },
},
spaceRole: "manager",
spaceRoles: {
main: { role: "manager", spaceName: "Espace Principal" },
legal: { role: "admin", spaceName: "Espace Juridique" },
},
},
{
id: "2",
name: "Sophie Laurent",
email: "sophie.laurent@company.com",
avatar: "SL",
folderRoles: {
"2": { role: "owner", assignedDate: new Date("2024-01-02") },
"3": { role: "contributor", assignedDate: new Date("2024-01-10") },
},
spaceRole: "user",
spaceRoles: {
main: { role: "user", spaceName: "Espace Principal" },
analytics: { role: "manager", spaceName: "Espace Analytics" },
},
},
{
id: "3",
name: "Jean Martin",
email: "jean.martin@company.com",
avatar: "JM",
folderRoles: {
"3": { role: "owner", assignedDate: new Date("2024-01-03") },
"2": { role: "viewer", assignedDate: new Date("2024-01-15") },
},
spaceRole: "user",
spaceRoles: {
main: { role: "user", spaceName: "Espace Principal" },
projects: { role: "admin", spaceName: "Espace Projets" },
},
},
{
id: "4",
name: "Pierre Durand",
email: "pierre.durand@company.com",
avatar: "PD",
folderRoles: {
"6": { role: "owner", assignedDate: new Date("2024-01-04") },
"5": { role: "validator", assignedDate: new Date("2024-01-08") },
},
spaceRole: "user",
spaceRoles: {
main: { role: "user", spaceName: "Espace Principal" },
training: { role: "admin", spaceName: "Espace Formation" },
},
},
{
id: "5",
name: "Admin Système",
email: "admin@company.com",
avatar: "AD",
folderRoles: {
"5": { role: "owner", assignedDate: new Date("2024-01-01") },
"1": { role: "validator", assignedDate: new Date("2024-01-01") },
"4": { role: "validator", assignedDate: new Date("2024-01-01") },
},
spaceRole: "admin",
spaceRoles: {
main: { role: "admin", spaceName: "Espace Principal" },
legal: { role: "admin", spaceName: "Espace Juridique" },
analytics: { role: "admin", spaceName: "Espace Analytics" },
projects: { role: "admin", spaceName: "Espace Projets" },
training: { role: "admin", spaceName: "Espace Formation" },
},
},
])
const [roles] = useState<Role[]>([
{
id: "folder-owner",
name: "Propriétaire du dossier",
description: "Contrôle total sur le dossier",
level: "folder",
},
{ id: "folder-editor", name: "Éditeur du dossier", description: "Peut modifier les documents", level: "folder" },
{
id: "folder-validator",
name: "Validateur du dossier",
description: "Peut valider les documents",
level: "folder",
},
{
id: "folder-contributor",
name: "Contributeur du dossier",
description: "Peut ajouter des documents",
level: "folder",
},
{ id: "folder-viewer", name: "Lecteur du dossier", description: "Lecture seule", level: "folder" },
{ id: "space-admin", name: "Administrateur d'espace", description: "Contrôle total sur l'espace", level: "space" },
{
id: "space-manager",
name: "Gestionnaire d'espace",
description: "Gestion des utilisateurs et dossiers",
level: "space",
},
{ id: "space-user", name: "Utilisateur d'espace", description: "Accès standard à l'espace", level: "space" },
{ id: "space-guest", name: "Invité d'espace", description: "Accès limité à l'espace", level: "space" },
{ id: "global-admin", name: "Administrateur global", description: "Accès à tous les espaces", level: "global" },
{ id: "global-manager", name: "Gestionnaire global", description: "Gestion multi-espaces", level: "global" },
])
const colors = [
{ id: "blue", name: "Bleu", class: "text-blue-600 bg-blue-100" },
{ id: "green", name: "Vert", class: "text-green-600 bg-green-100" },
{ id: "purple", name: "Violet", class: "text-purple-600 bg-purple-100" },
{ id: "orange", name: "Orange", class: "text-orange-600 bg-orange-100" },
{ id: "red", name: "Rouge", class: "text-red-600 bg-red-100" },
{ id: "pink", name: "Rose", class: "text-pink-600 bg-pink-100" },
{ id: "yellow", name: "Jaune", class: "text-yellow-600 bg-yellow-100" },
{ id: "gray", name: "Gris", class: "text-gray-600 bg-gray-100" },
]
// 4NK Integration useEffects
useEffect(() => {
const userStore = UserStore.getInstance();
const connected = userStore.isConnected();
const pairingId = userStore.getUserPairingId();
console.log('Initialisation 4NK:', { connected, pairingId });
setIsConnected(connected);
setUserPairingId(pairingId);
}, []);
useEffect(() => {
const handleConnectionFlow = async () => {
if (!isConnected) return;
const userStore = UserStore.getInstance();
const messageBus = MessageBus.getInstance(iframeUrl);
try {
await messageBus.isReady();
let pairingId = userStore.getUserPairingId();
// 1⃣ Créer le pairing si non existant
if (!pairingId) {
console.log("🚀 No pairing found — creating new pairing...");
pairingId = await messageBus.createUserPairing();
console.log("✅ Pairing created:", 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);
// 3⃣ Charger les myProcesses
const myProcesses = await messageBus.getMyProcesses();
setMyProcesses(myProcesses);
} catch (err) {
console.error("❌ Error during pairing or process loading:", err);
}
};
handleConnectionFlow();
}, [isConnected, iframeUrl]);
useEffect(() => {
// Simuler le chargement des dossiers
const loadFolders = () => {
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,
})
}
loadFolders()
}, [])
// Notification system
const showNotification = (type: "success" | "error" | "info", message: string) => {
setNotification({ type, message })
setTimeout(() => setNotification(null), 3000)
}
// 4NK Authentication handlers
const handleLogin = useCallback(() => {
setShowAuthModal(true);
}, []);
const handleLogout = useCallback(() => {
UserStore.getInstance().disconnect();
setIsConnected(false);
setProcesses(null);
setMyProcesses([]);
setUserPairingId(null);
// Émettre un événement pour vider les messages locaux
EventBus.getInstance().emit('CLEAR_CONSOLE');
showNotification("info", "Déconnexion réussie");
}, []);
const handleAuthConnect = useCallback(() => {
setIsConnected(true);
setShowAuthModal(false);
console.log('Auth Connect - Connexion établie, le useEffect se chargera de récupérer le userPairingId');
showNotification("success", "Connexion 4NK réussie");
}, []);
const handleAuthClose = useCallback(() => {
setShowAuthModal(false);
}, []);
// Fonction pour envoyer une notification dans le chat du dossier
const sendFolderChatNotification = (folderId: string, message: string, actionType: string) => {
const folderUsers = users.filter((user) => user.folderRoles[folderId])
console.log("Notification envoyée dans le chat du dossier:", {
folderId,
recipients: folderUsers.map((u) => ({ name: u.name, role: u.folderRoles[folderId]?.role })),
message,
actionType,
timestamp: new Date().toISOString(),
})
folderUsers.forEach((user) => {
console.log(`📱 Notification push envoyée à ${user.name} (${user.email})`)
})
}
// Fonction pour organiser les utilisateurs par rôles
const organizeUsersForInvitation = (currentFolderId: string) => {
const organized = {
folderRoles: {} as { [role: string]: UserWithRoles[] },
spaceRoles: {} as { [role: string]: UserWithRoles[] },
otherSpaces: {} as { [spaceName: string]: UserWithRoles[] },
}
users.forEach((user) => {
if (user.folderRoles[currentFolderId]) {
const role = user.folderRoles[currentFolderId].role
if (!organized.folderRoles[role]) organized.folderRoles[role] = []
organized.folderRoles[role].push(user)
}
const spaceRole = user.spaceRole
if (!organized.spaceRoles[spaceRole]) organized.spaceRoles[spaceRole] = []
organized.spaceRoles[spaceRole].push(user)
Object.values(user.spaceRoles).forEach((spaceInfo) => {
if (spaceInfo.spaceName !== "Espace Principal") {
if (!organized.otherSpaces[spaceInfo.spaceName]) organized.otherSpaces[spaceInfo.spaceName] = []
organized.otherSpaces[spaceInfo.spaceName].push(user)
}
})
})
return organized
}
// Folder actions
const handleOpenFolder = (folder: FolderData) => {
// Rediriger vers la page documents avec le filtre du dossier
router.push(`/dashboard/documents?folder=${encodeURIComponent(folder.name)}`)
}
const handleInviteFolder = (folder: FolderData) => {
setInviteMessage("")
setSelectedUser("")
setSelectedRole("")
setInviteScope("user")
setActionModal({ type: "invite", folder, folders: [] })
}
const handleArchiveFolder = (folder: FolderData) => {
setArchiveReason("")
setRetentionPeriod("5")
setActionModal({ type: "archive", folder, folders: [] })
}
const handleStorageConfig = (folder: FolderData) => {
setStorageDuration(folder.temporaryStorageConfig?.duration.toString() || "30")
setDataUsage(folder.temporaryStorageConfig?.dataUsage || "")
setThirdPartyAccess(folder.temporaryStorageConfig?.thirdPartyAccess || "")
setActionModal({ type: "storage_config", folder, folders: [] })
}
const handleAIAnalysis = (folder: FolderData) => {
showNotification("info", `Analyse IA en cours pour ${folder.name}...`)
// Simuler une analyse IA
setTimeout(
() => {
const analysisResults = [
`📊 **Analyse du dossier "${folder.name}"**\n\n` +
`**Contenu :** ${folder.documentsCount} documents analysés (${folder.size})\n` +
`**Thématiques principales :** ${folder.tags.join(", ")}\n` +
`**Niveau d'activité :** ${folder.activity.length > 2 ? "Élevé" : "Modéré"} (dernière modification ${formatDate(folder.modified)})\n\n` +
`**Recommandations :**\n` +
`${folder.storageType === "temporary" ? "Considérer l'archivage vers le stockage permanent" : "Dossier déjà archivé de manière optimale"}\n` +
`${folder.access === "private" ? "Évaluer les possibilités de partage avec l'équipe" : "Partage actuel avec " + folder.members.length + " membre(s)"}\n` +
`• Dernière activité significative détectée il y a ${Math.floor(Math.random() * 7) + 1} jour(s)\n\n` +
`**Score de pertinence :** ${Math.floor(Math.random() * 30) + 70}/100`,
`🔍 **Analyse approfondie du dossier "${folder.name}"**\n\n` +
`**Structure documentaire :**\n` +
`${Math.floor(folder.documentsCount * 0.4)} documents principaux\n` +
`${Math.floor(folder.documentsCount * 0.3)} documents de support\n` +
`${Math.floor(folder.documentsCount * 0.3)} documents annexes\n\n` +
`**Analyse temporelle :**\n` +
`• Création : ${folder.created.toLocaleDateString("fr-FR")}\n` +
`• Pic d'activité détecté en ${new Date().toLocaleDateString("fr-FR", { month: "long", year: "numeric" })}\n` +
`• Tendance : ${Math.random() > 0.5 ? "Croissante" : "Stable"}\n\n` +
`**Recommandations stratégiques :**\n` +
`${folder.documentsCount > 50 ? "Envisager une réorganisation en sous-dossiers" : "Structure actuelle optimale"}\n` +
`${folder.members.length < 3 ? "Potentiel de collaboration à explorer" : "Équipe collaborative active"}\n` +
`• Prochaine révision recommandée : ${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString("fr-FR")}`,
`🎯 **Insights IA pour "${folder.name}"**\n\n` +
`**Analyse sémantique :**\n` +
`• Cohérence thématique : ${Math.floor(Math.random() * 20) + 80}%\n` +
`• Mots-clés dominants : ${folder.tags.slice(0, 3).join(", ")}\n` +
`• Complexité moyenne : ${["Faible", "Modérée", "Élevée"][Math.floor(Math.random() * 3)]}\n\n` +
`**Patterns détectés :**\n` +
`${Math.random() > 0.5 ? "Cycle de révision régulier identifié" : "Activité sporadique détectée"}\n` +
`${Math.random() > 0.5 ? "Collaboration inter-équipes active" : "Usage principalement individuel"}\n` +
`${folder.storageType === "permanent" ? "Archivage conforme aux bonnes pratiques" : "Optimisation de stockage possible"}\n\n` +
`**Actions suggérées :**\n` +
`${Math.random() > 0.5 ? "Créer un template basé sur ce dossier" : "Standardiser la nomenclature"}\n` +
`${Math.random() > 0.5 ? "Planifier une session de nettoyage" : "Maintenir la structure actuelle"}\n` +
`• Prochaine analyse automatique : ${new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toLocaleDateString("fr-FR")}`,
]
const randomAnalysis = analysisResults[Math.floor(Math.random() * analysisResults.length)]
// Envoyer l'analyse dans le chat du dossier
sendFolderChatNotification(folder.id.toString(), `🤖 ${randomAnalysis}`, "ai_analysis")
showNotification("success", `Analyse IA terminée pour ${folder.name}. Redirection vers le chat...`)
// Rediriger vers le chat après 1.5 secondes
setTimeout(() => {
router.push("/dashboard/chat")
}, 1500)
},
2000 + Math.random() * 3000,
)
}
const handleViewCertificate = (folder: FolderData) => {
setActionModal({ type: "certificate", folder, folders: [] })
}
const handleViewDocumentsCertificates = (folder: FolderData) => {
setActionModal({ type: "documents_certificates", folder, folders: [] })
}
const handleDownloadCertificate = (folder: FolderData) => {
if (folder.status === "validated") {
showNotification("info", `Téléchargement du certificat blockchain pour le dossier ${folder.name}...`)
sendFolderChatNotification(
folder.id.toString(),
`🔗 Certificat blockchain du dossier téléchargé`,
"folder_blockchain_certificate_download",
)
setTimeout(() => {
showNotification("success", `Certificat blockchain du dossier ${folder.name} téléchargé avec succès`)
}, 2000)
}
}
const handleManageRoles = (folder: FolderData) => {
// Rediriger vers la gestion des rôles du dossier
router.push(`/dashboard/folders/${folder.id}/roles`)
}
const handleRequestDocument = (folder: FolderData) => {
setSelectedDocument("")
setRequestMessage("")
setActionModal({ type: "request_document", folder, folders: [] })
}
const handleDeleteFolder = (folder: FolderData) => {
setActionModal({ type: "delete", folder, folders: [] })
}
const handleOpenModal = (type: FolderType) => {
setFolderType(type);
setIsModalOpen(true);
setMenuOpen(false);
};
const handleCloseModal = () => {
setIsModalOpen(false);
setFolderType(null);
};
const handleSaveNewFolder = useCallback(
(folderData: SDKFolderData) => {
if (!isConnected || !userPairingId) {
console.error('Conditions non remplies:', { isConnected, userPairingId });
showNotification(
"error",
`Vous devez être connecté à 4NK pour créer un dossier (Connected: ${isConnected}, PairingId: ${userPairingId ? 'OK' : 'NULL'})`
);
return;
}
// Ajout du type dans les données du dossier
const folderToCreate = {
...folderData,
type: folderType
};
const roles = setDefaultFolderRoles(userPairingId, [], []);
const folderPrivateFields = FolderPrivateFields;
MessageBus.getInstance(iframeUrl)
.createFolder(folderToCreate, folderPrivateFields, roles)
.then((_folderCreated: FolderCreated) => {
const firstStateId = _folderCreated.process.states[0].state_id;
MessageBus.getInstance(iframeUrl)
.notifyProcessUpdate(_folderCreated.processId, firstStateId)
.then(() =>
MessageBus.getInstance(iframeUrl)
.validateState(_folderCreated.processId, firstStateId)
.then(() =>
MessageBus.getInstance(iframeUrl)
.getProcesses()
.then(async (processes: any) => {
setProcesses(processes)
})
)
);
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 */}
{notification && (
<div
className={`fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg flex items-center space-x-2 ${notification.type === "success"
? "bg-green-100 text-green-800 border border-green-200"
: notification.type === "error"
? "bg-red-100 text-red-800 border border-red-200"
: "bg-blue-100 text-blue-800 border border-blue-200"
}`}
>
{notification.type === "success" && <CheckCircle className="h-5 w-5" />}
{notification.type === "error" && <XCircle className="h-5 w-5" />}
{notification.type === "info" && <Info className="h-5 w-5" />}
<span>{notification.message}</span>
<Button variant="ghost" size="sm" onClick={() => setNotification(null)}>
<X className="h-4 w-4" />
</Button>
</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>
)}
{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 className="space-y-6">
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div className="flex items-center space-x-3">
<FileCheck className="h-8 w-8 text-blue-400" />
<div>
<h4 className="font-semibold text-blue-200">Certificats des documents</h4>
<p className="text-sm text-blue-300">
Téléchargez les certificats blockchain individuels des documents de ce dossier
</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-4">
{actionModal.folder.documents?.map((doc) => (
<div key={doc.id} className="border border-gray-700 rounded-lg p-4 bg-gray-900">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<FileText className="h-8 w-8 text-gray-300" />
<div>
<h5 className="font-medium text-gray-100">{doc.name}</h5>
{doc.hasCertificate && doc.certificateId && (
<p className="text-sm text-gray-400">ID: {doc.certificateId}</p>
)}
</div>
</div>
<div className="flex items-center space-x-2">
{doc.hasCertificate ? (
<>
<Badge className="bg-green-800 text-green-100">Certifié</Badge>
<Button
variant="outline"
size="sm"
onClick={() => {
showNotification("info", `Téléchargement du certificat pour ${doc.name}...`)
setTimeout(() => {
showNotification("success", `Certificat de ${doc.name} téléchargé`)
sendFolderChatNotification(
actionModal.folder!.id.toString(),
`📜 Certificat du document "${doc.name}" téléchargé`,
"document_certificate_download",
)
}, 1500)
}}
>
<Download className="h-4 w-4 mr-2" />
Télécharger
</Button>
</>
) : (
<Badge variant="outline" className="bg-gray-700 text-gray-300">
Non certifié
</Badge>
)}
</div>
</div>
</div>
))}
</div>
<div className="bg-gray-800 p-4 rounded-lg border border-gray-700">
<h5 className="font-medium text-gray-100 mb-3">Actions groupées</h5>
<div className="flex space-x-3">
<Button
variant="outline"
onClick={() => {
const certifiedDocs = actionModal.folder!.documents?.filter((doc) => doc.hasCertificate) || []
showNotification("info", `Téléchargement de ${certifiedDocs.length} certificat(s)...`)
setTimeout(() => {
showNotification("success", `${certifiedDocs.length} certificat(s) téléchargé(s)`)
sendFolderChatNotification(
actionModal.folder!.id.toString(),
`📦 Archive des certificats téléchargée (${certifiedDocs.length} documents)`,
"bulk_certificates_download",
)
}, 2000)
}}
>
<Archive className="h-4 w-4 mr-2" />
Télécharger tous les certificats (.zip)
</Button>
<Button
variant="outline"
onClick={() => {
showNotification("info", "Vérification en ligne des certificats...")
setTimeout(() => {
showNotification("success", "Tous les certificats sont valides")
}, 3000)
}}
>
<ShieldCheck className="h-4 w-4 mr-2" />
Vérifier tous en ligne
</Button>
</div>
</div>
</div>
</>
)}
{/* Storage Config Modal */}
{actionModal.type === "storage_config" && actionModal.folder && (
<>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-100">
Configuration du stockage temporaire
</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 className="space-y-4">
<div className="bg-gray-800 p-3 rounded-lg border border-gray-700">
<div className="flex items-center space-x-2 mb-2">
<Timer className="h-5 w-5 text-orange-400" />
<span className="font-medium text-orange-300">
Configuration du stockage temporaire
</span>
</div>
<p className="text-sm text-orange-200">
Configurez la durée de conservation et les informations d'usage pour le dossier{" "}
<strong>{actionModal.folder.name}</strong> en stockage temporaire.
</p>
</div>
<div>
<Label htmlFor="storageDuration" className="text-gray-100">Durée de conservation (en jours)</Label>
<Select value={storageDuration} onValueChange={setStorageDuration}>
<SelectTrigger className="bg-gray-900 text-gray-100 border-gray-700">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-900 text-gray-100 border-gray-700">
<SelectItem value="7">7 jours</SelectItem>
<SelectItem value="15">15 jours</SelectItem>
<SelectItem value="30">30 jours</SelectItem>
<SelectItem value="60">60 jours</SelectItem>
<SelectItem value="90">90 jours</SelectItem>
<SelectItem value="180">180 jours</SelectItem>
<SelectItem value="365">1 an</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-400 mt-1">
Durée pendant laquelle les données seront conservées en stockage temporaire avant archivage automatique
</p>
</div>
<div>
<Label htmlFor="dataUsage" className="text-gray-100">Usage de la donnée</Label>
<Textarea
id="dataUsage"
value={dataUsage}
onChange={(e) => setDataUsage(e.target.value)}
placeholder="Décrivez l'usage prévu de ces données (ex: analyses commerciales, rapports internes, documentation projet...)"
rows={3}
className="bg-gray-900 text-gray-100 border-gray-700"
/>
<p className="text-xs text-gray-400 mt-1">
Description de l'utilisation prévue des données contenues dans ce dossier
</p>
</div>
<div>
<Label htmlFor="thirdPartyAccess" className="text-gray-100">Tiers pouvant avoir accès</Label>
<Textarea
id="thirdPartyAccess"
value={thirdPartyAccess}
onChange={(e) => setThirdPartyAccess(e.target.value)}
placeholder="Listez les tiers externes qui pourraient avoir accès à ces données (ex: consultants, partenaires, auditeurs...)"
rows={3}
className="bg-gray-900 text-gray-100 border-gray-700"
/>
<p className="text-xs text-gray-400 mt-1">
Liste des parties externes qui pourraient être amenées à consulter ces données
</p>
</div>
<div className="bg-gray-800 p-3 rounded-lg border border-gray-700">
<div className="flex items-center space-x-2 mb-2">
<Info className="h-4 w-4 text-blue-400" />
<span className="font-medium text-blue-200">Information RGPD</span>
</div>
<p className="text-xs text-blue-300">
Ces informations sont utilisées pour assurer la conformité RGPD et la traçabilité des données.
Elles seront incluses dans le registre des traitements.
</p>
</div>
<div className="flex justify-end space-x-2">
<Button
variant="outline"
onClick={() => setActionModal({ type: null, folder: null, folders: [] })}
>
Annuler
</Button>
<Button onClick={confirmStorageConfig}>
<Timer className="h-4 w-4 mr-2" />
Enregistrer la configuration
</Button>
</div>
</div>
</>
)}
{/* Certificate Modal */}
{actionModal.type === "certificate" && actionModal.folder && (
<>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-100">
Certificat de validation du dossier
</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 className="space-y-6">
<div className="bg-gray-800 border border-green-700 rounded-lg p-4">
<div className="flex items-center space-x-3">
<ShieldCheck className="h-8 w-8 text-green-400" />
<div>
<h4 className="font-semibold text-green-300">Dossier certifié</h4>
<p className="text-sm text-green-200">
Ce dossier et tous ses documents ont été validés et certifiés numériquement
</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-gray-900 p-4 rounded-lg border border-gray-700">
<h5 className="font-medium text-gray-100 mb-3">Informations du dossier</h5>
<div className="space-y-2 text-sm text-gray-300">
<div className="flex justify-between">
<span className="text-gray-400">Nom :</span>
<span className="font-medium">{actionModal.folder.name}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Documents :</span>
<span className="font-medium">{actionModal.folder.documentsCount}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Taille totale :</span>
<span className="font-medium">{actionModal.folder.size}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Type :</span>
<span className="font-medium capitalize">{actionModal.folder.type}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Hash du dossier :</span>
<span className="font-mono text-xs bg-gray-800 p-1 rounded break-all text-gray-200">
{Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15)}
</span>
</div>
</div>
</div>
<div className="bg-gray-900 p-4 rounded-lg border border-gray-700">
<h5 className="font-medium text-gray-100 mb-3">Certificat numérique</h5>
<div className="space-y-2 text-sm text-gray-300">
<div className="flex justify-between">
<span className="text-gray-400">Émis le :</span>
<span className="font-medium">{actionModal.folder.modified.toLocaleDateString("fr-FR")}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Validé par :</span>
<span className="font-medium">{actionModal.folder.owner}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Autorité :</span>
<span className="font-medium">DocV Folder Certification</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">ID Certificat :</span>
<span className="font-mono text-xs bg-gray-800 p-1 rounded text-gray-200">
FOLDER-CERT-{actionModal.folder.id}-{new Date().getFullYear()}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Validité :</span>
<span className="font-medium text-green-400">
{new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toLocaleDateString("fr-FR")}
</span>
</div>
</div>
</div>
</div>
<div className="bg-gray-800 border border-blue-700 rounded-lg p-4">
<h5 className="font-medium text-blue-300 mb-3">Validation du dossier complet</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-gray-300">
<div className="space-y-2 text-sm">
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Intégrité de tous les documents vérifiée</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Structure du dossier validée</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Permissions et accès contrôlés</span>
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Horodatage certifié pour tous les fichiers</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Conformité RGPD du dossier</span>
</div>
<div className="flex items-center space-x-2">
<CheckCircle className="h-4 w-4 text-green-400" />
<span>Traçabilité complète des modifications</span>
</div>
</div>
</div>
</div>
<div className="bg-gray-900 border border-green-700 rounded-lg p-4">
<h5 className="font-medium text-gray-100 mb-3">Chaîne de confiance distribuée</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-gray-300">
<div className="space-y-2 text-sm">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<span>Block #{Math.floor(Math.random() * 1000000)} - Dossier principal</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
<span>{actionModal.folder.documentsCount} documents liés dans la blockchain</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-purple-500 rounded-full"></div>
<span>Réplication sur {Math.floor(Math.random() * 5) + 3} nœuds souverains</span>
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-orange-500 rounded-full"></div>
<span>Stockage {actionModal.folder.storageType === "permanent" ? "permanent" : "temporaire"} certifié</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
<span>{Math.floor(Math.random() * 100) + 50} confirmations réseau</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-indigo-500 rounded-full"></div>
<span>Audit de sécurité: {new Date().toLocaleDateString("fr-FR")}</span>
</div>
</div>
</div>
</div>
{actionModal.folder.expectedDocuments.length > 0 && (
<div className="bg-gray-800 border border-yellow-700 rounded-lg p-4">
<h5 className="font-medium text-yellow-300 mb-3">Documents attendus - Statut de validation</h5>
<div className="space-y-2 text-gray-300 text-sm">
{actionModal.folder.expectedDocuments.map((doc, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<FileText className="h-4 w-4 text-gray-400" />
<span>{doc.name}</span>
{doc.required && <span className="text-red-500 text-xs">*</span>}
</div>
<div className="flex items-center space-x-2">
<Badge
className={
doc.status === "received"
? "bg-green-700 text-green-200"
: doc.status === "pending"
? "bg-orange-700 text-orange-200"
: "bg-red-700 text-red-200"
}
>
{doc.status === "received"
? " Validé"
: doc.status === "pending"
? " En attente"
: " Manquant"}
</Badge>
</div>
</div>
))}
</div>
</div>
)}
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-700">
<Button
variant="outline"
onClick={() => {
showNotification("success", `Certificat du dossier ${actionModal.folder!.name} téléchargé`);
sendFolderChatNotification(
actionModal.folder!.id.toString(),
`📜 Certificat de validation du dossier téléchargé`,
"folder_certificate_download",
);
}}
>
<Download className="h-4 w-4 mr-2" />
Télécharger le certificat (.pdf)
</Button>
<Button
variant="outline"
onClick={() => {
showNotification("info", "Vérification en ligne du certificat du dossier...");
setTimeout(() => {
showNotification("success", "Certificat du dossier vérifié avec succès");
}, 3000);
}}
>
<ShieldCheck className="h-4 w-4 mr-2" />
Vérifier en ligne
</Button>
<Button
variant="outline"
onClick={() => {
showNotification("info", "Préparation de l'archive certifiée...");
setTimeout(() => {
showNotification(
"success",
`Archive certifiée du dossier ${actionModal.folder!.name} téléchargée`,
);
sendFolderChatNotification(
actionModal.folder!.id.toString(),
`📦 Archive certifiée complète téléchargée (${actionModal.folder!.documentsCount} documents)`,
"certified_archive_download",
);
}, 4000);
}}
>
<Archive className="h-4 w-4 mr-2" />
Archive certifiée (.zip)
</Button>
</div>
</div>
</>
)}
{/* Request Document Modal */}
{actionModal.type === "request_document" && actionModal.folder && (
<>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-100">Demander un document</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 className="space-y-4">
<div className="bg-gray-800 p-3 rounded-lg">
<p className="text-sm text-blue-300">
Sélectionnez un document attendu pour le dossier <strong>{actionModal.folder.name}</strong> et
envoyez une demande à la personne responsable.
</p>
</div>
<div>
<Label htmlFor="selectedDocument" className="text-gray-100">Document à demander</Label>
<Select value={selectedDocument} onValueChange={setSelectedDocument}>
<SelectTrigger className="bg-gray-900 text-gray-100 border-gray-700">
<SelectValue placeholder="Choisir un document" />
</SelectTrigger>
<SelectContent className="bg-gray-900 text-gray-100 border-gray-700">
{actionModal.folder.expectedDocuments.map((doc) => (
<SelectItem key={doc.name} value={doc.name}>
<div className="flex items-center justify-between w-full">
<div className="flex items-center space-x-2">
<FileText className="h-4 w-4 text-gray-300" />
<span>{doc.name}</span>
{doc.required && <span className="text-red-500">*</span>}
</div>
<div className="flex items-center space-x-2">
<Badge
className={
doc.status === "received"
? "bg-green-700 text-green-200"
: doc.status === "pending"
? "bg-orange-700 text-orange-200"
: "bg-red-700 text-red-200"
}
>
{doc.status === "received"
? "Reçu"
: doc.status === "pending"
? "En attente"
: "Manquant"}
</Badge>
<span className="text-xs text-gray-400">({doc.assignedRole})</span>
</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="requestMessage" className="text-gray-100">Message de demande</Label>
<Textarea
id="requestMessage"
value={requestMessage}
onChange={(e) => setRequestMessage(e.target.value)}
placeholder="Ajouter un message pour expliquer votre demande..."
rows={3}
className="bg-gray-900 text-gray-100 border-gray-700 placeholder-gray-500"
/>
</div>
<div className="flex justify-end space-x-2">
<Button
variant="outline"
onClick={() => setActionModal({ type: null, folder: null, folders: [] })}
>
Annuler
</Button>
<Button onClick={confirmRequestDocument} disabled={!selectedDocument}>
<FileQuestion className="h-4 w-4 mr-2" />
Envoyer la demande
</Button>
</div>
</div>
</>
)}
</div>
</div>
)}
{/* Modal */}
{isModalOpen && (
<FolderModal
isOpen={isModalOpen}
onClose={handleCloseModal}
onSave={handleSaveNewFolder}
onCancel={handleCloseModal}
folderType={folderType || "general"}
/>
)}
{/* 4NK Authentication Modal */}
{showAuthModal && (
<AuthModal
isOpen={showAuthModal}
onConnect={handleAuthConnect}
onClose={handleAuthClose}
iframeUrl={iframeUrl}
/>
)}
{/* 4NK Iframe - only show when connected */}
{isConnected && <Iframe iframeUrl={iframeUrl} />}
</div>
)
}