Ogni messaggio PEC contiene un vettore di ricerca full-text (search_vector)
generato automaticamente da oggetto, mittente, destinatari e corpo.
Il worker indicizza anche il testo estratto dagli allegati PDF e DOCX.
Il reindex rigenera questi vettori manualmente, utile dopo migrazioni o
in caso di messaggi non indicizzati.
{(stats.attachments_total - stats.attachments_extracted).toLocaleString('it-IT')} allegati non hanno ancora il testo estratto.
Usa Riscansiona allegati per elaborarli.
)}
Il testo degli allegati viene estratto automaticamente dal worker
durante la sincronizzazione IMAP. Il reindex include il testo
gia' estratto nei vettori di ricerca.
)}
)}
{/* ── Stato job in corso ── */}
{jobStatus && (
Stato indicizzazione
{isRunning && (
aggiornamento automatico ogni 3s
)}
Il job e' in esecuzione da {formatElapsed(jobStatus.elapsed_seconds)} e
potrebbe essere bloccato. Usa il pulsante "Termina indicizzazione" per
cancellarlo e riavviarlo.
Il job si fermera' alla fine del batch corrente.
I messaggi gia' indicizzati rimarranno indicizzati.
·
)}
{showFullReindexConfirm && (
Reindex totale
Verra' riscritto il vettore di ricerca per tutti i{' '}
{stats?.total_messages.toLocaleString('it-IT')} messaggi.
La ricerca rimane disponibile durante il processo.
·
)}
{showForceRescanConfirm && (
Riscansione forzata allegati
Il testo verra' ri-estratto da tutti gli{' '}
{stats?.attachments_total.toLocaleString('it-IT')} allegati del tenant,
sovrascrivendo quelli gia' presenti. Operazione piu' lunga del differenziale.
·
)}
{showRescanCancelConfirm && (
Conferma annullamento scansione
La scansione si fermera' alla fine del batch corrente.
·
)}
{/* ── Pulsanti reindex ── */}
Reindex messaggi
{isRunning && (
)}
{/* ── Pulsanti scansione allegati ── */}
Scansione allegati
{isRescanRunning && (
)}
{/* Legenda pulsanti */}
Reindex differenziale – Indicizza solo i
messaggi con search_vector NULL. Rapido, ideale per uso routinario.
Reindex totale – Riscrive il vettore di
ricerca per tutti i messaggi, includendo il testo degli allegati gia' estratti.
Riscansiona allegati – Scarica da MinIO
gli allegati senza testo estratto (PDF, DOCX, ecc.), ne estrae il testo e
aggiorna il vettore di ricerca. Differenziale: solo allegati non ancora elaborati.
Riscansione forzata – Come
riscansiona, ma ri-estrae il testo da tutti gli allegati (sovrascrive).
Utile dopo migrazioni o per correggere estrazioni errate.
>
)}
)}
)
}
// ─── Pagina principale ────────────────────────────────────────────────────────
export function SettingsPage() {
const { user } = useAuth()
const loadUser = useAuthStore((s) => s.loadUser)
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin'
const isSuperAdmin = user?.role === 'super_admin'
/* ── Stato modifica nome ── */
const [fullName, setFullName] = useState(user?.full_name ?? '')
const [savingName, setSavingName] = useState(false)
/* ── Stato cambio password ── */
const [newPassword, setNewPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [savingPwd, setSavingPwd] = useState(false)
/* ── Stato impostazioni archiviazione (admin) ── */
const [archivalSettings, setArchivalSettings] = useState(null)
const [loadingArchival, setLoadingArchival] = useState(false)
const [archivalExpanded, setArchivalExpanded] = useState(false)
// Form archiviazione
const [archivalMode, setArchivalMode] = useState('mock')
const [conservatoreId, setConservatoreId] = useState('')
const [conservatoreEndpoint, setConservatoreEndpoint] = useState('')
const [conservatoreUsername, setConservatoreUsername] = useState('')
const [conservatorePassword, setConservatorePassword] = useState('')
const [archivalNotes, setArchivalNotes] = useState('')
const [showPassword, setShowPassword] = useState(false)
const [savingArchival, setSavingArchival] = useState(false)
const [showProductionConfirm, setShowProductionConfirm] = useState(false)
/* ── Carica impostazioni archiviazione ── */
useEffect(() => {
if (isAdmin) {
loadArchivalSettings()
}
}, [isAdmin])
const loadArchivalSettings = async () => {
setLoadingArchival(true)
try {
const data = await settingsApi.get()
setArchivalSettings(data)
setArchivalMode(data.archival_mode)
setConservatoreId(data.conservatore_id)
setConservatoreEndpoint(data.conservatore_endpoint ?? '')
setArchivalNotes(data.archival_notes ?? '')
} catch {
toast.error('Errore durante il caricamento delle impostazioni di archiviazione')
} finally {
setLoadingArchival(false)
}
}
/* ── Salva nome ── */
const handleSaveName = async () => {
if (!user) return
if (!fullName.trim()) {
toast.error('Il nome non puo\' essere vuoto')
return
}
setSavingName(true)
try {
await usersApi.update(user.id, { full_name: fullName.trim() })
await loadUser()
toast.success('Nome aggiornato con successo')
} catch {
toast.error('Errore durante il salvataggio del nome')
} finally {
setSavingName(false)
}
}
/* ── Cambia password ── */
const handleChangePassword = async () => {
if (!user) return
if (newPassword.length < 8) {
toast.error('La password deve essere di almeno 8 caratteri')
return
}
if (newPassword !== confirmPassword) {
toast.error('Le password non coincidono')
return
}
setSavingPwd(true)
try {
await usersApi.resetPassword(user.id, newPassword)
setNewPassword('')
setConfirmPassword('')
toast.success('Password aggiornata con successo')
} catch {
toast.error('Errore durante il cambio della password')
} finally {
setSavingPwd(false)
}
}
/* ── Cambio modalita' archiviazione ── */
const handleModeToggle = (newMode: ArchivalMode) => {
if (newMode === 'production' && archivalMode === 'mock') {
setShowProductionConfirm(true)
} else {
setArchivalMode(newMode)
setShowProductionConfirm(false)
}
}
/* ── Salva impostazioni archiviazione ── */
const handleSaveArchival = async () => {
if (archivalMode === 'production' && !conservatoreEndpoint.trim()) {
toast.error('La modalita\' produzione richiede un URL endpoint del conservatore')
return
}
setSavingArchival(true)
try {
const payload: Parameters[0] = {
archival_mode: archivalMode,
conservatore_id: conservatoreId || undefined,
conservatore_endpoint: conservatoreEndpoint || undefined,
archival_notes: archivalNotes || undefined,
}
if (conservatoreUsername) payload.conservatore_username = conservatoreUsername
if (conservatorePassword) payload.conservatore_password = conservatorePassword
const updated = await settingsApi.update(payload)
setArchivalSettings(updated)
setArchivalMode(updated.archival_mode)
setConservatoreId(updated.conservatore_id)
setConservatoreEndpoint(updated.conservatore_endpoint ?? '')
setArchivalNotes(updated.archival_notes ?? '')
setConservatoreUsername('')
setConservatorePassword('')
setShowProductionConfirm(false)
toast.success(
updated.archival_mode === 'production'
? 'Archiviazione attivata in modalita\' PRODUZIONE'
: 'Archiviazione impostata in modalita\' mock'
)
} catch (err: unknown) {
const msg = (err as { response?: { data?: { detail?: string } } })
?.response?.data?.detail
toast.error(msg ?? 'Errore durante il salvataggio delle impostazioni')
} finally {
setSavingArchival(false)
}
}
if (!user) return null
return (
{/* ── Intestazione ── */}
Impostazioni
Gestisci il tuo profilo e le credenziali di accesso
{/* ── Card: Informazioni account ── */}
Informazioni account
Email
{user.email}
Ruolo
{roleLabel(user.role)}
{/* Modifica nome */}
setFullName(e.target.value)}
placeholder="Il tuo nome"
className="flex-1"
/>
In modalita' mock le operazioni di versamento vengono
simulate localmente senza inviare dati a sistemi esterni. Attiva la
modalita' produzione solo dopo aver configurato l'endpoint
e le credenziali del conservatore AgID.