mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
763 lines
27 KiB
TypeScript
763 lines
27 KiB
TypeScript
/**
|
||
* Sidebar – navigazione principale di PEChub.
|
||
*
|
||
* Struttura visiva (sidebar espansa):
|
||
* ┌────────────────────────────────┐
|
||
* │ [PF] PEChub [◀] │
|
||
* ├────────────────────────────────┤
|
||
* │ TUTTE LE CASELLE │
|
||
* │ 📥 Posta in Arrivo [badge] │
|
||
* │ 📤 Posta Inviata │
|
||
* │ ⭐ Preferiti │
|
||
* │ 📦 Archiviati │
|
||
* ├────────────────────────────────┤
|
||
* │ LE TUE CASELLE │
|
||
* │ ● gmgspa@pec.it [▼] │
|
||
* │ ├ 📥 In Arrivo │
|
||
* │ ├ 📤 Inviata │
|
||
* │ ├ ⭐ Preferiti │
|
||
* │ └ 📦 Archiviati │
|
||
* ├────────────────────────────────┤
|
||
* │ ✉ Nuova PEC │
|
||
* ├────────────────────────────────┤
|
||
* │ AMMINISTRAZIONE │
|
||
* │ 📬 Caselle PEC │
|
||
* │ 👥 Utenti │
|
||
* │ 🛡 Permessi │
|
||
* ├────────────────────────────────┤
|
||
* │ [avatar] Nome utente │
|
||
* │ Impostazioni | Esci │
|
||
* └────────────────────────────────┘
|
||
*
|
||
* Quando collassata (w-16) mostra solo icone/avatar con tooltip.
|
||
*/
|
||
|
||
import { NavLink } from 'react-router-dom'
|
||
import {
|
||
Inbox,
|
||
Send,
|
||
MailCheck,
|
||
Users,
|
||
Settings,
|
||
LogOut,
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
Shield,
|
||
ChevronDown,
|
||
Filter,
|
||
Bell,
|
||
Star,
|
||
Archive,
|
||
Building2,
|
||
} 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'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import { mailboxesApi } from '@/api/mailboxes.api'
|
||
import { virtualBoxesApi } from '@/api/virtual_boxes.api'
|
||
import type { MailboxResponse, VirtualBoxResponse } from '@/types/api.types'
|
||
|
||
// ─── Sidebar principale ───────────────────────────────────────────────────────
|
||
|
||
export function Sidebar() {
|
||
const [collapsed, setCollapsed] = useState(false)
|
||
/**
|
||
* Set degli ID casella che l'utente ha esplicitamente chiuso.
|
||
* Tutte le caselle sono espanse per default (nessuno nell'insieme).
|
||
*/
|
||
const [collapsedMailboxes, setCollapsedMailboxes] = useState<Set<string>>(new Set())
|
||
/** Set degli ID virtual box che l'utente ha esplicitamente chiuso. */
|
||
const [collapsedVboxes, setCollapsedVboxes] = useState<Set<string>>(new Set())
|
||
|
||
const { user, isAdmin, isSuperAdmin, logout } = useAuth()
|
||
const unreadCount = useInboxStore((s) => s.unreadCount)
|
||
|
||
// Le caselle PEC vengono caricate qui e condivise via React Query cache
|
||
const { data: mailboxesData } = useQuery({
|
||
queryKey: ['mailboxes'],
|
||
queryFn: () => mailboxesApi.list(),
|
||
staleTime: 5 * 60 * 1000,
|
||
})
|
||
const mailboxes = mailboxesData?.items ?? []
|
||
|
||
// Virtual Box assegnate all'utente corrente
|
||
const { data: myVboxes = [] } = useQuery({
|
||
queryKey: ['virtual-boxes', 'my'],
|
||
queryFn: () => virtualBoxesApi.myVirtualBoxes(),
|
||
staleTime: 5 * 60 * 1000,
|
||
})
|
||
|
||
const isMailboxExpanded = (id: string) => !collapsedMailboxes.has(id)
|
||
|
||
const toggleMailbox = (id: string) => {
|
||
setCollapsedMailboxes((prev) => {
|
||
const next = new Set(prev)
|
||
if (next.has(id)) {
|
||
next.delete(id)
|
||
} else {
|
||
next.add(id)
|
||
}
|
||
return next
|
||
})
|
||
}
|
||
|
||
const isVboxExpanded = (id: string) => !collapsedVboxes.has(id)
|
||
|
||
const toggleVbox = (id: string) => {
|
||
setCollapsedVboxes((prev) => {
|
||
const next = new Set(prev)
|
||
if (next.has(id)) {
|
||
next.delete(id)
|
||
} else {
|
||
next.add(id)
|
||
}
|
||
return next
|
||
})
|
||
}
|
||
|
||
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 flex-shrink-0',
|
||
collapsed ? 'w-16' : 'w-64',
|
||
)}
|
||
>
|
||
{/* ── Logo + pulsante collassa ── */}
|
||
<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 flex-shrink-0">
|
||
PF
|
||
</div>
|
||
<span className="font-bold text-lg">PEChub</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((c) => !c)}
|
||
className={cn(
|
||
'p-1 rounded hover:bg-gray-700 transition-colors text-gray-400',
|
||
collapsed && 'mx-auto mt-0',
|
||
)}
|
||
title={collapsed ? 'Espandi' : 'Comprimi'}
|
||
>
|
||
{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 space-y-4">
|
||
|
||
{/* ── Sezione: Tutte le caselle ── */}
|
||
<div>
|
||
{!collapsed && (
|
||
<p className="px-4 mb-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||
Tutte le caselle
|
||
</p>
|
||
)}
|
||
<div className="space-y-0.5 px-2">
|
||
{/* Posta in Arrivo globale */}
|
||
<NavLink
|
||
to="/inbox"
|
||
end
|
||
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 ? 'Posta in Arrivo (tutte le caselle)' : undefined}
|
||
>
|
||
<Inbox className="h-4 w-4 flex-shrink-0" />
|
||
{!collapsed && (
|
||
<>
|
||
<span className="flex-1">Posta in Arrivo</span>
|
||
{unreadCount > 0 && (
|
||
<span className="inline-flex items-center justify-center h-5 min-w-[20px] px-1 rounded-full bg-blue-500 text-white text-xs font-bold">
|
||
{unreadCount > 99 ? '99+' : unreadCount}
|
||
</span>
|
||
)}
|
||
</>
|
||
)}
|
||
{/* Badge compatto in modalità collassata */}
|
||
{collapsed && unreadCount > 0 && (
|
||
<span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-red-500 text-white text-[10px] font-bold flex items-center justify-center">
|
||
{unreadCount > 9 ? '9+' : unreadCount}
|
||
</span>
|
||
)}
|
||
</NavLink>
|
||
|
||
{/* Posta Inviata globale */}
|
||
<NavLink
|
||
to="/sent"
|
||
end
|
||
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 ? 'Posta Inviata (tutte le caselle)' : undefined}
|
||
>
|
||
<Send className="h-4 w-4 flex-shrink-0" />
|
||
{!collapsed && <span>Posta Inviata</span>}
|
||
</NavLink>
|
||
|
||
{/* Preferiti globali */}
|
||
<NavLink
|
||
to="/starred"
|
||
end
|
||
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 ? 'Preferiti (tutte le caselle)' : undefined}
|
||
>
|
||
<Star className="h-4 w-4 flex-shrink-0" />
|
||
{!collapsed && <span>Preferiti</span>}
|
||
</NavLink>
|
||
|
||
{/* Archiviati globali */}
|
||
<NavLink
|
||
to="/archived"
|
||
end
|
||
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 ? 'Archiviati (tutte le caselle)' : undefined}
|
||
>
|
||
<Archive className="h-4 w-4 flex-shrink-0" />
|
||
{!collapsed && <span>Archiviati</span>}
|
||
</NavLink>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Sezione: Caselle individuali ── */}
|
||
{mailboxes.length > 0 && (
|
||
<div>
|
||
{!collapsed && (
|
||
<>
|
||
<div className="border-t border-gray-700 mx-4 mb-3" />
|
||
<p className="px-4 mb-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||
Le tue caselle
|
||
</p>
|
||
</>
|
||
)}
|
||
{collapsed && <div className="border-t border-gray-700 mx-3 mb-2" />}
|
||
|
||
<div className="space-y-0.5 px-2">
|
||
{mailboxes.map((mailbox) => (
|
||
<MailboxNavItem
|
||
key={mailbox.id}
|
||
mailbox={mailbox}
|
||
collapsed={collapsed}
|
||
isExpanded={isMailboxExpanded(mailbox.id)}
|
||
onToggle={() => toggleMailbox(mailbox.id)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ── Sezione: Le tue Virtual Box ── */}
|
||
{myVboxes.length > 0 && (
|
||
<div>
|
||
{!collapsed && (
|
||
<>
|
||
<div className="border-t border-gray-700 mx-4 mb-3" />
|
||
<p className="px-4 mb-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||
Le tue virtual box
|
||
</p>
|
||
</>
|
||
)}
|
||
{collapsed && <div className="border-t border-gray-700 mx-3 mb-2" />}
|
||
|
||
<div className="space-y-0.5 px-2">
|
||
{myVboxes.map((vbox) => (
|
||
<VirtualBoxNavItem
|
||
key={vbox.id}
|
||
vbox={vbox}
|
||
collapsed={collapsed}
|
||
isExpanded={isVboxExpanded(vbox.id)}
|
||
onToggle={() => toggleVbox(vbox.id)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ── Nuova PEC ── */}
|
||
<div>
|
||
<div className="border-t border-gray-700 mx-4 mb-3" />
|
||
<div className="px-2">
|
||
<NavLink
|
||
to="/compose"
|
||
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 ? 'Nuova PEC' : undefined}
|
||
>
|
||
<MailCheck className="h-5 w-5 flex-shrink-0" />
|
||
{!collapsed && <span>Nuova PEC</span>}
|
||
</NavLink>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Sezione Amministrazione ── */}
|
||
{isAdmin && (
|
||
<div>
|
||
{!collapsed && (
|
||
<>
|
||
<div className="border-t border-gray-700 mx-4 mb-3" />
|
||
<p className="px-4 mb-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||
Amministrazione
|
||
</p>
|
||
</>
|
||
)}
|
||
{collapsed && <div className="border-t border-gray-700 mx-3 mb-2" />}
|
||
|
||
<div className="space-y-0.5 px-2">
|
||
{([
|
||
{ to: '/mailboxes', label: 'Caselle PEC', icon: MailCheck },
|
||
{ to: '/users', label: 'Utenti', icon: Users },
|
||
{ to: '/permissions', label: 'Permessi', icon: Shield },
|
||
{ to: '/virtual-boxes', label: 'Virtual Box', icon: Filter },
|
||
{ to: '/notifications', label: 'Notifiche', icon: Bell },
|
||
] as const).map((item) => (
|
||
<NavLink
|
||
key={item.to}
|
||
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}
|
||
>
|
||
<item.icon className="h-5 w-5 flex-shrink-0" />
|
||
{!collapsed && <span>{item.label}</span>}
|
||
</NavLink>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ── Sezione Super Admin – visibile solo ai super_admin ── */}
|
||
{isSuperAdmin && (
|
||
<div>
|
||
{!collapsed && (
|
||
<>
|
||
<div className="border-t border-purple-900/50 mx-4 mb-3" />
|
||
<p className="px-4 mb-1.5 text-xs font-semibold text-purple-400 uppercase tracking-wider">
|
||
Super Admin
|
||
</p>
|
||
</>
|
||
)}
|
||
{collapsed && <div className="border-t border-purple-900/50 mx-3 mb-2" />}
|
||
|
||
<div className="space-y-0.5 px-2">
|
||
<NavLink
|
||
to="/multitenant"
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
|
||
isActive
|
||
? 'bg-purple-700 text-white'
|
||
: 'text-purple-300 hover:bg-purple-900/40 hover:text-white',
|
||
collapsed && 'justify-center px-2',
|
||
)
|
||
}
|
||
title={collapsed ? 'Multi-Tenant' : undefined}
|
||
>
|
||
<Building2 className="h-5 w-5 flex-shrink-0" />
|
||
{!collapsed && <span>Multi-Tenant</span>}
|
||
</NavLink>
|
||
</div>
|
||
</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>
|
||
)
|
||
}
|
||
|
||
// ─── Voce di casella PEC nel sidebar ─────────────────────────────────────────
|
||
|
||
interface MailboxNavItemProps {
|
||
mailbox: MailboxResponse
|
||
collapsed: boolean
|
||
isExpanded: boolean
|
||
onToggle: () => void
|
||
}
|
||
|
||
/** Colore del pallino di stato casella */
|
||
function statusDot(status: MailboxResponse['status']): string {
|
||
switch (status) {
|
||
case 'active':
|
||
return 'bg-green-500'
|
||
case 'paused':
|
||
return 'bg-yellow-400'
|
||
case 'error':
|
||
return 'bg-red-500'
|
||
case 'deleted':
|
||
return 'bg-gray-600'
|
||
default:
|
||
return 'bg-gray-500'
|
||
}
|
||
}
|
||
|
||
function MailboxNavItem({ mailbox, collapsed, isExpanded, onToggle }: MailboxNavItemProps) {
|
||
const displayName = mailbox.display_name || mailbox.email_address
|
||
const initial = displayName[0]?.toUpperCase() ?? '?'
|
||
const dotClass = statusDot(mailbox.status)
|
||
|
||
/* ── Modalità compressa: solo avatar/iniziale → link diretto all'inbox ── */
|
||
if (collapsed) {
|
||
return (
|
||
<NavLink
|
||
to={`/mailbox/${mailbox.id}/inbox`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'relative flex justify-center items-center w-full px-2 py-2 rounded-lg transition-colors',
|
||
isActive
|
||
? 'bg-blue-600 text-white'
|
||
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
title={displayName}
|
||
>
|
||
<div className="relative">
|
||
<div className="h-6 w-6 rounded-full bg-gray-600 flex items-center justify-center text-xs font-semibold">
|
||
{initial}
|
||
</div>
|
||
<span
|
||
className={cn(
|
||
'absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full border border-gray-900',
|
||
dotClass,
|
||
)}
|
||
/>
|
||
</div>
|
||
</NavLink>
|
||
)
|
||
}
|
||
|
||
/* ── Modalità espansa: sezione espandibile con In Arrivo, Inviata, Preferiti, Archiviati ── */
|
||
return (
|
||
<div>
|
||
{/* Intestazione casella (espandi/comprimi) */}
|
||
<button
|
||
onClick={onToggle}
|
||
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white transition-colors group"
|
||
>
|
||
{/* Avatar + status dot */}
|
||
<div className="relative flex-shrink-0">
|
||
<div className="h-5 w-5 rounded-full bg-gray-600 flex items-center justify-center text-xs font-semibold text-white">
|
||
{initial}
|
||
</div>
|
||
<span
|
||
className={cn(
|
||
'absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full border border-gray-900',
|
||
dotClass,
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* Nome / email */}
|
||
<span className="flex-1 text-left truncate text-xs leading-tight">
|
||
{displayName}
|
||
</span>
|
||
|
||
{/* Chevron espandi/comprimi */}
|
||
<ChevronDown
|
||
className={cn(
|
||
'h-3.5 w-3.5 text-gray-500 transition-transform flex-shrink-0',
|
||
isExpanded && 'rotate-180',
|
||
)}
|
||
/>
|
||
</button>
|
||
|
||
{/* Sub-voci: In Arrivo, Inviata, Preferiti, Archiviati */}
|
||
{isExpanded && (
|
||
<div className="ml-4 mt-0.5 mb-1 space-y-0.5 border-l border-gray-700 pl-3">
|
||
<NavLink
|
||
to={`/mailbox/${mailbox.id}/inbox`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-blue-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Inbox className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>In Arrivo</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/mailbox/${mailbox.id}/sent`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-blue-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Send className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Inviata</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/mailbox/${mailbox.id}/starred`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-blue-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Star className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Preferiti</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/mailbox/${mailbox.id}/archived`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-blue-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Archive className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Archiviati</span>
|
||
</NavLink>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ─── Voce di Virtual Box nel sidebar ─────────────────────────────────────────
|
||
|
||
interface VirtualBoxNavItemProps {
|
||
vbox: VirtualBoxResponse
|
||
collapsed: boolean
|
||
isExpanded: boolean
|
||
onToggle: () => void
|
||
}
|
||
|
||
function VirtualBoxNavItem({ vbox, collapsed, isExpanded, onToggle }: VirtualBoxNavItemProps) {
|
||
const displayName = vbox.label || vbox.name
|
||
const initial = displayName[0]?.toUpperCase() ?? '?'
|
||
|
||
/* ── Modalità compressa: solo icona filtro → link diretto all'inbox ── */
|
||
if (collapsed) {
|
||
return (
|
||
<NavLink
|
||
to={`/virtual-box/${vbox.id}/inbox`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'relative flex justify-center items-center w-full px-2 py-2 rounded-lg transition-colors',
|
||
isActive
|
||
? 'bg-purple-600 text-white'
|
||
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
title={displayName}
|
||
>
|
||
<div className="h-6 w-6 rounded-full bg-purple-800 flex items-center justify-center text-xs font-semibold">
|
||
{initial}
|
||
</div>
|
||
</NavLink>
|
||
)
|
||
}
|
||
|
||
/* ── Modalità espansa: sezione espandibile ── */
|
||
return (
|
||
<div>
|
||
{/* Intestazione VBox (espandi/comprimi) */}
|
||
<button
|
||
onClick={onToggle}
|
||
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white transition-colors"
|
||
>
|
||
{/* Icona VBox */}
|
||
<div className="h-5 w-5 rounded-full bg-purple-800 flex items-center justify-center text-xs font-semibold text-white flex-shrink-0">
|
||
{initial}
|
||
</div>
|
||
|
||
{/* Nome */}
|
||
<span className="flex-1 text-left truncate text-xs leading-tight">
|
||
{displayName}
|
||
</span>
|
||
|
||
{/* Chevron espandi/comprimi */}
|
||
<ChevronDown
|
||
className={cn(
|
||
'h-3.5 w-3.5 text-gray-500 transition-transform flex-shrink-0',
|
||
isExpanded && 'rotate-180',
|
||
)}
|
||
/>
|
||
</button>
|
||
|
||
{/* Sub-voci: In Arrivo, Inviata, Preferiti, Archiviati */}
|
||
{isExpanded && (
|
||
<div className="ml-4 mt-0.5 mb-1 space-y-0.5 border-l border-purple-800/40 pl-3">
|
||
<NavLink
|
||
to={`/virtual-box/${vbox.id}/inbox`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-purple-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Inbox className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>In Arrivo</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/virtual-box/${vbox.id}/sent`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-purple-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Send className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Inviata</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/virtual-box/${vbox.id}/starred`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-purple-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Star className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Preferiti</span>
|
||
</NavLink>
|
||
|
||
<NavLink
|
||
to={`/virtual-box/${vbox.id}/archived`}
|
||
className={({ isActive }) =>
|
||
cn(
|
||
'flex items-center gap-2 px-2 py-1.5 rounded-md text-xs font-medium transition-colors',
|
||
isActive
|
||
? 'bg-purple-600 text-white'
|
||
: 'text-gray-400 hover:bg-gray-700 hover:text-white',
|
||
)
|
||
}
|
||
>
|
||
<Archive className="h-3.5 w-3.5 flex-shrink-0" />
|
||
<span>Archiviati</span>
|
||
</NavLink>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|