mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
Ruolo supervisor
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user