docv/app/dashboard/page.tsx
2025-10-20 11:58:50 +02:00

505 lines
17 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, 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,
XCircle,
Info,
} from "@/lib/icons"
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
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>
{/* 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>
)
}