555 lines
19 KiB
TypeScript
555 lines
19 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect, useCallback } from "react"
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import {
|
||
FileText,
|
||
Folder,
|
||
Users,
|
||
Activity,
|
||
TrendingUp,
|
||
Clock,
|
||
Shield,
|
||
AlertCircle,
|
||
CheckCircle,
|
||
Download,
|
||
Upload,
|
||
Search,
|
||
Plus,
|
||
MoreHorizontal,
|
||
Edit,
|
||
Share2,
|
||
TestTube,
|
||
Zap,
|
||
HardDrive,
|
||
X,
|
||
FolderPlus,
|
||
Brain,
|
||
XCircle,
|
||
Info,
|
||
} from "lucide-react"
|
||
import MessageBus from "@/lib/4nk/MessageBus"
|
||
import Link from "next/link"
|
||
import Chat from "@/components/4nk/Chat"
|
||
import UserStore from "@/lib/4nk/UserStore"
|
||
import EventBus from "@/lib/4nk/EventBus"
|
||
import { iframeUrl } from "../page"
|
||
import Iframe from "@/components/4nk/Iframe"
|
||
|
||
type FolderType = "contrat" | "projet" | "rapport" | "finance" | "rh" | "marketing";
|
||
|
||
export default function DashboardPage() {
|
||
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
|
||
|
||
// 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 [isModalOpen, setIsModalOpen] = useState(false);
|
||
const [menuOpen, setMenuOpen] = useState(false);
|
||
const [folderType, setFolderType] = useState<FolderType | null>(null);
|
||
|
||
const [stats, setStats] = useState({
|
||
totalDocuments: 0,
|
||
totalFolders: 0,
|
||
totalUsers: 0,
|
||
storageUsed: 0,
|
||
storageLimit: 100,
|
||
recentActivity: 0,
|
||
// Nouveaux indicateurs
|
||
permanentStorage: 0,
|
||
permanentStorageLimit: 1000, // 1 To en Go
|
||
temporaryStorage: 0,
|
||
temporaryStorageLimit: 100, // 100 Go
|
||
newFoldersThisMonth: 0,
|
||
newFoldersLimit: 75,
|
||
tokensUsed: 0,
|
||
tokensTotal: 1000,
|
||
})
|
||
|
||
const [recentDocuments, setRecentDocuments] = useState<any[]>([])
|
||
const [recentActivity, setRecentActivity] = useState<any[]>([])
|
||
const [notifications, setNotifications] = useState<any[]>([])
|
||
|
||
useEffect(() => {
|
||
// Simuler le chargement des données
|
||
const mockMode = true
|
||
if (mockMode) {
|
||
setStats({
|
||
totalDocuments: 1247,
|
||
totalFolders: 89,
|
||
totalUsers: 12,
|
||
storageUsed: 67.3,
|
||
storageLimit: 100,
|
||
recentActivity: 24,
|
||
// Nouveaux indicateurs avec données réalistes
|
||
permanentStorage: 673, // 673 Go utilisés sur 1000 Go
|
||
permanentStorageLimit: 1000,
|
||
temporaryStorage: 45, // 45 Go utilisés sur 100 Go
|
||
temporaryStorageLimit: 100,
|
||
newFoldersThisMonth: 23, // 23 nouveaux dossiers ce mois
|
||
newFoldersLimit: 75,
|
||
tokensUsed: 673, // Environ 67% des jetons utilisés
|
||
tokensTotal: 1000,
|
||
})
|
||
|
||
setRecentDocuments([
|
||
{
|
||
id: "doc_001",
|
||
name: "Contrat_Client_ABC_2024.pdf",
|
||
type: "PDF",
|
||
size: "2.4 MB",
|
||
modifiedAt: "Il y a 2 heures",
|
||
modifiedBy: "Marie Dubois",
|
||
status: "Signé",
|
||
folder: "Contrats 2024",
|
||
},
|
||
{
|
||
id: "doc_002",
|
||
name: "Rapport_Financier_Q1.xlsx",
|
||
type: "Excel",
|
||
size: "1.8 MB",
|
||
modifiedAt: "Il y a 4 heures",
|
||
modifiedBy: "Jean Martin",
|
||
status: "En révision",
|
||
folder: "Finance",
|
||
},
|
||
{
|
||
id: "doc_003",
|
||
name: "Présentation_Produit_V2.pptx",
|
||
type: "PowerPoint",
|
||
size: "15.2 MB",
|
||
modifiedAt: "Hier",
|
||
modifiedBy: "Sophie Laurent",
|
||
status: "Finalisé",
|
||
folder: "Marketing",
|
||
},
|
||
{
|
||
id: "doc_004",
|
||
name: "Cahier_des_charges_Projet_X.docx",
|
||
type: "Word",
|
||
size: "892 KB",
|
||
modifiedAt: "Il y a 2 jours",
|
||
modifiedBy: "Pierre Durand",
|
||
status: "Brouillon",
|
||
folder: "Projets",
|
||
},
|
||
{
|
||
id: "doc_005",
|
||
name: "Facture_2024_001.pdf",
|
||
type: "PDF",
|
||
size: "156 KB",
|
||
modifiedAt: "Il y a 3 jours",
|
||
modifiedBy: "Marie Dubois",
|
||
status: "Payée",
|
||
folder: "Comptabilité",
|
||
},
|
||
])
|
||
|
||
setRecentActivity([
|
||
{
|
||
id: "act_001",
|
||
type: "upload",
|
||
user: "Marie Dubois",
|
||
action: "a téléchargé",
|
||
target: "Contrat_Client_ABC_2024.pdf",
|
||
time: "Il y a 2 heures",
|
||
icon: Upload,
|
||
color: "text-green-600",
|
||
},
|
||
{
|
||
id: "act_002",
|
||
type: "edit",
|
||
user: "Jean Martin",
|
||
action: "a modifié",
|
||
target: "Rapport_Financier_Q1.xlsx",
|
||
time: "Il y a 4 heures",
|
||
icon: Edit,
|
||
color: "text-blue-600",
|
||
},
|
||
{
|
||
id: "act_003",
|
||
type: "share",
|
||
user: "Sophie Laurent",
|
||
action: "a partagé",
|
||
target: "Présentation_Produit_V2.pptx",
|
||
time: "Hier",
|
||
icon: Share2,
|
||
color: "text-purple-600",
|
||
},
|
||
{
|
||
id: "act_004",
|
||
type: "create",
|
||
user: "Pierre Durand",
|
||
action: "a créé le dossier",
|
||
target: "Projets 2024",
|
||
time: "Il y a 2 jours",
|
||
icon: Folder,
|
||
color: "text-orange-600",
|
||
},
|
||
{
|
||
id: "act_005",
|
||
type: "download",
|
||
user: "Marie Dubois",
|
||
action: "a téléchargé",
|
||
target: "Facture_2024_001.pdf",
|
||
time: "Il y a 3 jours",
|
||
icon: Download,
|
||
color: "text-indigo-600",
|
||
},
|
||
])
|
||
|
||
setNotifications([
|
||
{
|
||
id: "notif_001",
|
||
type: "success",
|
||
title: "Document signé",
|
||
message: "Le contrat ABC a été signé par toutes les parties",
|
||
time: "Il y a 1 heure",
|
||
icon: CheckCircle,
|
||
color: "text-green-600",
|
||
bgColor: "bg-green-50",
|
||
},
|
||
{
|
||
id: "notif_002",
|
||
type: "warning",
|
||
title: "Stockage temporaire élevé",
|
||
message: "45 Go utilisés sur 100 Go de stockage temporaire ce mois",
|
||
time: "Il y a 2 heures",
|
||
icon: AlertCircle,
|
||
color: "text-orange-600",
|
||
bgColor: "bg-orange-50",
|
||
},
|
||
{
|
||
id: "notif_003",
|
||
type: "info",
|
||
title: "Nouvel utilisateur",
|
||
message: "Thomas Petit a rejoint l'équipe Marketing",
|
||
time: "Hier",
|
||
icon: Users,
|
||
color: "text-blue-600",
|
||
bgColor: "bg-blue-50",
|
||
},
|
||
])
|
||
}
|
||
}, [])
|
||
|
||
const getFileIcon = (type: string) => {
|
||
switch (type.toLowerCase()) {
|
||
case "pdf":
|
||
return "📄"
|
||
case "excel":
|
||
return "📊"
|
||
case "powerpoint":
|
||
return "📈"
|
||
case "word":
|
||
return "📝"
|
||
default:
|
||
return "📄"
|
||
}
|
||
}
|
||
|
||
const getStatusColor = (status: string) => {
|
||
switch (status.toLowerCase()) {
|
||
case "signé":
|
||
case "finalisé":
|
||
case "payée":
|
||
return "bg-green-100 text-green-800"
|
||
case "en révision":
|
||
return "bg-orange-100 text-orange-800"
|
||
case "brouillon":
|
||
return "bg-gray-100 text-gray-800"
|
||
default:
|
||
return "bg-blue-100 text-blue-800"
|
||
}
|
||
}
|
||
|
||
// 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 || pairingId === 'undefined' || pairingId === 'null') {
|
||
// We may have a pairing id but the value is not in cache for some reason
|
||
pairingId = await messageBus.getUserPairingId();
|
||
if (pairingId) {
|
||
userStore.pair(pairingId);
|
||
setUserPairingId(pairingId);
|
||
} else {
|
||
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]);
|
||
|
||
const handleOpenModal = (type: FolderType) => {
|
||
setFolderType(type);
|
||
setIsModalOpen(true);
|
||
setMenuOpen(false);
|
||
};
|
||
|
||
const handleCloseModal = () => {
|
||
setIsModalOpen(false);
|
||
setFolderType(null);
|
||
};
|
||
|
||
// Notification system
|
||
const showNotification = (type: "success" | "error" | "info", message: string) => {
|
||
setNotification({ type, message })
|
||
setTimeout(() => setNotification(null), 3000)
|
||
}
|
||
|
||
// 4NK handlers
|
||
|
||
// Debug function pour forcer la récupération du userPairingId
|
||
const handleForceGetPairingId = useCallback(() => {
|
||
console.log('Force récupération userPairingId - État actuel:', {
|
||
isConnected,
|
||
userPairingId,
|
||
userStoreConnected: UserStore.getInstance().isConnected(),
|
||
userStorePairingId: UserStore.getInstance().getUserPairingId()
|
||
});
|
||
|
||
// D'abord essayer de synchroniser depuis UserStore
|
||
const userStorePairingId = UserStore.getInstance().getUserPairingId();
|
||
if (userStorePairingId) {
|
||
console.log('Force - Synchronisation depuis UserStore:', userStorePairingId);
|
||
setUserPairingId(userStorePairingId);
|
||
showNotification("success", `UserPairingId synchronisé: ${userStorePairingId.substring(0, 8)}...`);
|
||
return;
|
||
}
|
||
|
||
// Sinon récupérer depuis MessageBus
|
||
if (isConnected) {
|
||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||
messageBus.isReady().then(() => {
|
||
console.log('Force - MessageBus prêt');
|
||
messageBus.getUserPairingId().then((retrievedPairingId: string) => {
|
||
console.log('Force - UserPairingId récupéré:', retrievedPairingId);
|
||
UserStore.getInstance().pair(retrievedPairingId);
|
||
setUserPairingId(retrievedPairingId);
|
||
showNotification("success", `UserPairingId récupéré: ${retrievedPairingId.substring(0, 8)}...`);
|
||
}).catch((error) => {
|
||
console.error('Force - Erreur récupération userPairingId:', error);
|
||
showNotification("error", "Erreur lors de la récupération du userPairingId");
|
||
});
|
||
}).catch((error) => {
|
||
console.error('Force - Erreur MessageBus isReady:', error);
|
||
showNotification("error", "MessageBus non prêt");
|
||
});
|
||
} else {
|
||
showNotification("error", "Vous devez être connecté à 4NK");
|
||
}
|
||
}, [isConnected, userPairingId, iframeUrl]);
|
||
|
||
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>
|
||
)}
|
||
{/* En-tête */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">My work</h1>
|
||
<p className="text-gray-600 dark:text-gray-300">
|
||
Vue d'ensemble de votre espace documentaire sécurisé
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
{/* Debug PairingId */}
|
||
{!userPairingId && (
|
||
<Button variant="outline" size="sm" onClick={handleForceGetPairingId}>
|
||
<Brain className="h-4 w-4 mr-2" />
|
||
Debug PairingId
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{/* Statistiques principales */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
{/* SUPPRIMER les cartes Documents, Dossiers, Collaborateurs */}
|
||
{/* Conserver uniquement les autres indicateurs utiles (ex : Jetons utilisés, stockage, etc.) */}
|
||
</div>
|
||
|
||
{/* Messages intégrés */}
|
||
<div className="mt-6">
|
||
<Chat heightClass="h-[600px]" />
|
||
</div>
|
||
|
||
{/* Nouveaux indicateurs de stockage */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||
Stockage permanent
|
||
</CardTitle>
|
||
<HardDrive className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.permanentStorage} Go</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||
<div
|
||
className="bg-blue-600 dark:bg-blue-400 h-2 rounded-full"
|
||
style={{ width: `${(stats.permanentStorage / stats.permanentStorageLimit) * 100}%` }}
|
||
></div>
|
||
</div>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||
{stats.permanentStorage} Go / {stats.permanentStorageLimit} Go (1 To)
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||
Stockage temporaire
|
||
</CardTitle>
|
||
<Zap className="h-4 w-4 text-orange-600 dark:text-orange-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.temporaryStorage} Go</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||
<div
|
||
className={`h-2 rounded-full ${stats.temporaryStorage > 80
|
||
? "bg-red-600 dark:bg-red-500"
|
||
: stats.temporaryStorage > 60
|
||
? "bg-orange-600 dark:bg-orange-500"
|
||
: "bg-green-600 dark:bg-green-500"
|
||
}`}
|
||
style={{ width: `${(stats.temporaryStorage / stats.temporaryStorageLimit) * 100}%` }}
|
||
></div>
|
||
</div>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||
{stats.temporaryStorage} Go / {stats.temporaryStorageLimit} Go ce mois
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
<CardTitle className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||
Nouveaux dossiers
|
||
</CardTitle>
|
||
<Plus className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">{stats.newFoldersThisMonth}</div>
|
||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mt-2">
|
||
<div
|
||
className="bg-green-600 dark:bg-green-400 h-2 rounded-full"
|
||
style={{ width: `${(stats.newFoldersThisMonth / stats.newFoldersLimit) * 100}%` }}
|
||
></div>
|
||
</div>
|
||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||
{stats.newFoldersThisMonth} / {stats.newFoldersLimit} ce mois
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Sécurité */}
|
||
<Card className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center text-gray-900 dark:text-gray-100">
|
||
<Shield className="h-5 w-5 mr-2 text-green-600 dark:text-green-400" />
|
||
Statut de sécurité
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="flex items-center space-x-4 p-4 bg-green-50 dark:bg-green-900 rounded-lg">
|
||
<CheckCircle className="h-8 w-8 text-green-600 dark:text-green-400" />
|
||
<div>
|
||
<h4 className="font-medium text-green-900 dark:text-green-300">Sécurité optimale</h4>
|
||
<p className="text-sm text-green-700 dark:text-green-200">
|
||
Tous vos documents sont chiffrés et sécurisés par la technologie 4NK
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||
<div className="text-center p-3">
|
||
<Shield className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Chiffrement bout en bout</p>
|
||
</div>
|
||
<div className="text-center p-3">
|
||
<CheckCircle className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Authentification 4NK</p>
|
||
</div>
|
||
<div className="text-center p-3">
|
||
<Activity className="h-6 w-6 mx-auto text-green-600 dark:text-green-400 mb-2" />
|
||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">Audit complet</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 4NK Iframe - only show when connected */}
|
||
{isConnected && <Iframe iframeUrl={iframeUrl} />}
|
||
</div>
|
||
)
|
||
}
|