mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
vbox funzionanti
This commit is contained in:
@@ -1,3 +1,37 @@
|
||||
/**
|
||||
* Sidebar – navigazione principale di PecFlow.
|
||||
*
|
||||
* Struttura visiva (sidebar espansa):
|
||||
* ┌────────────────────────────────┐
|
||||
* │ [PF] PecFlow [◀] │
|
||||
* ├────────────────────────────────┤
|
||||
* │ 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,
|
||||
@@ -9,38 +43,80 @@ import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Shield,
|
||||
ChevronDown,
|
||||
Filter,
|
||||
Bell,
|
||||
Star,
|
||||
Archive,
|
||||
} 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'
|
||||
|
||||
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 },
|
||||
]
|
||||
// ─── 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, 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()
|
||||
@@ -53,15 +129,15 @@ export function Sidebar() {
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
'flex flex-col h-screen bg-gray-900 text-white transition-all duration-300',
|
||||
'flex flex-col h-screen bg-gray-900 text-white transition-all duration-300 flex-shrink-0',
|
||||
collapsed ? 'w-16' : 'w-64',
|
||||
)}
|
||||
>
|
||||
{/* Logo + toggle */}
|
||||
{/* ── 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">
|
||||
<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">PecFlow</span>
|
||||
@@ -73,54 +149,253 @@ export function Sidebar() {
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
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" />}
|
||||
{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}
|
||||
/>
|
||||
))}
|
||||
{/* ── 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 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} />
|
||||
{/* ── 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>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* Profilo utente + logout */}
|
||||
{/* ── 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'}
|
||||
{user?.full_name?.[0]?.toUpperCase() ?? 'U'}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium truncate">{user?.full_name}</p>
|
||||
@@ -158,40 +433,295 @@ export function Sidebar() {
|
||||
)
|
||||
}
|
||||
|
||||
interface SidebarLinkProps {
|
||||
item: NavItem
|
||||
// ─── Voce di casella PEC nel sidebar ─────────────────────────────────────────
|
||||
|
||||
interface MailboxNavItemProps {
|
||||
mailbox: MailboxResponse
|
||||
collapsed: boolean
|
||||
badge?: number
|
||||
isExpanded: boolean
|
||||
onToggle: () => void
|
||||
}
|
||||
|
||||
function SidebarLink({ item, collapsed, badge }: SidebarLinkProps) {
|
||||
const Icon = item.icon
|
||||
/** 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 (
|
||||
<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>
|
||||
<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>
|
||||
)}
|
||||
</NavLink>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user