OCR + reportistica

This commit is contained in:
2026-03-27 13:54:07 +01:00
parent cbeedc2d2f
commit bb2060c1ae
26 changed files with 5503 additions and 237 deletions
+99
View File
@@ -0,0 +1,99 @@
/**
* API client per la Dashboard e Reportistica (Fase 7).
*/
import { apiClient } from './client'
// ─── Tipi ─────────────────────────────────────────────────────────────────────
export interface KpiSummary {
received_today: number
sent_today: number
received_7d: number
sent_7d: number
received_30d: number
sent_30d: number
anomalie_attive: number
tasso_consegna: number
caselle_in_errore: number
messaggi_non_letti: number
totale_messaggi: number
}
export interface DailyStat {
day: string // "YYYY-MM-DD"
received: number
sent: number
}
export interface OutboundStateStat {
state: string
count: number
}
export interface MailboxStat {
mailbox_id: string
email_address: string
display_name: string | null
status: string
received_total: number
sent_total: number
anomalie: number
non_letti: number
last_sync_at: string | null
}
export interface ReportSummaryResponse {
generated_at: string
period_days: number
kpi: KpiSummary
daily_stats: DailyStat[]
outbound_states: OutboundStateStat[]
mailbox_stats: MailboxStat[]
}
// ─── API ──────────────────────────────────────────────────────────────────────
export const reportsApi = {
/**
* Recupera il riepilogo KPI + grafici per la dashboard.
* @param days Numero di giorni per la serie storica (default 7)
*/
getSummary: async (days = 7): Promise<ReportSummaryResponse> => {
const res = await apiClient.get<ReportSummaryResponse>('/reports/summary', {
params: { days },
})
return res.data
},
/**
* Scarica il report in formato CSV.
* Il browser riceverà un file da scaricare.
*/
exportCsv: (params?: {
date_from?: string
date_to?: string
mailbox_id?: string
}) => {
const url = new URL('/api/v1/reports/export', window.location.origin)
url.searchParams.set('format', 'csv')
if (params?.date_from) url.searchParams.set('date_from', params.date_from)
if (params?.date_to) url.searchParams.set('date_to', params.date_to)
if (params?.mailbox_id) url.searchParams.set('mailbox_id', params.mailbox_id)
return url.toString()
},
/**
* Scarica il report in formato PDF.
*/
exportPdf: (params?: {
date_from?: string
date_to?: string
}) => {
const url = new URL('/api/v1/reports/export', window.location.origin)
url.searchParams.set('format', 'pdf')
if (params?.date_from) url.searchParams.set('date_from', params.date_from)
if (params?.date_to) url.searchParams.set('date_to', params.date_to)
return url.toString()
},
}
+132 -4
View File
@@ -2,13 +2,17 @@
* API client per le impostazioni del tenant.
*
* Endpoint:
* GET /api/v1/settings legge configurazione (admin)
* PUT /api/v1/settings aggiorna configurazione (admin)
* GET /api/v1/settings -> legge configurazione (admin)
* PUT /api/v1/settings -> aggiorna configurazione (admin)
* GET /api/v1/settings/indexing/stats -> statistiche indicizzazione
* GET /api/v1/settings/indexing/status -> stato job reindex
* POST /api/v1/settings/indexing/reindex -> avvia reindex
* DELETE /api/v1/settings/indexing/reindex -> cancella reindex
*/
import { apiClient } from './client'
// ─── Tipi ──────────────────────────────────────────────────────────────────
// ─── Tipi impostazioni generali ────────────────────────────────────────────
export type ArchivalMode = 'mock' | 'production'
@@ -37,7 +41,38 @@ export interface TenantSettingsUpdate {
archival_notes?: string
}
// ─── Client ────────────────────────────────────────────────────────────────
// ─── Tipi indicizzazione full-text ─────────────────────────────────────────
export interface IndexingStats {
total_messages: number
indexed_messages: number
unindexed_messages: number
coverage_pct: number // 0-100
attachments_total: number
attachments_extracted: number
attachments_pct: number // 0-100
}
export type ReindexMode = 'full' | 'differential'
export type JobStatus = 'idle' | 'running' | 'completed' | 'failed' | 'cancelled'
export interface IndexingJobStatus {
status: JobStatus
mode: ReindexMode | null
total: number
processed: number
progress_pct: number
started_at: string | null // ISO datetime
finished_at: string | null // ISO datetime
started_by: string | null // email
elapsed_seconds: number | null
is_stale: boolean // running da piu' di 2 ore
error: string | null
}
// ─── Client impostazioni generali ──────────────────────────────────────────
export const settingsApi = {
/**
@@ -57,4 +92,97 @@ export const settingsApi = {
const { data } = await apiClient.put<TenantSettingsResponse>('/settings', payload)
return data
},
// ── Indicizzazione full-text ──────────────────────────────────────────────
/**
* Restituisce le statistiche di copertura dell'indicizzazione.
* @param tenantId - (solo super_admin) UUID del tenant target
*/
getIndexingStats: async (tenantId?: string): Promise<IndexingStats> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.get<IndexingStats>('/settings/indexing/stats', { params })
return data
},
/**
* Restituisce lo stato del job di reindex in corso (o idle se nessuno).
* @param tenantId - (solo super_admin) UUID del tenant target
*/
getIndexingStatus: async (tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.get<IndexingJobStatus>('/settings/indexing/status', { params })
return data
},
/**
* Avvia un job di reindex in background.
* @param mode - 'differential' (solo NULL) o 'full' (tutti i messaggi)
* @param tenantId - (solo super_admin) UUID del tenant target
*/
startReindex: async (mode: ReindexMode, tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.post<IndexingJobStatus>(
'/settings/indexing/reindex',
{ mode },
{ params }
)
return data
},
/**
* Cancella il job di reindex in corso.
* @param tenantId - (solo super_admin) UUID del tenant target
*/
cancelReindex: async (tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.delete<IndexingJobStatus>(
'/settings/indexing/reindex',
{ params }
)
return data
},
// ── Scansione allegati ────────────────────────────────────────────────────
/**
* Restituisce lo stato del job di scansione allegati (idle se nessuno).
* @param tenantId - (solo super_admin) UUID del tenant target
*/
getRescanStatus: async (tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.get<IndexingJobStatus>(
'/settings/indexing/rescan-status',
{ params }
)
return data
},
/**
* Avvia un job di scansione allegati in background.
* @param force - false: solo allegati senza testo estratto; true: tutti
* @param tenantId - (solo super_admin) UUID del tenant target
*/
startRescan: async (force: boolean = false, tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.post<IndexingJobStatus>(
'/settings/indexing/rescan',
{ force },
{ params }
)
return data
},
/**
* Cancella il job di scansione allegati in corso.
* @param tenantId - (solo super_admin) UUID del tenant target
*/
cancelRescan: async (tenantId?: string): Promise<IndexingJobStatus> => {
const params = tenantId ? { tenant_id: tenantId } : undefined
const { data } = await apiClient.delete<IndexingJobStatus>(
'/settings/indexing/rescan',
{ params }
)
return data
},
}