This commit is contained in:
2026-03-18 20:54:43 +01:00
parent b3c8b77f12
commit 9fe656b34c
8058 changed files with 912898 additions and 23 deletions
+197
View File
@@ -0,0 +1,197 @@
import { NavLink } from 'react-router-dom'
import {
Inbox,
Send,
MailCheck,
Users,
Settings,
LogOut,
ChevronLeft,
ChevronRight,
Shield,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useAuth } from '@/hooks/useAuth'
import { useInboxStore } from '@/store/inbox.store'
import { useState } from 'react'
import toast from 'react-hot-toast'
interface NavItem {
to: string
label: string
icon: React.ElementType
adminOnly?: boolean
badge?: number
}
const NAV_ITEMS: NavItem[] = [
{ to: '/inbox', label: 'Posta in Arrivo', icon: Inbox },
{ to: '/sent', label: 'Posta Inviata', icon: Send },
{ to: '/compose', label: 'Nuova PEC', icon: MailCheck },
]
const ADMIN_NAV_ITEMS: NavItem[] = [
{ to: '/mailboxes', label: 'Caselle PEC', icon: MailCheck, adminOnly: true },
{ to: '/users', label: 'Utenti', icon: Users, adminOnly: true },
{ to: '/permissions', label: 'Permessi', icon: Shield, adminOnly: true },
]
export function Sidebar() {
const [collapsed, setCollapsed] = useState(false)
const { user, isAdmin, logout } = useAuth()
const unreadCount = useInboxStore((s) => s.unreadCount)
const handleLogout = async () => {
try {
await logout()
toast.success('Disconnessione effettuata')
} catch {
toast.error('Errore durante il logout')
}
}
return (
<aside
className={cn(
'flex flex-col h-screen bg-gray-900 text-white transition-all duration-300',
collapsed ? 'w-16' : 'w-64',
)}
>
{/* Logo + toggle */}
<div className="flex items-center justify-between p-4 border-b border-gray-700">
{!collapsed && (
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-lg bg-blue-500 flex items-center justify-center text-white font-bold text-sm">
PF
</div>
<span className="font-bold text-lg">PecFlow</span>
</div>
)}
{collapsed && (
<div className="mx-auto h-8 w-8 rounded-lg bg-blue-500 flex items-center justify-center text-white font-bold text-sm">
PF
</div>
)}
<button
onClick={() => setCollapsed(!collapsed)}
className={cn(
'p-1 rounded hover:bg-gray-700 transition-colors text-gray-400',
collapsed && 'mx-auto mt-0',
)}
>
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
</button>
</div>
{/* Navigazione principale */}
<nav className="flex-1 overflow-y-auto py-4">
<div className="space-y-1 px-2">
{NAV_ITEMS.map((item) => (
<SidebarLink
key={item.to}
item={item}
collapsed={collapsed}
badge={item.to === '/inbox' ? unreadCount : undefined}
/>
))}
</div>
{/* Sezione Admin */}
{isAdmin && (
<>
<div className={cn('mt-6 px-4 mb-2', collapsed && 'hidden')}>
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
Amministrazione
</p>
</div>
{!collapsed && <div className="border-t border-gray-700 mx-4 mb-2" />}
<div className="space-y-1 px-2">
{ADMIN_NAV_ITEMS.map((item) => (
<SidebarLink key={item.to} item={item} collapsed={collapsed} />
))}
</div>
</>
)}
</nav>
{/* Profilo utente + logout */}
<div className="border-t border-gray-700 p-3">
{!collapsed ? (
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="h-8 w-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-medium flex-shrink-0">
{user?.full_name?.[0]?.toUpperCase() || 'U'}
</div>
<div className="min-w-0">
<p className="text-sm font-medium truncate">{user?.full_name}</p>
<p className="text-xs text-gray-400 truncate">{user?.email}</p>
</div>
</div>
<div className="flex gap-2">
<NavLink
to="/settings"
className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded text-xs text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
>
<Settings className="h-3.5 w-3.5" />
Impostazioni
</NavLink>
<button
onClick={handleLogout}
className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded text-xs text-gray-400 hover:text-red-400 hover:bg-gray-700 transition-colors"
>
<LogOut className="h-3.5 w-3.5" />
Esci
</button>
</div>
</div>
) : (
<button
onClick={handleLogout}
className="w-full flex justify-center p-2 rounded text-gray-400 hover:text-red-400 hover:bg-gray-700 transition-colors"
title="Esci"
>
<LogOut className="h-4 w-4" />
</button>
)}
</div>
</aside>
)
}
interface SidebarLinkProps {
item: NavItem
collapsed: boolean
badge?: number
}
function SidebarLink({ item, collapsed, badge }: SidebarLinkProps) {
const Icon = item.icon
return (
<NavLink
to={item.to}
className={({ isActive }) =>
cn(
'flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
isActive
? 'bg-blue-600 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
collapsed && 'justify-center px-2',
)
}
title={collapsed ? item.label : undefined}
>
<Icon className="h-5 w-5 flex-shrink-0" />
{!collapsed && (
<>
<span className="flex-1">{item.label}</span>
{badge !== undefined && badge > 0 && (
<span className="inline-flex items-center justify-center h-5 w-5 rounded-full bg-blue-500 text-white text-xs font-bold">
{badge > 99 ? '99+' : badge}
</span>
)}
</>
)}
</NavLink>
)
}