espace-paie-odentas/components/Sidebar.tsx

763 lines
35 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState, useEffect, useRef } from "react";
import { Shield, KeyRound, Home, FileSignature, Banknote, Users, Percent, FolderOpen, Receipt, Building2, UserCog, Edit3, LifeBuoy, CreditCard, Calculator, Euro, Mail, Database, Clapperboard, LogOut, Scale, FileEdit, FileText, Megaphone, Gift, Clock } from "lucide-react";
// import { api } from "@/lib/fetcher";
import { createPortal } from "react-dom";
import LogoutButton from "@/components/LogoutButton";
import { MaintenanceButton } from "@/components/MaintenanceButton";
import { useDemoMode } from "@/hooks/useDemoMode";
import { usePendingSignatures } from "@/hooks/usePendingSignatures";
import { StaffOrgBadge } from "@/components/StaffOrgBadge";
function AccessLink({ disabled, fullWidth = false }: { disabled: boolean; fullWidth?: boolean }) {
const btnRef = useRef<HTMLElement | null>(null);
const [tipOpen, setTipOpen] = useState(false);
const [tipPos, setTipPos] = useState<{ top: number; left: number } | null>(null);
function computePos() {
const el = btnRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
setTipPos({ top: r.top + window.scrollY, left: r.left + window.scrollX + r.width / 2 });
}
useEffect(() => {
if (!tipOpen) return;
const onScroll = () => computePos();
const onResize = () => computePos();
window.addEventListener('scroll', onScroll, true);
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('scroll', onScroll, true);
window.removeEventListener('resize', onResize);
};
}, [tipOpen]);
if (disabled) {
return (
<>
<span
ref={btnRef}
onMouseEnter={() => { computePos(); setTipOpen(true); }}
onMouseLeave={() => setTipOpen(false)}
onFocus={() => { computePos(); setTipOpen(true); }}
onBlur={() => setTipOpen(false)}
className={
`inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-emerald-50 text-emerald-700/60 cursor-not-allowed ${fullWidth ? 'w-full justify-center' : ''}`
}
role="link"
aria-disabled="true"
tabIndex={0}
>
<KeyRound className="w-3.5 h-3.5" />
Vos accès
</span>
{tipOpen && tipPos && createPortal(
(() => {
const top = tipPos.top - 10; // au-dessus
const left = tipPos.left;
return (
<div className="z-[1200]" style={{ position: 'fixed', top, left, transform: 'translate(-50%, -100%)' }}>
<div className="inline-block max-w-[300px] rounded-md border border-amber-400 bg-amber-50 text-amber-900 text-[12px] px-2 py-1 shadow">
Vous n'avez pas les habilitations nécessaires pour accéder à cette page
</div>
<div className="mx-auto w-0 h-0" style={{ borderLeft: '8px solid transparent', borderRight: '8px solid transparent', borderTop: '8px solid #f59e0b' }} />
</div>
);
})(),
document.body
)}
</>
);
}
return (
<Link
href="/vos-acces"
prefetch={false}
className={`inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-emerald-50 text-emerald-700 hover:bg-emerald-100 ${fullWidth ? 'w-full justify-center' : ''}`}
title={'Gérer les accès'}
>
<KeyRound className="w-3.5 h-3.5" />
Vos accès
</Link>
);
}
function DisabledMenuItem({ icon: Icon, label, tooltipMessage }: { icon: any; label: string; tooltipMessage: string }) {
const btnRef = useRef<HTMLSpanElement | null>(null);
const [tipOpen, setTipOpen] = useState(false);
const [tipPos, setTipPos] = useState<{ top: number; left: number } | null>(null);
function computePos() {
const el = btnRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
// Position à droite de l'élément
setTipPos({
top: r.top + window.scrollY + r.height / 2,
left: r.right + window.scrollX + 8 // 8px de marge à droite
});
}
useEffect(() => {
if (!tipOpen) return;
const onScroll = () => computePos();
const onResize = () => computePos();
window.addEventListener('scroll', onScroll, true);
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('scroll', onScroll, true);
window.removeEventListener('resize', onResize);
};
}, [tipOpen]);
return (
<>
<span
ref={btnRef}
onMouseEnter={() => { computePos(); setTipOpen(true); }}
onMouseLeave={() => setTipOpen(false)}
onFocus={() => { computePos(); setTipOpen(true); }}
onBlur={() => setTipOpen(false)}
className="block px-3 py-2 rounded-xl text-sm cursor-not-allowed opacity-50"
role="link"
aria-disabled="true"
tabIndex={0}
>
<span className="inline-flex items-center gap-2">
<Icon className="w-4 h-4" aria-hidden />
<span>{label}</span>
</span>
</span>
{tipOpen && tipPos && createPortal(
(() => {
const top = tipPos.top;
const left = tipPos.left;
return (
<div className="z-[1200] fixed" style={{ top, left, transform: 'translateY(-50%)' }}>
<div className="flex items-center">
{/* Flèche pointant vers la gauche */}
<div className="w-0 h-0" style={{
borderTop: '6px solid transparent',
borderBottom: '6px solid transparent',
borderRight: '6px solid rgb(17, 24, 39)' // gray-900
}} />
<div className="inline-block max-w-[280px] rounded-lg bg-gray-900 text-white text-sm px-3 py-2 shadow-xl">
{tooltipMessage}
</div>
</div>
</div>
);
})(),
document.body
)}
</>
);
}
type ClientInfo = {
id: string;
name: string;
api_name?: string;
user?: {
id: string;
email: string;
display_name?: string;
first_name?: string;
} | null;
} | null;
function isActivePath(pathname: string, href: string) {
if (href === "/") return pathname === "/"; // dashboard only on exact root
// Spécifique : l'onglet Contrats & Paies doit aussi être actif sur /contrats-multi
if (href === "/contrats") {
if (pathname === "/contrats" || pathname.startsWith("/contrats/")) return true;
if (pathname.startsWith("/contrats-multi")) return true;
if (pathname.startsWith("/contrats-rg")) return true;
return false;
}
if (href === "/virements-salaires") {
if (pathname === "/virements-salaires" || pathname.startsWith("/virements-salaires/")) return true;
return false;
}
return pathname === href || pathname.startsWith(href + "/");
}
const items = [
{ href: "/", label: "Tableau de bord", icon: Home },
{ href: "/contrats", label: "Contrats & Paies", icon: FileSignature },
{ href: "/signatures-electroniques", label: "Signatures électroniques", icon: Edit3 },
{ href: "/virements-salaires", label: "Virements de salaires", icon: Banknote },
{ href: "/salaries", label: "Salariés", icon: Users },
{ href: "/cotisations", label: "Cotisations", icon: Percent },
];
// Hook pour l'horloge en temps réel
function useCurrentTime() {
// Important: initialiser mais ne pas utiliser avant le montage pour éviter la
// divergence SSR/CSR (l'heure évolue entre le rendu serveur et l'hydratation)
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
return currentTime;
}
// Composant horloge
function LiveClock() {
// Ne rien afficher de dépendant du temps pendant l'hydratation
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const currentTime = useCurrentTime();
const timeString = mounted
? currentTime.toLocaleTimeString('fr-FR', {
timeZone: 'Europe/Paris',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
: '—:—:—';
const dateString = mounted
? currentTime.toLocaleDateString('fr-FR', {
timeZone: 'Europe/Paris',
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
})
: '—';
return (
<div className="mt-3 mb-3 mx-auto max-w-[280px] rounded-xl border bg-white p-2 text-center">
<div className="font-mono text-sm font-medium text-slate-700" suppressHydrationWarning>
{timeString}
</div>
<div className="text-xs text-slate-500 capitalize leading-tight" suppressHydrationWarning>
{dateString}
</div>
</div>
);
}
export default function Sidebar({ clientInfo, isStaff = false, mobile = false, onNavigate }: { clientInfo?: ClientInfo, isStaff?: boolean, mobile?: boolean, onNavigate?: () => void }) {
const pathname = usePathname();
const [canManageAccess, setCanManageAccess] = useState(false);
const [userRole, setUserRole] = useState<string | null>(null);
// 🎭 Détection du mode démo
const { isDemoMode } = useDemoMode();
// 🔔 Compteur de signatures en attente (polling 30s, optimisé)
const orgId = clientInfo?.id || null;
const { data: pendingData } = usePendingSignatures(orgId, !isStaff && !isDemoMode);
const pendingCount = pendingData?.count || 0;
// Signature count in sidebar disabled to reduce Airtable load
useEffect(() => {
let cancelled = false;
async function run() {
try {
const res = await fetch('/api/me/role', { credentials: 'include', cache: 'no-store' });
if (!res.ok) throw new Error(String(res.status));
const j = await res.json();
const role = (j?.role ? String(j.role).toUpperCase() : null) as string | null;
if (!cancelled) {
setUserRole(role);
setCanManageAccess(role === 'ADMIN' || role === 'SUPER_ADMIN' || j?.isStaff === true);
}
} catch {
if (!cancelled) {
setUserRole(null);
setCanManageAccess(false);
}
}
}
run();
return () => { cancelled = true; };
}, [clientInfo?.id, clientInfo?.user?.id]);
// Removed: polling signatures count to avoid Airtable 429s
// Plus besoin de useState ou useEffect - les infos viennent directement du serveur
const orgName = clientInfo?.name || "Organisation";
const rawFirstName = clientInfo?.user?.first_name || clientInfo?.user?.display_name?.split(' ')[0] || null;
const userFirstName = rawFirstName
? rawFirstName.trim().replace(/^./, (ch) => ch.toLocaleUpperCase('fr-FR'))
: null;
return (
<aside className={`${mobile ? 'block' : 'hidden md:block'} w-full min-w-0 overflow-y-auto overflow-x-hidden px-2 box-border`}>
{/* Horloge en temps réel */}
<LiveClock />
<div className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-4 text-sm mb-3">
<div className="font-medium truncate">👋 Bonjour{userFirstName ? ` ${userFirstName}` : ''}</div>
<div className="text-xs text-slate-500 mt-1">Votre structure</div>
<div className="mb-2 text-xs truncate" title={isStaff ? 'Odentas' : orgName}>{isStaff ? 'Odentas' : orgName}</div>
<div className="text-xs text-slate-500">Votre niveau d'accès</div>
<div className="mb-2 text-xs uppercase tracking-wide">
{isDemoMode ? 'DEMO' : (isStaff ? 'STAFF' : (userRole || ''))}
</div>
{/* Badge orga active (uniquement pour le staff) */}
{isStaff && <div className="mb-2"><StaffOrgBadge /></div>}
{!isStaff && (
<>
<div className="text-xs text-slate-500">Votre gestionnaire</div>
<div className="text-xs truncate">
{isDemoMode ? '' : 'Renaud BREVIERE-ABRAHAM'}
</div>
</>
)}
{/* Sous-card liens sécurité & accès (compacte) */}
<div className="mt-2 rounded-lg border bg-slate-50 p-2 overflow-hidden">
<div className="grid grid-cols-2 gap-2">
{/* Sécurité */}
{isDemoMode ? (
<span
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-100 text-slate-400 cursor-not-allowed w-full justify-center"
title="Désactivé en mode démo"
>
<Shield className="w-3.5 h-3.5" />
Sécurité
</span>
) : (
<Link
href="/securite"
prefetch={false}
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-indigo-50 text-indigo-700 hover:bg-indigo-100 w-full justify-center"
title="Sécurité du compte"
>
<Shield className="w-3.5 h-3.5" />
Sécurité
</Link>
)}
{/* Vos accès */}
{isDemoMode ? (
<span
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-100 text-slate-400 cursor-not-allowed w-full justify-center"
title="Désactivé en mode démo"
>
<KeyRound className="w-3.5 h-3.5" />
Vos accès
</span>
) : (
<AccessLink disabled={!canManageAccess} fullWidth />
)}
{/* Support */}
{isDemoMode ? (
<span
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-100 text-slate-400 cursor-not-allowed w-full justify-center"
title="Désactivé en mode démo"
>
<LifeBuoy className="w-3.5 h-3.5" />
Support
</span>
) : (
<Link
href="/support"
prefetch={false}
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-blue-50 text-blue-700 hover:bg-blue-100 w-full justify-center"
title="Support"
>
<LifeBuoy className="w-3.5 h-3.5" />
Support
</Link>
)}
{/* Déconnexion */}
{isDemoMode ? (
<span
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-100 text-slate-400 cursor-not-allowed w-full justify-center"
title="Désactivé en mode démo"
>
<LogOut className="w-3.5 h-3.5" />
Déconnexion
</span>
) : (
<LogoutButton variant="compact" className="w-full justify-center" />
)}
</div>
{/* Parrainage - Temporairement masqué */}
{/* {!isDemoMode && (
<div className="mt-2 pt-2 border-t">
<Link
href="/parrainage"
prefetch={false}
className="inline-flex items-center gap-1.5 px-2 py-1.5 rounded-md text-[10px] font-medium bg-gradient-to-r from-orange-50 to-amber-50 text-orange-700 hover:from-orange-100 hover:to-amber-100 w-full justify-center transition-colors"
title="Parrainez vos amis"
>
<Gift className="w-3 h-3" />
Parrainage
</Link>
</div>
)} */}
</div>
</div>
{/* Menu Staff */}
{isStaff && (
<div className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-2">
<div className="px-3 py-2 text-sm font-semibold text-slate-700 border-b">
Staff
</div>
{/* Gestion Paie */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Gestion Paie
</div>
<Link href="/staff/salaries" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/salaries") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des salariés">
<span className="inline-flex items-center gap-2">
<Users className="w-4 h-4" aria-hidden />
<span>Salariés</span>
</span>
</Link>
<Link href="/staff/gestion-productions" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/gestion-productions") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des productions">
<span className="inline-flex items-center gap-2">
<Clapperboard className="w-4 h-4" aria-hidden />
<span>Productions</span>
</span>
</Link>
<Link href="/staff/contrats" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/contrats") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des contrats">
<span className="inline-flex items-center gap-2">
<FileSignature className="w-4 h-4" aria-hidden />
<span>Contrats</span>
</span>
</Link>
<Link href="/staff/avenants" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/avenants") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des avenants">
<span className="inline-flex items-center gap-2">
<FileEdit className="w-4 h-4" aria-hidden />
<span>Avenants</span>
</span>
</Link>
<Link href="/staff/payslips" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/payslips") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des fiches de paie">
<span className="inline-flex items-center gap-2">
<CreditCard className="w-4 h-4" aria-hidden />
<span>Fiches de paie</span>
</span>
</Link>
<Link href="/staff/virements-salaires" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/virements-salaires") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Virements de salaires">
<span className="inline-flex items-center gap-2">
<Banknote className="w-4 h-4" aria-hidden />
<span>Virements salaires</span>
</span>
</Link>
<Link href="/staff/gestion-cotisations" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/gestion-cotisations") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des cotisations">
<span className="inline-flex items-center gap-2">
<Percent className="w-4 h-4" aria-hidden />
<span>Cotisations</span>
</span>
</Link>
<Link href="/staff/contrats/saisie-temps-reel" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/contrats/saisie-temps-reel") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Saisie temps de travail réel">
<span className="inline-flex items-center gap-2">
<Clock className="w-4 h-4" aria-hidden />
<span>Temps de travail réel</span>
</span>
</Link>
</div>
{/* Administration */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Administration
</div>
<Link href="/staff/clients" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/clients") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des clients">
<span className="inline-flex items-center gap-2">
<Building2 className="w-4 h-4" aria-hidden />
<span>Clients</span>
</span>
</Link>
<Link href="/staff/utilisateurs" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/utilisateurs") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des utilisateurs">
<span className="inline-flex items-center gap-2">
<UserCog className="w-4 h-4" aria-hidden />
<span>Utilisateurs</span>
</span>
</Link>
<Link href="/staff/documents" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/documents") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Documents">
<span className="inline-flex items-center gap-2">
<FolderOpen className="w-4 h-4" aria-hidden />
<span>Documents</span>
</span>
</Link>
</div>
{/* Gestion Facturation */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Gestion Facturation
</div>
<Link href="/staff/facturation" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/facturation") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion de la facturation">
<span className="inline-flex items-center gap-2">
<CreditCard className="w-4 h-4" aria-hidden />
<span>Facturation</span>
</span>
</Link>
<Link href="/staff/naa" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/naa") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Notes Apporteurs d'Affaires">
<span className="inline-flex items-center gap-2">
<FileText className="w-4 h-4" aria-hidden />
<span>NAA</span>
</span>
</Link>
<Link href="/staff/apporteurs" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/apporteurs") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Gestion des apporteurs">
<span className="inline-flex items-center gap-2">
<Building2 className="w-4 h-4" aria-hidden />
<span>Apporteurs</span>
</span>
</Link>
</div>
{/* Support & Communication */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Support & Communication
</div>
<Link href="/staff/tickets" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/tickets") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Tickets support">
<span className="inline-flex items-center gap-2">
<LifeBuoy className="w-4 h-4" aria-hidden />
<span>Tickets support</span>
</span>
</Link>
<Link href="/staff/emails-groupes" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/emails-groupes") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Envoi d'emails groupés">
<span className="inline-flex items-center gap-2">
<Mail className="w-4 h-4" aria-hidden />
<span>Emails groupés</span>
</span>
</Link>
<Link href="/staff/email-logs" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/email-logs") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Logs des emails">
<span className="inline-flex items-center gap-2">
<Database className="w-4 h-4" aria-hidden />
<span>Logs des emails</span>
</span>
</Link>
</div>
{/* Mockups & Tests */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Mockups & Tests
</div>
<Link href="/staff/mockups/hub-salarie" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/mockups/hub-salarie") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Mockup Hub Salarié">
<span className="inline-flex items-center gap-2">
<Users className="w-4 h-4" aria-hidden />
<span>Hub Salarié</span>
</span>
</Link>
</div>
{/* Espace Paie */}
<div className="mt-3">
<div className="px-3 py-1.5 text-xs font-medium text-slate-500 uppercase tracking-wider">
Espace Paie
</div>
<Link href="/staff/offres-promo" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/staff/offres-promo") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Offres promo">
<span className="inline-flex items-center gap-2">
<Megaphone className="w-4 h-4" aria-hidden />
<span>Offres promo</span>
</span>
</Link>
<div className="px-3 py-2">
<MaintenanceButton isStaff={true} />
</div>
</div>
</div>
)}
{/* Menu Gestion Paie */}
<nav className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-2">
<div className="px-3 py-2 text-sm font-semibold text-slate-700 border-b">
Gestion paie
</div>
<div className="mt-2">
{items.map((it) => {
const active = isActivePath(pathname, it.href);
const Icon = it.icon as any;
// Badge spécial pour les signatures électroniques
const showBadge = it.href === "/signatures-electroniques" && pendingCount > 0;
return (
<Link
key={it.href}
href={it.href}
onClick={() => onNavigate && onNavigate()}
className={`flex items-center justify-between px-3 py-2 rounded-xl text-sm transition truncate ${
active
? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold"
: "hover:bg-slate-50"
}`}
title={it.label}
>
<span className="inline-flex items-center gap-2">
{Icon ? <Icon className="w-4 h-4" aria-hidden /> : null}
<span>{it.label}</span>
</span>
{/* Badge rouge avec animation pour les signatures en attente */}
{showBadge && (
<span
className="flex items-center justify-center min-w-[20px] h-5 px-1.5 rounded-full bg-red-500 text-white text-xs font-semibold animate-pulse shadow-sm"
title={`${pendingCount} signature${pendingCount > 1 ? 's' : ''} en attente`}
>
{pendingCount}
</span>
)}
</Link>
);
})}
</div>
</nav>
{/* Menu Gestion Compte */}
<div className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-2">
<div className="px-3 py-2 text-sm font-semibold text-slate-700 border-b">
Gestion compte
</div>
<div className="mt-2">
{isDemoMode ? (
<DisabledMenuItem
icon={Receipt}
label="Facturation"
tooltipMessage="Désactivé en mode démo"
/>
) : (
<Link href="/facturation" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/facturation") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Facturation">
<span className="inline-flex items-center gap-2">
<Receipt className="w-4 h-4" aria-hidden />
<span>Facturation</span>
</span>
</Link>
)}
{isDemoMode ? (
<DisabledMenuItem
icon={Building2}
label="Vos informations"
tooltipMessage="Désactivé en mode démo"
/>
) : (
<Link href="/informations" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/informations") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Vos informations">
<span className="inline-flex items-center gap-2">
<Building2 className="w-4 h-4" aria-hidden />
<span>Vos informations</span>
</span>
</Link>
)}
{isDemoMode ? (
<DisabledMenuItem
icon={FolderOpen}
label="Vos documents"
tooltipMessage="Désactivé en mode démo"
/>
) : (
<Link href="/vos-documents" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/vos-documents") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Vos documents">
<span className="inline-flex items-center gap-2">
<FolderOpen className="w-4 h-4" aria-hidden />
<span>Vos documents</span>
</span>
</Link>
)}
</div>
</div>
{/* Menu Simulation & CCN */}
<div className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-2">
<div className="px-3 py-2 text-sm font-semibold text-slate-700 border-b">
Simulation & CCN
</div>
<div className="mt-2">
<Link href="/minima-ccn" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/minima-ccn") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Minima CCN">
<span className="inline-flex items-center gap-2">
<Scale className="w-4 h-4" aria-hidden />
<span>Minima CCN</span>
</span>
</Link>
<Link href="/simulateur" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
isActivePath(pathname, "/simulateur") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
}`} title="Simulateur de paie">
<span className="inline-flex items-center gap-2">
<Calculator className="w-4 h-4" aria-hidden />
<span>Simulateur de paie</span>
</span>
</Link>
</div>
</div>
<div className="mt-3 mx-auto max-w-[280px] rounded-2xl border bg-white p-2 text-xs">
<div className="text-[11px] text-slate-400 flex flex-wrap items-center justify-center gap-1">
<span>
© 2021<span suppressHydrationWarning>{new Date().getFullYear()}</span> Odentas Media SAS
</span>
<span className="inline-flex items-center gap-1 whitespace-nowrap" title="Hébergement France">
<span role="img" aria-label="Drapeau français">🇫🇷</span>
<span>Données hébergées en France</span>
</span>
<a href="/mentions-legales" className="underline hover:text-slate-600 whitespace-nowrap">Mentions légales</a>
<span>•</span>
<a href="/politique-confidentialite" className="underline hover:text-slate-600 whitespace-nowrap">Confidentialité</a>
</div>
<div className="text-[11px] text-slate-400 mt-2 text-center">
v2.0.0
</div>
</div>
</aside>
);
}