763 lines
35 KiB
TypeScript
763 lines
35 KiB
TypeScript
"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>
|
||
);
|
||
}
|