Ruolo supervisor

This commit is contained in:
2026-03-27 14:43:42 +01:00
parent ab6db28449
commit d7ae840ac6
9 changed files with 166 additions and 81 deletions
+35 -1
View File
@@ -75,7 +75,7 @@ export function Sidebar() {
/** 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 { user, isAdmin, isSuperAdmin, isSupervisor, logout } = useAuth()
const unreadCount = useInboxStore((s) => s.unreadCount)
// Le caselle PEC vengono caricate qui e condivise via React Query cache
@@ -416,6 +416,40 @@ export function Sidebar() {
</div>
</div>
{/* ── Sezione Supervisione visibile solo ai supervisor ── */}
{isSupervisor && (
<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-cyan-400 uppercase tracking-wider">
Supervisione
</p>
</>
)}
{collapsed && <div className="border-t border-gray-700 mx-3 mb-2" />}
<div className="space-y-0.5 px-2">
<NavLink
to="/mailboxes"
className={({ isActive }) =>
cn(
'flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors',
isActive
? 'bg-cyan-700 text-white'
: 'text-cyan-300 hover:bg-cyan-900/40 hover:text-white',
collapsed && 'justify-center px-2',
)
}
title={collapsed ? 'Caselle PEC' : undefined}
>
<MailCheck className="h-5 w-5 flex-shrink-0" />
{!collapsed && <span>Caselle PEC</span>}
</NavLink>
</div>
</div>
)}
{/* ── Sezione Amministrazione ── */}
{isAdmin && (
<div>
+12
View File
@@ -2,6 +2,14 @@ import { useAuthStore } from '@/store/auth.store'
/**
* Hook helper per accedere all'utente corrente e ai permessi.
*
* Gerarchia ruoli:
* super_admin > admin > supervisor > operator > readonly
*
* Supervisor:
* - Lettura implicita su tutte le caselle del tenant
* - Invio solo se ha permesso esplicito can_send sulla casella
* - Non puo' gestire caselle, utenti, permessi o impostazioni
*/
export function useAuth() {
const user = useAuthStore((s) => s.user)
@@ -11,6 +19,8 @@ export function useAuth() {
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin'
const isSuperAdmin = user?.role === 'super_admin'
const isSupervisor = user?.role === 'supervisor'
const isSupervisorOrAdmin = isAdmin || isSupervisor
const canSend = user?.role !== 'readonly'
const canManage = isAdmin
@@ -20,6 +30,8 @@ export function useAuth() {
isLoading,
isAdmin,
isSuperAdmin,
isSupervisor,
isSupervisorOrAdmin,
canSend,
canManage,
logout,
+59 -51
View File
@@ -27,6 +27,7 @@ import { mailboxesApi } from '@/api/mailboxes.api'
import { getErrorMessage } from '@/api/client'
import { formatDate, MAILBOX_STATUS_LABELS } from '@/lib/utils'
import { cn } from '@/lib/utils'
import { useAuth } from '@/hooks/useAuth'
import type { MailboxCreateRequest, MailboxResponse } from '@/types/api.types'
const STATUS_COLORS = {
@@ -45,6 +46,7 @@ const STATUS_ICONS = {
export function MailboxesPage() {
const queryClient = useQueryClient()
const { isAdmin } = useAuth()
const [showCreateDialog, setShowCreateDialog] = useState(false)
const [editingMailbox, setEditingMailbox] = useState<MailboxResponse | null>(null)
const [testingId, setTestingId] = useState<string | null>(null)
@@ -112,10 +114,12 @@ export function MailboxesPage() {
<h1 className="text-xl font-semibold">Caselle PEC</h1>
<span className="text-sm text-muted-foreground">({mailboxes.length})</span>
</div>
<Button onClick={() => setShowCreateDialog(true)}>
<Plus className="h-4 w-4 mr-2" />
Aggiungi casella
</Button>
{isAdmin && (
<Button onClick={() => setShowCreateDialog(true)}>
<Plus className="h-4 w-4 mr-2" />
Aggiungi casella
</Button>
)}
</div>
{/* Contenuto */}
@@ -129,12 +133,14 @@ export function MailboxesPage() {
<MailCheck className="h-12 w-12 text-muted-foreground/30 mb-3" />
<p className="text-muted-foreground font-medium">Nessuna casella PEC configurata</p>
<p className="text-sm text-muted-foreground/70 mt-1">
Aggiungi una casella per iniziare a gestire le PEC
{isAdmin ? 'Aggiungi una casella per iniziare a gestire le PEC' : 'Nessuna casella disponibile'}
</p>
<Button className="mt-4" onClick={() => setShowCreateDialog(true)}>
<Plus className="h-4 w-4 mr-2" />
Aggiungi casella
</Button>
{isAdmin && (
<Button className="mt-4" onClick={() => setShowCreateDialog(true)}>
<Plus className="h-4 w-4 mr-2" />
Aggiungi casella
</Button>
)}
</div>
) : (
<div className="grid gap-4">
@@ -186,51 +192,53 @@ export function MailboxesPage() {
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
{/* Test connessione */}
<Button
variant="outline"
size="sm"
onClick={() => handleTest(mailbox)}
isLoading={testingId === mailbox.id}
title="Testa connessione IMAP"
>
<TestTube className="h-4 w-4 mr-1" />
Test
</Button>
{isAdmin && (
<div className="flex items-center gap-2 flex-shrink-0">
{/* Test connessione */}
<Button
variant="outline"
size="sm"
onClick={() => handleTest(mailbox)}
isLoading={testingId === mailbox.id}
title="Testa connessione IMAP"
>
<TestTube className="h-4 w-4 mr-1" />
Test
</Button>
{/* Forza sincronizzazione */}
<Button
variant="outline"
size="sm"
onClick={() => handleForceSync(mailbox)}
isLoading={syncingId === mailbox.id}
title="Forza sincronizzazione IMAP immediata"
disabled={mailbox.status === 'deleted'}
>
<RefreshCw className="h-4 w-4 mr-1" />
Sync
</Button>
{/* Forza sincronizzazione */}
<Button
variant="outline"
size="sm"
onClick={() => handleForceSync(mailbox)}
isLoading={syncingId === mailbox.id}
title="Forza sincronizzazione IMAP immediata"
disabled={mailbox.status === 'deleted'}
>
<RefreshCw className="h-4 w-4 mr-1" />
Sync
</Button>
{/* Modifica */}
<Button
variant="outline"
size="sm"
onClick={() => setEditingMailbox(mailbox)}
>
<Edit className="h-4 w-4" />
</Button>
{/* Modifica */}
<Button
variant="outline"
size="sm"
onClick={() => setEditingMailbox(mailbox)}
>
<Edit className="h-4 w-4" />
</Button>
{/* Elimina */}
<Button
variant="outline"
size="sm"
onClick={() => handleDelete(mailbox)}
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
{/* Elimina */}
<Button
variant="outline"
size="sm"
onClick={() => handleDelete(mailbox)}
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</div>
)
})}