Fascicoli+Tassonomia+permessi
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
import { useState } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
ArrowLeft,
|
||||
FolderOpen,
|
||||
FolderCheck,
|
||||
FolderArchive,
|
||||
MessageSquare,
|
||||
Calendar,
|
||||
Pencil,
|
||||
Trash2,
|
||||
Plus,
|
||||
X,
|
||||
Search,
|
||||
Inbox,
|
||||
Send,
|
||||
ExternalLink,
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
} from 'lucide-react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Label } from '@/components/ui/Label'
|
||||
import {
|
||||
fascicoliApi,
|
||||
type FascicoloResponse,
|
||||
type FascicoloMessageItem,
|
||||
type FascicoloUpdate,
|
||||
} from '@/api/fascicoli.api'
|
||||
import { messagesApi } from '@/api/messages.api'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
import { getErrorMessage } from '@/api/client'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { PecStateBadge } from '@/components/PecBadge/PecBadge'
|
||||
|
||||
// ─── Badge stato ──────────────────────────────────────────────────────────────
|
||||
|
||||
function StatoBadge({ stato }: { stato: FascicoloResponse['stato'] }) {
|
||||
const config: Record<string, { label: string; className: string }> = {
|
||||
aperto: { label: 'Aperto', className: 'bg-green-100 text-green-800 border border-green-200' },
|
||||
chiuso: { label: 'Chiuso', className: 'bg-gray-100 text-gray-700 border border-gray-200' },
|
||||
archiviato: { label: 'Archiviato', className: 'bg-amber-100 text-amber-800 border border-amber-200' },
|
||||
}
|
||||
const { label, className } = config[stato] ?? config.aperto
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${className}`}>
|
||||
{label}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Dialog modifica fascicolo ────────────────────────────────────────────────
|
||||
|
||||
interface EditDialogProps {
|
||||
fascicolo: FascicoloResponse
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
function EditDialog({ fascicolo, onClose }: EditDialogProps) {
|
||||
const queryClient = useQueryClient()
|
||||
const [form, setForm] = useState<FascicoloUpdate & { stato: 'aperto' | 'chiuso' | 'archiviato' }>({
|
||||
titolo: fascicolo.titolo,
|
||||
numero_pratica: fascicolo.numero_pratica ?? '',
|
||||
stato: fascicolo.stato,
|
||||
categoria: fascicolo.categoria ?? '',
|
||||
scadenza: fascicolo.scadenza ? fascicolo.scadenza.substring(0, 16) : '',
|
||||
note: fascicolo.note ?? '',
|
||||
})
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: (data: FascicoloUpdate) => fascicoliApi.update(fascicolo.id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicolo', fascicolo.id] })
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicoli'] })
|
||||
toast.success('Fascicolo aggiornato')
|
||||
onClose()
|
||||
},
|
||||
onError: (e) => toast.error(getErrorMessage(e)),
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!form.titolo?.trim()) { toast.error('Il titolo e obbligatorio'); return }
|
||||
updateMutation.mutate({
|
||||
titolo: form.titolo?.trim(),
|
||||
numero_pratica: (form.numero_pratica as string)?.trim() || null,
|
||||
stato: form.stato,
|
||||
categoria: (form.categoria as string)?.trim() || null,
|
||||
scadenza: form.scadenza ? new Date(form.scadenza as string).toISOString() : null,
|
||||
note: (form.note as string)?.trim() || null,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background rounded-xl shadow-2xl w-full max-w-lg">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b">
|
||||
<h3 className="text-lg font-semibold">Modifica fascicolo</h3>
|
||||
<button onClick={onClose} className="p-1 rounded hover:bg-muted"><X className="h-5 w-5" /></button>
|
||||
</div>
|
||||
<div className="px-5 py-4 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<Label>Titolo *</Label>
|
||||
<Input value={form.titolo ?? ''} onChange={(e) => setForm((f) => ({ ...f, titolo: e.target.value }))} autoFocus />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Numero pratica</Label>
|
||||
<Input value={form.numero_pratica as string ?? ''} onChange={(e) => setForm((f) => ({ ...f, numero_pratica: e.target.value }))} placeholder="Es. 2024/0042" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Stato</Label>
|
||||
<select value={form.stato} onChange={(e) => setForm((f) => ({ ...f, stato: e.target.value as typeof form.stato }))}
|
||||
className="w-full h-9 rounded-md border border-input bg-background px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
||||
<option value="aperto">Aperto</option>
|
||||
<option value="chiuso">Chiuso</option>
|
||||
<option value="archiviato">Archiviato</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>Categoria</Label>
|
||||
<Input value={form.categoria as string ?? ''} onChange={(e) => setForm((f) => ({ ...f, categoria: e.target.value }))} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Scadenza</Label>
|
||||
<Input type="datetime-local" value={form.scadenza as string ?? ''} onChange={(e) => setForm((f) => ({ ...f, scadenza: e.target.value }))} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Note</Label>
|
||||
<textarea value={form.note as string ?? ''} onChange={(e) => setForm((f) => ({ ...f, note: e.target.value }))} rows={3}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring resize-none" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 px-5 py-4 border-t">
|
||||
<Button variant="outline" onClick={onClose}>Annulla</Button>
|
||||
<Button onClick={handleSubmit} isLoading={updateMutation.isPending}>Salva modifiche</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Modal selezione messaggi da aggiungere ───────────────────────────────────
|
||||
|
||||
interface AddMessagesModalProps {
|
||||
fascicoloId: string
|
||||
existingIds: Set<string>
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
function AddMessagesModal({ fascicoloId, existingIds, onClose }: AddMessagesModalProps) {
|
||||
const queryClient = useQueryClient()
|
||||
const [search, setSearch] = useState('')
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set())
|
||||
|
||||
const { data: messages = [] } = useQuery({
|
||||
queryKey: ['messages-for-fascicolo', search],
|
||||
queryFn: async () => {
|
||||
// Carica messaggi recenti (non gia' nel fascicolo)
|
||||
const params: Record<string, string | number> = { page: 1, page_size: 50, direction: 'all' }
|
||||
if (search) params.search = search
|
||||
const result = await messagesApi.list(params)
|
||||
const items = (result as any).items ?? result
|
||||
return items.filter((m: { id: string }) => !existingIds.has(m.id))
|
||||
},
|
||||
})
|
||||
|
||||
const addMutation = useMutation({
|
||||
mutationFn: () => fascicoliApi.addMessages(fascicoloId, Array.from(selected)),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicolo', fascicoloId] })
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicolo-messages', fascicoloId] })
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicoli'] })
|
||||
toast.success(`${data.added} messaggi aggiunti al fascicolo`)
|
||||
onClose()
|
||||
},
|
||||
onError: (e) => toast.error(getErrorMessage(e)),
|
||||
})
|
||||
|
||||
const toggleSelect = (id: string) => {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) next.delete(id)
|
||||
else next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||
<div className="bg-background rounded-xl shadow-2xl w-full max-w-2xl flex flex-col max-h-[80vh]">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b flex-shrink-0">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Plus className="h-5 w-5 text-primary" />
|
||||
Aggiungi messaggi al fascicolo
|
||||
</h3>
|
||||
<button onClick={onClose} className="p-1 rounded hover:bg-muted"><X className="h-5 w-5" /></button>
|
||||
</div>
|
||||
|
||||
{/* Ricerca */}
|
||||
<div className="px-5 py-3 border-b flex-shrink-0">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input className="pl-8" placeholder="Cerca messaggi..." value={search} onChange={(e) => setSearch(e.target.value)} autoFocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lista messaggi */}
|
||||
<div className="flex-1 overflow-y-auto px-5 py-3 space-y-1.5">
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<CheckCircle2 className="h-10 w-10 mx-auto mb-3 opacity-20" />
|
||||
<p className="text-sm">Nessun messaggio disponibile</p>
|
||||
</div>
|
||||
) : (
|
||||
messages.map((msg: FascicoloMessageItem & { id: string; subject?: string; from_address?: string; to_addresses?: string[]; direction?: string; state?: string; received_at?: string; sent_at?: string }) => (
|
||||
<label
|
||||
key={msg.id}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
|
||||
selected.has(msg.id) ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.has(msg.id)}
|
||||
onChange={() => toggleSelect(msg.id)}
|
||||
className="h-4 w-4 accent-primary flex-shrink-0"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{msg.subject || '(nessun oggetto)'}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{msg.direction === 'inbound' ? `Da: ${msg.from_address}` : `A: ${(msg.to_addresses ?? []).join(', ')}`}
|
||||
{' · '}
|
||||
{formatDate(msg.received_at || msg.sent_at || msg.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
{msg.direction === 'inbound'
|
||||
? <Inbox className="h-4 w-4 text-blue-500" />
|
||||
: <Send className="h-4 w-4 text-green-500" />}
|
||||
</div>
|
||||
</label>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between px-5 py-4 border-t flex-shrink-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{selected.size > 0 ? `${selected.size} messaggi selezionati` : 'Seleziona i messaggi da aggiungere'}
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" onClick={onClose}>Annulla</Button>
|
||||
<Button
|
||||
onClick={() => addMutation.mutate()}
|
||||
disabled={selected.size === 0}
|
||||
isLoading={addMutation.isPending}
|
||||
>
|
||||
Aggiungi {selected.size > 0 ? `(${selected.size})` : ''}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Pagina dettaglio fascicolo ───────────────────────────────────────────────
|
||||
|
||||
export function FascicoloDetailPage() {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const navigate = useNavigate()
|
||||
const queryClient = useQueryClient()
|
||||
const { isAdmin } = useAuth()
|
||||
|
||||
const [showEditDialog, setShowEditDialog] = useState(false)
|
||||
const [showAddMessages, setShowAddMessages] = useState(false)
|
||||
const [deleteConfirm, setDeleteConfirm] = useState(false)
|
||||
|
||||
const { data: fascicolo, isLoading: loadingFasc } = useQuery({
|
||||
queryKey: ['fascicolo', id],
|
||||
queryFn: () => fascicoliApi.get(id!),
|
||||
enabled: !!id,
|
||||
})
|
||||
|
||||
const { data: messages = [], isLoading: loadingMsgs } = useQuery({
|
||||
queryKey: ['fascicolo-messages', id],
|
||||
queryFn: () => fascicoliApi.getMessages(id!),
|
||||
enabled: !!id,
|
||||
})
|
||||
|
||||
const removeMsgMutation = useMutation({
|
||||
mutationFn: (msgId: string) => fascicoliApi.removeMessages(id!, [msgId]),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicolo-messages', id] })
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicolo', id] })
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicoli'] })
|
||||
toast.success('Messaggio rimosso dal fascicolo')
|
||||
},
|
||||
onError: (e) => toast.error(getErrorMessage(e)),
|
||||
})
|
||||
|
||||
const deleteFascicoloMutation = useMutation({
|
||||
mutationFn: () => fascicoliApi.delete(id!),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fascicoli'] })
|
||||
toast.success('Fascicolo eliminato')
|
||||
navigate('/fascicoli')
|
||||
},
|
||||
onError: (e) => toast.error(getErrorMessage(e)),
|
||||
})
|
||||
|
||||
if (loadingFasc) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!fascicolo) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-64 gap-4">
|
||||
<AlertTriangle className="h-10 w-10 text-muted-foreground opacity-40" />
|
||||
<p className="text-muted-foreground">Fascicolo non trovato</p>
|
||||
<Button variant="outline" onClick={() => navigate('/fascicoli')}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Torna ai fascicoli
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const existingIds = new Set(messages.map((m) => m.id))
|
||||
|
||||
const FolderIcon = fascicolo.stato === 'aperto'
|
||||
? FolderOpen
|
||||
: fascicolo.stato === 'chiuso'
|
||||
? FolderCheck
|
||||
: FolderArchive
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Toolbar */}
|
||||
<div className="border-b bg-background px-6 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate('/fascicoli')}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<span className="text-sm text-muted-foreground">Fascicoli</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setShowEditDialog(true)}>
|
||||
<Pencil className="h-4 w-4 mr-1" />
|
||||
Modifica
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<Button variant="outline" size="sm" onClick={() => setDeleteConfirm(true)}
|
||||
className="text-destructive border-destructive/30 hover:bg-destructive/10">
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
Elimina
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Intestazione fascicolo */}
|
||||
<div className="border-b bg-muted/30 px-6 py-5">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-start gap-4">
|
||||
<FolderIcon className={`h-10 w-10 flex-shrink-0 mt-0.5 ${
|
||||
fascicolo.stato === 'aperto' ? 'text-green-600'
|
||||
: fascicolo.stato === 'chiuso' ? 'text-gray-500'
|
||||
: 'text-amber-600'
|
||||
}`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<h1 className="text-xl font-bold">{fascicolo.titolo}</h1>
|
||||
<StatoBadge stato={fascicolo.stato} />
|
||||
{fascicolo.numero_pratica && (
|
||||
<span className="text-sm text-muted-foreground font-mono bg-background border px-2 py-0.5 rounded">
|
||||
#{fascicolo.numero_pratica}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-6 mt-2 text-sm text-muted-foreground flex-wrap">
|
||||
{fascicolo.categoria && (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="font-medium">Categoria:</span> {fascicolo.categoria}
|
||||
</span>
|
||||
)}
|
||||
{fascicolo.scadenza && (
|
||||
<span className="flex items-center gap-1.5 text-amber-700">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span className="font-medium">Scadenza:</span> {formatDate(fascicolo.scadenza)}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1.5">
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
{fascicolo.message_count} {fascicolo.message_count === 1 ? 'messaggio' : 'messaggi'}
|
||||
</span>
|
||||
<span>Aggiornato: {formatDate(fascicolo.updated_at)}</span>
|
||||
</div>
|
||||
{fascicolo.note && (
|
||||
<p className="mt-2 text-sm text-muted-foreground italic">{fascicolo.note}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sezione messaggi */}
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-base font-semibold flex items-center gap-2">
|
||||
<MessageSquare className="h-4 w-4 text-primary" />
|
||||
Messaggi nel fascicolo ({messages.length})
|
||||
</h2>
|
||||
<Button size="sm" onClick={() => setShowAddMessages(true)}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Aggiungi messaggi
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{loadingMsgs ? (
|
||||
<div className="flex justify-center py-10">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
||||
</div>
|
||||
) : messages.length === 0 ? (
|
||||
<div className="text-center py-14 text-muted-foreground border-2 border-dashed rounded-xl">
|
||||
<MessageSquare className="h-12 w-12 mx-auto mb-4 opacity-20" />
|
||||
<p className="font-medium">Nessun messaggio nel fascicolo</p>
|
||||
<p className="text-sm mt-1">Aggiungi messaggi PEC per costruire il fascicolo.</p>
|
||||
<Button className="mt-4" size="sm" onClick={() => setShowAddMessages(true)}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Aggiungi messaggi
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className="flex items-center gap-4 p-4 rounded-lg border bg-card hover:shadow-sm transition-shadow group"
|
||||
>
|
||||
{/* Icona direzione */}
|
||||
<div className="flex-shrink-0">
|
||||
{msg.direction === 'inbound'
|
||||
? <Inbox className="h-5 w-5 text-blue-500" />
|
||||
: <Send className="h-5 w-5 text-green-500" />}
|
||||
</div>
|
||||
|
||||
{/* Info messaggio */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium truncate">{msg.subject || '(nessun oggetto)'}</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{msg.direction === 'inbound'
|
||||
? `Da: ${msg.from_address}`
|
||||
: `A: ${(msg.to_addresses ?? []).join(', ')}`}
|
||||
{' · '}
|
||||
{formatDate(msg.received_at || msg.sent_at || msg.created_at)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/70">
|
||||
Aggiunto: {formatDate(msg.added_at)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Badge stato */}
|
||||
<div className="flex-shrink-0">
|
||||
<PecStateBadge state={msg.state} />
|
||||
</div>
|
||||
|
||||
{/* Azioni */}
|
||||
<div className="flex items-center gap-1 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(`/messages/${msg.id}`)}
|
||||
className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-primary"
|
||||
title="Apri messaggio"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeMsgMutation.mutate(msg.id)}
|
||||
className="p-1.5 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive"
|
||||
title="Rimuovi dal fascicolo"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal modifica */}
|
||||
{showEditDialog && (
|
||||
<EditDialog fascicolo={fascicolo} onClose={() => setShowEditDialog(false)} />
|
||||
)}
|
||||
|
||||
{/* Modal aggiungi messaggi */}
|
||||
{showAddMessages && (
|
||||
<AddMessagesModal
|
||||
fascicoloId={id!}
|
||||
existingIds={existingIds}
|
||||
onClose={() => setShowAddMessages(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Conferma eliminazione */}
|
||||
{deleteConfirm && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background rounded-xl shadow-2xl p-6 w-full max-w-md space-y-4">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2 text-destructive">
|
||||
<Trash2 className="h-5 w-5" />
|
||||
Elimina fascicolo
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Stai per eliminare <strong>{fascicolo.titolo}</strong>.
|
||||
I messaggi collegati non verranno eliminati.
|
||||
Questa operazione non puo essere annullata.
|
||||
</p>
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<Button variant="outline" onClick={() => setDeleteConfirm(false)}>Annulla</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
isLoading={deleteFascicoloMutation.isPending}
|
||||
onClick={() => deleteFascicoloMutation.mutate()}
|
||||
>
|
||||
Elimina
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user