upgrade design
This commit is contained in:
+41
-134
@@ -1,12 +1,8 @@
|
||||
// src/layouts/AppLayout.jsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
|
||||
import { getClientTickets } from '../services/api';
|
||||
import ConfirmLogoutModal from '../components/ui/ConfirmLogoutModal';
|
||||
import ConfirmLogoutModal from '../components/ui/ConfirmLogoutModal'; // Le modal extrait !
|
||||
|
||||
// ============================================================================
|
||||
// COMPOSANT PRINCIPAL : LAYOUT DU PORTAIL
|
||||
// ============================================================================
|
||||
export default function AppLayout() {
|
||||
const navigate = useNavigate();
|
||||
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
||||
@@ -16,187 +12,98 @@ export default function AppLayout() {
|
||||
try {
|
||||
const data = await getClientTickets();
|
||||
if (data && data.list) {
|
||||
// On cherche les tickets qui ont le statut 'on_hold'
|
||||
const unread = data.list.filter(t => t.status === 'on_hold' || t.unread).length;
|
||||
setUnreadCount(unread);
|
||||
}
|
||||
} catch (err) {
|
||||
// Erreur silencieuse : on ne pollue pas l'écran si le réseau saute
|
||||
}
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkUnreadTickets(); // Premier check immédiat
|
||||
const intervalId = setInterval(checkUnreadTickets, 120000); // Check toutes les 2 minutes
|
||||
checkUnreadTickets();
|
||||
const intervalId = setInterval(checkUnreadTickets, 120000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
// Ouvre simplement le modal
|
||||
const handleLogoutClick = () => {
|
||||
setShowLogoutModal(true);
|
||||
};
|
||||
|
||||
// Exécute la vraie déconnexion
|
||||
const confirmLogout = () => {
|
||||
// Ici, tu pourras effacer les tokens (ex: localStorage.removeItem('token'))
|
||||
setShowLogoutModal(false);
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
// --- DESIGN SYSTEM DU BUNKER ---
|
||||
const sidebarStyle = {
|
||||
width: '260px',
|
||||
backgroundColor: '#121212', // Gris blindage
|
||||
borderRight: '1px solid #222222',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
fontFamily: 'monospace'
|
||||
};
|
||||
|
||||
const mainContentStyle = {
|
||||
marginLeft: '260px', // Laisse la place à la sidebar
|
||||
minHeight: '100vh',
|
||||
backgroundColor: '#050505', // Fond abyssal
|
||||
color: '#E0E0E0',
|
||||
padding: '40px'
|
||||
};
|
||||
|
||||
// Fonction pour styliser le lien actif (en Cyan)
|
||||
const getNavLinkStyle = ({ isActive }) => ({
|
||||
display: 'block',
|
||||
padding: '15px 25px',
|
||||
color: isActive ? '#00E5FF' : '#888',
|
||||
backgroundColor: isActive ? 'rgba(0, 229, 255, 0.05)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
borderLeft: isActive ? '4px solid #00E5FF' : '4px solid transparent',
|
||||
transition: 'all 0.2s',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '1px',
|
||||
fontSize: '0.9rem'
|
||||
});
|
||||
// 🌟 La fonction magique Tailwind pour les liens du menu
|
||||
const navLinkClass = ({ isActive }) =>
|
||||
`block px-6 py-4 no-underline border-l-4 transition-all uppercase tracking-widest text-sm font-mono ${
|
||||
isActive
|
||||
? 'text-cyan-400 bg-cyan-400/5 border-cyan-400'
|
||||
: 'text-[#888] border-transparent hover:text-gray-300 hover:bg-white/5'
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'block' }}>
|
||||
|
||||
{/* ========================================== */}
|
||||
{/* BARRE LATÉRALE (SIDEBAR) */}
|
||||
{/* ========================================== */}
|
||||
<aside style={sidebarStyle}>
|
||||
|
||||
{/* En-tête de la Sidebar (Logo / Marque) */}
|
||||
<div style={{ padding: '30px 25px', borderBottom: '1px solid #222' }}>
|
||||
<h1 style={{
|
||||
color: '#FFF',
|
||||
margin: 0,
|
||||
fontSize: '1.8rem',
|
||||
letterSpacing: '3px',
|
||||
display: 'flex',
|
||||
alignItems: 'baseline',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<div className="flex min-h-screen bg-[#050505] text-[#E0E0E0] font-sans">
|
||||
|
||||
{/* SIDEBAR */}
|
||||
<aside className="w-[260px] bg-[#121212] border-r border-[#222222] h-screen flex flex-col fixed top-0 left-0">
|
||||
|
||||
<div className="px-6 py-8 border-b border-[#222]">
|
||||
<h1 className="text-white m-0 text-3xl font-black tracking-widest flex items-baseline gap-2">
|
||||
NEXUS
|
||||
<span style={{
|
||||
color: '#00E5FF',
|
||||
fontSize: '0.7rem',
|
||||
letterSpacing: '1px',
|
||||
fontWeight: 'normal',
|
||||
}}>
|
||||
by GISE
|
||||
</span>
|
||||
<span className="text-cyan-400 text-[10px] tracking-widest font-normal">by GISE</span>
|
||||
</h1>
|
||||
<p style={{ color: '#555', fontSize: '0.7rem', margin: '8px 0 0 0' }}>
|
||||
CONSOLE D'INFRASTRUCTURE v1.0
|
||||
<p className="text-[#555] text-[10px] font-mono tracking-wider mt-2 uppercase">
|
||||
Console d'infrastructure v1.0
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Menu de Navigation */}
|
||||
<nav style={{ flex: 1, marginTop: '20px' }}>
|
||||
<div style={{ padding: '0 25px', marginBottom: '10px', color: '#444', fontSize: '0.7rem', fontWeight: 'bold' }}>
|
||||
<nav className="flex-1 mt-6">
|
||||
<div className="px-6 mb-2 text-[#444] text-[10px] font-bold font-mono tracking-widest">
|
||||
// SUPERVISION
|
||||
</div>
|
||||
<NavLink style={getNavLinkStyle} to="/dashboard">Tableau de bord</NavLink>
|
||||
<NavLink style={getNavLinkStyle} to="/services">Inventaire Réseau</NavLink>
|
||||
<NavLink className={navLinkClass} to="/dashboard">Tableau de bord</NavLink>
|
||||
<NavLink className={navLinkClass} to="/services">Inventaire Réseau</NavLink>
|
||||
|
||||
<div style={{ padding: '0 25px', marginTop: '25px', marginBottom: '10px', color: '#444', fontSize: '0.7rem', fontWeight: 'bold' }}>
|
||||
<div className="px-6 mt-8 mb-2 text-[#444] text-[10px] font-bold font-mono tracking-widest">
|
||||
// GESTION
|
||||
</div>
|
||||
{/*<NavLink style={getNavLinkStyle} to="/billing" locked>Facturation</NavLink>*/}
|
||||
<NavLink style={getNavLinkStyle} to="/support">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||
<NavLink className={navLinkClass} to="/support">
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span>Tickets Support</span>
|
||||
|
||||
{/* LE BADGE DE NOTIFICATION */}
|
||||
{unreadCount > 0 && (
|
||||
<span style={{
|
||||
backgroundColor: '#00E5FF',
|
||||
color: '#000',
|
||||
fontSize: '0.65rem',
|
||||
fontWeight: 'bold',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 0 10px rgba(0,229,255,0.6)'
|
||||
}}>
|
||||
<span className="bg-cyan-400 text-black text-[10px] font-bold px-2 py-0.5 rounded-full shadow-[0_0_10px_rgba(0,229,255,0.6)] animate-pulse">
|
||||
{unreadCount} NEW
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</NavLink>
|
||||
{/*<NavLink style={getNavLinkStyle} to="/settings">Profil & Sécurité</NavLink>*/}
|
||||
{/* === MODULES VERROUILLÉS (WORK IN PROGRESS) === */}
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
padding: '15px 25px', color: '#444', borderLeft: '4px solid transparent',
|
||||
textTransform: 'uppercase', letterSpacing: '1px', fontSize: '0.9rem',
|
||||
cursor: 'not-allowed', userSelect: 'none'
|
||||
}}>
|
||||
<span>Facturation</span>
|
||||
<span style={{ fontSize: '0.6rem', color: '#333', border: '1px solid #333', padding: '2px 5px', borderRadius: '3px', fontWeight: 'bold' }}>PROCHAINEMENT</span>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
padding: '15px 25px', color: '#444', borderLeft: '4px solid transparent',
|
||||
textTransform: 'uppercase', letterSpacing: '1px', fontSize: '0.9rem',
|
||||
cursor: 'not-allowed', userSelect: 'none'
|
||||
}}>
|
||||
<div className="flex justify-between items-center px-6 py-4 text-[#444] border-l-4 border-transparent uppercase tracking-widest text-sm font-mono cursor-not-allowed select-none">
|
||||
<span>Facturation</span>
|
||||
<span className="text-[9px] text-[#333] border border-[#333] px-1.5 py-0.5 rounded font-bold">WIP</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center px-6 py-4 text-[#444] border-l-4 border-transparent uppercase tracking-widest text-sm font-mono cursor-not-allowed select-none">
|
||||
<span>Profil & Sécurité</span>
|
||||
<span style={{ fontSize: '0.6rem', color: '#333', border: '1px solid #333', padding: '2px 5px', borderRadius: '3px', fontWeight: 'bold' }}>PROCHAINEMENT</span>
|
||||
<span className="text-[9px] text-[#333] border border-[#333] px-1.5 py-0.5 rounded font-bold">WIP</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Bas de la Sidebar (Déconnexion) */}
|
||||
<div style={{ padding: '20px', borderTop: '1px solid #222' }}>
|
||||
<div className="p-5 border-t border-[#222]">
|
||||
<button
|
||||
onClick={handleLogoutClick}
|
||||
style={{
|
||||
width: '100%', padding: '12px', backgroundColor: 'transparent',
|
||||
color: '#ff003c', border: '1px solid #ff003c', cursor: 'pointer',
|
||||
fontFamily: 'monospace', textTransform: 'uppercase'
|
||||
}}
|
||||
onClick={() => setShowLogoutModal(true)}
|
||||
className="w-full p-3 bg-transparent text-[#ff003c] hover:bg-[#ff003c]/10 border border-[#ff003c] cursor-pointer font-mono text-sm tracking-widest uppercase transition-colors rounded-sm"
|
||||
>
|
||||
[ Déconnexion ]
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* ========================================== */}
|
||||
{/* ZONE DE CONTENU DYNAMIQUE */}
|
||||
{/* ========================================== */}
|
||||
<main style={mainContentStyle}>
|
||||
{/* Le composant <Outlet /> injecte le contenu de la page demandée sans recharger la sidebar */}
|
||||
{/* ZONE DE CONTENU */}
|
||||
<main className="ml-[260px] p-10 flex-1 min-h-screen">
|
||||
<Outlet context={{ refreshNavbarTickets: checkUnreadTickets }} />
|
||||
</main>
|
||||
|
||||
{/* MODAL DE DÉCONNEXION */}
|
||||
<ConfirmLogoutModal
|
||||
isOpen={showLogoutModal}
|
||||
onClose={() => setShowLogoutModal(false)}
|
||||
onConfirm={confirmLogout}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user