import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { adminResultsApi, AdminResultItem, apiClient } from '../../services/api'; import { Database, AlertTriangle, RefreshCw, Search, X } from 'lucide-react'; import * as XLSX from 'xlsx'; interface Tenant { _id: string; name: string; } interface User { _id: string; name: string; email: string; role: string; tenant: { _id: string; name: string; } | null; isActive: boolean; } const AdminResultsPage: React.FC = () => { const { t } = useTranslation(); const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [limit, setLimit] = useState(20); const [totalPages, setTotalPages] = useState(0); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [rut, setRut] = useState(''); const [tenantId, setTenantId] = useState(''); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [tenants, setTenants] = useState([]); const [selectedTenants, setSelectedTenants] = useState([]); const [selectedPepFilter, setSelectedPepFilter] = useState(''); const [showTenantDropdown, setShowTenantDropdown] = useState(false); const [appliedRut, setAppliedRut] = useState(''); const [appliedTenants, setAppliedTenants] = useState([]); const [appliedPepFilter, setAppliedPepFilter] = useState(''); // Query type filter const [selectedQueryType, setSelectedQueryType] = useState(''); const [appliedQueryType, setAppliedQueryType] = useState(''); // User states const [allUsers, setAllUsers] = useState([]); const [selectedUserId, setSelectedUserId] = useState(''); const [appliedUserId, setAppliedUserId] = useState(''); // Export states const [isExporting, setIsExporting] = useState(false); const [exportProgress, setExportProgress] = useState(0); // NUEVOS ESTADOS PARA "APLICAR A TODAS LAS PÁGINAS" const [isLoadingAllPages, setIsLoadingAllPages] = useState(false); const [allPagesProgress, setAllPagesProgress] = useState(0); const [allPagesLoaded, setAllPagesLoaded] = useState(false); const [fullDataset, setFullDataset] = useState([]); const loadData = async (resetPage = false) => { setLoading(true); setError(null); try { const params: any = { page: resetPage ? 1 : page, limit, }; if (rut.trim()) params.rut = rut.trim(); if (tenantId.trim()) params.tenantId = tenantId.trim(); if (startDate) params.startDate = startDate; if (endDate) params.endDate = endDate; if (appliedUserId) params.userEmail = appliedUserId; if (appliedQueryType) params.queryType = appliedQueryType; const res = await adminResultsApi.list(params); setItems(res.items); setPage(res.page); setTotalPages(res.totalPages); setTotal(res.total); } catch (err) { setError(err instanceof Error ? err.message : t('common.error', 'Error loading data')); } finally { setLoading(false); } }; // NUEVA FUNCIÓN: Cargar todas las páginas const loadAllData = async () => { try { setIsLoadingAllPages(true); setAllPagesProgress(0); // NO enviamos el RUT al servidor, lo filtramos localmente después con checkRowVisibility const params: any = { page: 1, limit: limit, }; // NO incluir rut en params if (tenantId.trim()) params.tenantId = tenantId.trim(); if (startDate) params.startDate = startDate; if (endDate) params.endDate = endDate; if (appliedUserId) params.userEmail = appliedUserId; if (appliedQueryType) params.queryType = appliedQueryType; // Obtener primera página para saber total de páginas const firstRes = await adminResultsApi.list(params); const totalPagesToLoad = firstRes.totalPages; let allData: AdminResultItem[] = [...firstRes.items]; setAllPagesProgress(Math.round((1 / totalPagesToLoad) * 100)); // Cargar el resto de páginas for (let currentPage = 2; currentPage <= totalPagesToLoad; currentPage++) { const pageParams = { ...params, page: currentPage }; const res = await adminResultsApi.list(pageParams); allData = [...allData, ...res.items]; setAllPagesProgress(Math.round((currentPage / totalPagesToLoad) * 100)); } // NO filtramos por RUT aquí, se filtra con checkRowVisibility setFullDataset(allData); setAllPagesLoaded(true); setTotal(allData.length); setPage(1); } catch (err) { setError(err instanceof Error ? err.message : 'Error al cargar todas las páginas'); } finally { setIsLoadingAllPages(false); setAllPagesProgress(0); } }; useEffect(() => { if (!allPagesLoaded) { void loadData(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, limit, allPagesLoaded]); // Cerrar dropdowns al hacer clic fuera useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target.closest('.dropdown-container')) { setShowTenantDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const toggleTenant = (tenantId: string) => { setSelectedTenants(prev => prev.includes(tenantId) ? prev.filter(t => t !== tenantId) : [...prev, tenantId] ); }; const onApplyFilters = () => { setAllPagesLoaded(false); setFullDataset([]); setAppliedRut(rut.trim()); setAppliedTenants([...selectedTenants]); setAppliedPepFilter(selectedPepFilter); setAppliedUserId(selectedUserId); setAppliedQueryType(selectedQueryType); // NO llamamos a loadData, solo aplicamos filtros localmente }; const onApplyFiltersAllPages = async () => { setAppliedRut(rut.trim()); setAppliedTenants([...selectedTenants]); setAppliedPepFilter(selectedPepFilter); setAppliedUserId(selectedUserId); setAppliedQueryType(selectedQueryType); await loadAllData(); }; const onClearFilters = () => { setRut(''); setTenantId(''); setStartDate(''); setEndDate(''); setSelectedTenants([]); setSelectedPepFilter(''); setAppliedRut(''); setAppliedTenants([]); setAppliedPepFilter(''); setSelectedUserId(''); setAppliedUserId(''); setSelectedQueryType(''); setAppliedQueryType(''); setAllPagesLoaded(false); setFullDataset([]); setLimit(20); setPage(1); void loadData(true); }; const fetchTenants = async () => { try { const response = await apiClient.get('/tenant/all'); if (response.data && Array.isArray(response.data)) { setTenants(response.data); } } catch (err) { console.error('Error fetching tenants:', err); } }; useEffect(() => { fetchTenants() }, []) const fetchUsers = async () => { try { const response = await apiClient.get('/users/superadmin/all'); if (response.data && Array.isArray(response.data)) { setAllUsers(response.data); } } catch (err) { console.error('Error fetching users:', err); } }; useEffect(() => { fetchUsers() }, []) const checkRowVisibility = (item: AdminResultItem) => { // Filtro de RUT (solo cuando está aplicado) if (appliedRut.trim()) { if (!item.rut || !item.rut.toLowerCase().includes(appliedRut.trim().toLowerCase())) { return false; } } // Filtro de Tenants if (appliedTenants.length > 0) { const itemTenantId = typeof item.tenantId === 'string' ? item.tenantId : (item.tenantId as any)?._id; if (!appliedTenants.includes(itemTenantId)) return false; } // Filtro de PEP if (appliedPepFilter) { const isPepValue = item.isPep === true ? 'pep' : item.isPep === false ? 'no-pep' : 'unknown'; if (isPepValue !== appliedPepFilter) return false; } // Filtro de Usuario if (appliedUserId) { const usuarioData = allUsers.find(u => u.email === item.userEmail); const usuarioNombre = usuarioData ? usuarioData.name : item.userEmail; const selectedUserData = allUsers.find(u => u.email === appliedUserId); const selectedUserName = selectedUserData ? selectedUserData.name : appliedUserId; if (usuarioNombre !== selectedUserName) return false; } // Filtro de Tipo de Consulta if (appliedQueryType) { const itemType = (item.type || '').toLowerCase(); const filterType = appliedQueryType.toLowerCase(); if (itemType !== filterType) return false; } return true; }; const removeFilter = (filterType: 'tenant' | 'pep' | 'rut' | 'tenantId' | 'startDate' | 'endDate' | 'userId' | 'queryType', value?: string) => { switch (filterType) { case 'tenant': if (value) { const newTenants = appliedTenants.filter(t => t !== value); setAppliedTenants(newTenants); setSelectedTenants(newTenants); } break; case 'pep': setAppliedPepFilter(''); setSelectedPepFilter(''); break; case 'rut': setRut(''); setAppliedRut(''); break; case 'tenantId': setTenantId(''); break; case 'startDate': setStartDate(''); break; case 'endDate': setEndDate(''); break; case 'userId': setSelectedUserId(''); setAppliedUserId(''); break; case 'queryType': setSelectedQueryType(''); setAppliedQueryType(''); break; } // NO llamamos a loadData, solo limpiamos el filtro localmente }; const exportToExcel = async () => { try { setIsExporting(true); setExportProgress(0); let allExportResults: AdminResultItem[] = []; let currentExportPage = 1; // Recolectar todos los datos de todas las páginas while (currentExportPage <= totalPages) { const params: any = { page: currentExportPage, limit: limit, }; if (rut.trim()) params.rut = rut.trim(); if (tenantId.trim()) params.tenantId = tenantId.trim(); if (startDate) params.startDate = startDate; if (endDate) params.endDate = endDate; if (appliedUserId) params.userEmail = appliedUserId; if (appliedQueryType) params.queryType = appliedQueryType; const response = await adminResultsApi.list(params); if (response?.items && Array.isArray(response.items)) { allExportResults = [...allExportResults, ...response.items]; } // Calcular progreso setExportProgress(Math.round((currentExportPage / totalPages) * 100)); currentExportPage++; } // Aplicar filtros de visibilidad const filteredResults = allExportResults.filter(r => checkRowVisibility(r)); // Preparar datos para exportar const exportData = filteredResults.map(item => { const fecha = item.createdAt ? new Date(item.createdAt) : null; const fechaStr = fecha ? fecha.toLocaleDateString() : '—'; const horaStr = fecha ? fecha.toLocaleTimeString() : '—'; const nombreProveedor = item.sheriffLogData?.summaryData?.data?.sii?.razonSocial || item.sheriffLogData?.summaryData?.data?.compliance?.name || '—'; const tenantName = item.tenantName || (typeof item.tenantId === 'string' ? item.tenantId : item.tenantId && (item.tenantId as any).name) || '—'; const usuarioData = allUsers.find(u => u.email === item.userEmail); const usuario = usuarioData ? usuarioData.name : (item.userEmail || '—'); const tipo = item.type || '—'; const estado = item.status || '—'; const tiempo = item.processingTime ? `${item.processingTime}ms` : '—'; const credito = item.creditsConsumed !== undefined && item.creditsConsumed !== null ? item.creditsConsumed : '—'; const clasificacion = item?.isPep === true ? 'PEP' : item?.isPep === false ? 'No PEP' : '—'; return { "Tenant": tenantName, "Usuario": usuario, "Fecha": fechaStr, "Hora": horaStr, "RUT Proveedor": item.rut || '—', "Nombre Proveedor": nombreProveedor, "Clasificación Consulta": clasificacion, "Tipo": tipo, "Estado": estado, "Tiempo": tiempo, "Créditos": credito }; }); // Crear archivo Excel const ws = XLSX.utils.json_to_sheet(exportData); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Resultados Globales"); // Ajustar ancho de columnas const columnWidths = [ { wch: 20 }, // Tenant { wch: 20 }, // Usuario { wch: 12 }, // Fecha { wch: 12 }, // Hora { wch: 15 }, // RUT Proveedor { wch: 30 }, // Nombre Proveedor { wch: 20 }, // Clasificación Consulta { wch: 15 }, // Tipo { wch: 12 }, // Estado { wch: 12 }, // Tiempo { wch: 10 } // Créditos ]; ws['!cols'] = columnWidths; // Descargar archivo XLSX.writeFile(wb, `resultados_globales_${new Date().toISOString().slice(0, 10)}.xlsx`); } catch (err) { console.error('Error exporting to Excel:', err); setError(err instanceof Error ? err.message : 'Error al exportar a Excel'); } finally { setIsExporting(false); setExportProgress(0); } }; // Usar el dataset apropiado dependiendo del modo const activeDataset = allPagesLoaded ? fullDataset : items; const visibleItems = activeDataset.filter(checkRowVisibility); const hiddenItemsCount = activeDataset.length - visibleItems.length; const hasActiveFilters = appliedRut || tenantId || startDate || endDate || appliedTenants.length > 0 || appliedPepFilter || appliedUserId || appliedQueryType; // Filtrar usuarios basado en tenants seleccionados const filteredUsers = selectedTenants.length > 0 ? allUsers.filter(user => user.tenant && selectedTenants.includes(user.tenant._id)) : allUsers; return (

{t('adminResults.title', 'Resultados Globales (Todos los Tenants)')}

{/* Primera fila: RUT, Tenant, Clasificación, Usuario */}
setRut(e.target.value)} placeholder="12.345.678-9" className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500" />
{showTenantDropdown && (
{tenants.length > 0 ? ( tenants.map((tenant) => ( )) ) : (
No hay tenants disponibles
)}
)}
{/* Segunda fila: Tenant ID, Fecha Inicio, Fecha Fin, Tipo Consulta */}
setTenantId(e.target.value)} placeholder="tenant ObjectId" className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500" />
setStartDate(e.target.value)} className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500" />
setEndDate(e.target.value)} className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500" />
{/* Tercera fila: Botones */}
{/* Indicador cuando se han cargado todas las páginas */} {allPagesLoaded && (
Modo: Todas las páginas cargadas ({fullDataset.length} resultados totales)
)} {hasActiveFilters && (
Filtros activos: {appliedRut && ( )} {appliedTenants.map(tenantId => { const tenant = tenants.find(t => t._id === tenantId); return ( ); })} {appliedPepFilter && ( )} {tenantId && ( )} {startDate && ( )} {endDate && ( )} {appliedUserId && ( )} {appliedQueryType && ( )}
)} {error && (
{error}
)}
{loading ? ( ) : visibleItems.length === 0 ? ( ) : ( visibleItems.map(item => { const fecha = item.createdAt ? new Date(item.createdAt) : null; const fechaStr = fecha ? fecha.toLocaleDateString() : '—'; const horaStr = fecha ? fecha.toLocaleTimeString() : '—'; const nombreProveedor = item.sheriffLogData?.summaryData?.data?.sii?.razonSocial || item.sheriffLogData?.summaryData?.data?.compliance?.name || '—'; const tenantName = item.tenantName || (typeof item.tenantId === 'string' ? item.tenantId : item.tenantId && (item.tenantId as any).name) || '—'; const usuarioData = allUsers.find(u => u.email === item.userEmail); const usuario = usuarioData ? usuarioData.name : (item.userEmail || '—'); const tipo = item.type || '—'; const estado = item.status || '—'; const tiempo = item.processingTime ? `${item.processingTime}ms` : '—'; const credito = item.creditsConsumed !== undefined && item.creditsConsumed !== null ? item.creditsConsumed : '—'; return ( ) }) )}
Tenant Usuario Fecha Hora RUT Proveedor Nombre Proveedor Clasificación Consulta Tipo Estado Tiempo Crédito Acciones
{t('common.loading', 'Cargando...')}
{activeDataset.length > 0 ? 'Hay resultados en esta página pero están ocultos por los filtros.' : t('resultsTable.noResults', 'No se encontraron resultados')}
{tenantName} {usuario} {fechaStr} {horaStr} {item.rut || '—'} {nombreProveedor} {item?.isPep === true ? 'PEP' : item?.isPep === false ? 'No PEP' : '—'} {tipo} {estado} {tiempo} {credito} {t('common.view', 'Ver')}
{allPagesLoaded ? ( <> Mostrando todos los resultados | Total: {fullDataset.length} {(appliedTenants.length > 0 || appliedPepFilter || appliedUserId || appliedQueryType) && ( ({visibleItems.length} visibles después de filtros) )} ) : ( <> Mostrando página {page} de {totalPages} | Total: {total} {(appliedTenants.length > 0 || appliedPepFilter) && ( (En esta página: {visibleItems.length} visibles, {hiddenItemsCount} ocultos) )} )}
{!allPagesLoaded && (
)}
); }; export default AdminResultsPage;