import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { FileText, Plus, MoreVertical, Eye, Play, Clock, RefreshCw, Search, Download, Trash2, Edit2, XCircle, CheckCircle, AlertCircle, BarChart3, PlayCircle, PauseCircle, } from 'lucide-react'; import { Button } from '@components/atoms/Button'; import { Card, CardHeader, CardTitle, CardContent } from '@components/molecules/Card'; import { DataTable, type Column } from '@components/organisms/DataTable'; import { Dropdown, type DropdownItem } from '@components/organisms/Dropdown'; import { Breadcrumbs } from '@components/organisms/Breadcrumbs'; import { ConfirmModal } from '@components/organisms/Modal'; import { NoDataEmptyState, ErrorEmptyState } from '@components/templates/EmptyState'; import { useReportDefinitions, useReportExecutions, useReportSchedules, } from '@features/reports/hooks'; import type { ReportDefinition, ReportExecution, ReportSchedule, ExecutionStatus, ReportType, } from '@features/reports/types'; import { REPORT_TYPE_LABELS, EXECUTION_STATUS_LABELS, } from '@features/reports/types'; import { formatDate } from '@utils/formatters'; type TabType = 'definitions' | 'executions' | 'schedules'; const statusColors: Record = { pending: 'bg-yellow-100 text-yellow-700', running: 'bg-blue-100 text-blue-700', completed: 'bg-green-100 text-green-700', failed: 'bg-red-100 text-red-700', cancelled: 'bg-gray-100 text-gray-700', }; const typeColors: Record = { financial: 'bg-green-100 text-green-700', accounting: 'bg-blue-100 text-blue-700', tax: 'bg-purple-100 text-purple-700', management: 'bg-orange-100 text-orange-700', custom: 'bg-gray-100 text-gray-700', }; export function ReportsPage() { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('definitions'); const [searchTerm, setSearchTerm] = useState(''); const [selectedType, setSelectedType] = useState(''); const [selectedStatus, setSelectedStatus] = useState(''); const [definitionToDelete, setDefinitionToDelete] = useState(null); const [executionToCancel, setExecutionToCancel] = useState(null); const [scheduleToDelete, setScheduleToDelete] = useState(null); const [scheduleToToggle, setScheduleToToggle] = useState(null); // Definitions hook const { definitions, total: definitionsTotal, page: definitionsPage, totalPages: definitionsTotalPages, isLoading: definitionsLoading, error: definitionsError, setFilters: setDefinitionsFilters, refresh: refreshDefinitions, remove: removeDefinition, toggleActive: toggleDefinitionActive, } = useReportDefinitions({ initialFilters: { search: searchTerm, reportType: selectedType || undefined }, }); // Executions hook const { executions, total: executionsTotal, page: executionsPage, totalPages: executionsTotalPages, isLoading: executionsLoading, error: executionsError, refresh: refreshExecutions, cancel: cancelExecution, download: downloadExecution, } = useReportExecutions({ initialFilters: { status: selectedStatus || undefined }, pollInterval: 5000, }); // Schedules hook const { schedules, total: schedulesTotal, page: schedulesPage, totalPages: schedulesTotalPages, isLoading: schedulesLoading, error: schedulesError, refresh: refreshSchedules, remove: removeSchedule, toggle: toggleSchedule, runNow: runScheduleNow, } = useReportSchedules(); // Definition columns const definitionColumns: Column[] = [ { key: 'name', header: 'Reporte', render: (def) => (
{def.name}
{def.code}
), }, { key: 'reportType', header: 'Tipo', render: (def) => ( {REPORT_TYPE_LABELS[def.reportType]} ), }, { key: 'category', header: 'Categoria', render: (def) => ( {def.category || '-'} ), }, { key: 'isActive', header: 'Estado', render: (def) => ( {def.isActive ? ( <> Activo ) : ( <> Inactivo )} ), }, { key: 'isSystem', header: 'Sistema', render: (def) => ( {def.isSystem ? 'Si' : 'No'} ), }, { key: 'actions', header: '', render: (def) => { const items: DropdownItem[] = [ { key: 'view', label: 'Ver detalle', icon: , onClick: () => navigate(`/reports/definitions/${def.id}`), }, { key: 'execute', label: 'Ejecutar', icon: , // TODO: Keep for now - opens report execution modal/flow onClick: () => navigate(`/reports/definitions/${def.id}/execute`), }, ]; if (!def.isSystem) { items.push( { key: 'edit', label: 'Editar', icon: , onClick: () => navigate(`/reports/definitions/${def.id}/edit`), }, { key: 'toggle', label: def.isActive ? 'Desactivar' : 'Activar', icon: def.isActive ? : , onClick: () => toggleDefinitionActive(def.id), }, { key: 'delete', label: 'Eliminar', icon: , danger: true, onClick: () => setDefinitionToDelete(def), } ); } return ( } items={items} align="right" /> ); }, }, ]; // Execution columns const executionColumns: Column[] = [ { key: 'report', header: 'Reporte', render: (exec) => (
{exec.definitionName || 'Reporte'}
{exec.definitionCode || exec.definitionId}
), }, { key: 'status', header: 'Estado', render: (exec) => ( {exec.status === 'running' && } {exec.status === 'completed' && } {exec.status === 'failed' && } {EXECUTION_STATUS_LABELS[exec.status]} ), }, { key: 'startedAt', header: 'Iniciado', render: (exec) => ( {exec.startedAt ? formatDate(exec.startedAt, 'short') : '-'} ), }, { key: 'completedAt', header: 'Completado', render: (exec) => ( {exec.completedAt ? formatDate(exec.completedAt, 'short') : '-'} ), }, { key: 'duration', header: 'Duracion', render: (exec) => ( {exec.executionTimeMs ? `${(exec.executionTimeMs / 1000).toFixed(2)}s` : '-'} ), }, { key: 'rows', header: 'Filas', render: (exec) => ( {exec.rowCount?.toLocaleString() || '-'} ), }, { key: 'requestedBy', header: 'Solicitado por', render: (exec) => ( {exec.requestedByName || exec.requestedBy} ), }, { key: 'actions', header: '', render: (exec) => { const items: DropdownItem[] = [ { key: 'view', label: 'Ver detalle', icon: , onClick: () => navigate(`/reports/executions/${exec.id}`), }, ]; if (exec.status === 'completed' && exec.outputFiles.length > 0) { exec.outputFiles.forEach((file) => { items.push({ key: `download-${file.format}`, label: `Descargar ${file.format.toUpperCase()}`, icon: , onClick: () => downloadExecution(exec.id, file.format), }); }); } if (exec.status === 'pending' || exec.status === 'running') { items.push({ key: 'cancel', label: 'Cancelar', icon: , danger: true, onClick: () => setExecutionToCancel(exec), }); } return ( } items={items} align="right" /> ); }, }, ]; // Schedule columns const scheduleColumns: Column[] = [ { key: 'name', header: 'Programacion', render: (sched) => (
{sched.name}
{sched.definitionName}
), }, { key: 'cronExpression', header: 'Frecuencia', render: (sched) => ( {sched.cronExpression} ), }, { key: 'isActive', header: 'Estado', render: (sched) => ( {sched.isActive ? 'Activo' : 'Inactivo'} ), }, { key: 'lastRunAt', header: 'Ultima ejecucion', render: (sched) => ( {sched.lastRunAt ? formatDate(sched.lastRunAt, 'short') : 'Nunca'} ), }, { key: 'nextRunAt', header: 'Proxima ejecucion', render: (sched) => ( {sched.nextRunAt ? formatDate(sched.nextRunAt, 'short') : '-'} ), }, { key: 'actions', header: '', render: (sched) => { const items: DropdownItem[] = [ { key: 'view', label: 'Ver detalle', icon: , onClick: () => navigate(`/reports/schedules/${sched.id}`), }, { key: 'edit', label: 'Editar', icon: , onClick: () => navigate(`/reports/schedules/${sched.id}/edit`), }, { key: 'runNow', label: 'Ejecutar ahora', icon: , onClick: () => runScheduleNow(sched.id), }, { key: 'toggle', label: sched.isActive ? 'Pausar' : 'Activar', icon: sched.isActive ? : , onClick: () => setScheduleToToggle(sched), }, { key: 'delete', label: 'Eliminar', icon: , danger: true, onClick: () => setScheduleToDelete(sched), }, ]; return ( } items={items} align="right" /> ); }, }, ]; const handleDeleteDefinition = async () => { if (definitionToDelete) { await removeDefinition(definitionToDelete.id); setDefinitionToDelete(null); } }; const handleCancelExecution = async () => { if (executionToCancel) { await cancelExecution(executionToCancel.id); setExecutionToCancel(null); } }; const handleDeleteSchedule = async () => { if (scheduleToDelete) { await removeSchedule(scheduleToDelete.id); setScheduleToDelete(null); } }; const handleToggleSchedule = async () => { if (scheduleToToggle) { await toggleSchedule(scheduleToToggle.id); setScheduleToToggle(null); } }; const getCurrentRefresh = () => { switch (activeTab) { case 'definitions': return refreshDefinitions; case 'executions': return refreshExecutions; case 'schedules': return refreshSchedules; } }; const isCurrentLoading = () => { switch (activeTab) { case 'definitions': return definitionsLoading; case 'executions': return executionsLoading; case 'schedules': return schedulesLoading; } }; const getCurrentError = () => { switch (activeTab) { case 'definitions': return definitionsError; case 'executions': return executionsError; case 'schedules': return schedulesError; } }; // Stats const activeDefinitions = definitions.filter(d => d.isActive).length; const runningExecutions = executions.filter(e => e.status === 'running').length; const completedToday = executions.filter(e => { if (!e.completedAt) return false; const today = new Date().toDateString(); return new Date(e.completedAt).toDateString() === today && e.status === 'completed'; }).length; const activeSchedules = schedules.filter(s => s.isActive).length; const error = getCurrentError(); if (error) { return (
); } return (

Reportes

Gestiona definiciones, ejecuciones y programaciones de reportes

{activeTab === 'definitions' && ( )} {activeTab === 'schedules' && ( )}
{/* Summary Stats */}
setActiveTab('definitions')}>
Definiciones activas
{activeDefinitions}
setActiveTab('executions')}>
0 ? 'animate-spin' : ''}`} />
En ejecucion
{runningExecutions}
Completados hoy
{completedToday}
setActiveTab('schedules')}>
Programaciones
{activeSchedules}
{/* Tabs */}
{/* Content based on active tab */} {activeTab === 'definitions' && 'Definiciones de Reportes'} {activeTab === 'executions' && 'Historial de Ejecuciones'} {activeTab === 'schedules' && 'Programaciones'}
{/* Filters */}
{ setSearchTerm(e.target.value); if (activeTab === 'definitions') { setDefinitionsFilters({ search: e.target.value }); } }} className="w-full rounded-md border border-gray-300 py-2 pl-10 pr-4 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
{activeTab === 'definitions' && ( )} {activeTab === 'executions' && ( )} {(searchTerm || selectedType || selectedStatus) && ( )}
{/* Tables */} {activeTab === 'definitions' && ( definitions.length === 0 && !definitionsLoading ? ( ) : ( setDefinitionsFilters({ page: p }), }} /> ) )} {activeTab === 'executions' && ( executions.length === 0 && !executionsLoading ? ( ) : ( {}, }} /> ) )} {activeTab === 'schedules' && ( schedules.length === 0 && !schedulesLoading ? ( ) : ( {}, }} /> ) )}
{/* Delete Definition Modal */} setDefinitionToDelete(null)} onConfirm={handleDeleteDefinition} title="Eliminar definicion" message={`¿Eliminar la definicion "${definitionToDelete?.name}"? Esta accion no se puede deshacer.`} variant="danger" confirmText="Eliminar" /> {/* Cancel Execution Modal */} setExecutionToCancel(null)} onConfirm={handleCancelExecution} title="Cancelar ejecucion" message="¿Cancelar la ejecucion de este reporte? El reporte no se generara." variant="danger" confirmText="Cancelar ejecucion" /> {/* Delete Schedule Modal */} setScheduleToDelete(null)} onConfirm={handleDeleteSchedule} title="Eliminar programacion" message={`¿Eliminar la programacion "${scheduleToDelete?.name}"? Esta accion no se puede deshacer.`} variant="danger" confirmText="Eliminar" /> {/* Toggle Schedule Modal */} setScheduleToToggle(null)} onConfirm={handleToggleSchedule} title={scheduleToToggle?.isActive ? 'Pausar programacion' : 'Activar programacion'} message={scheduleToToggle?.isActive ? `¿Pausar la programacion "${scheduleToToggle?.name}"? No se ejecutara automaticamente hasta que se reactive.` : `¿Activar la programacion "${scheduleToToggle?.name}"? Se ejecutara segun la frecuencia configurada.` } variant={scheduleToToggle?.isActive ? 'warning' : 'success'} confirmText={scheduleToToggle?.isActive ? 'Pausar' : 'Activar'} />
); } export default ReportsPage;