diff --git a/package-lock.json b/package-lock.json index 806366f..88757b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.6.2", "react-window": "^1.8.11", "styled-components": "^6.1.19" @@ -2946,6 +2947,14 @@ "react": "^19.1.1" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/package.json b/package.json index 9d5233e..ba3d754 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.6.2", "react-window": "^1.8.11", "styled-components": "^6.1.19" diff --git a/src/pages/Datastage.jsx b/src/pages/Datastage.jsx index 363a1d9..5f45f2d 100644 --- a/src/pages/Datastage.jsx +++ b/src/pages/Datastage.jsx @@ -128,6 +128,7 @@ export default function Datastage() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [form, setForm] = useState({ archivo: null, contribuyente: '' }); + const [importadores, setImportadores] = useState([]); const [editingId, setEditingId] = useState(null); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); @@ -256,9 +257,21 @@ export default function Datastage() { }; // Abrir modal de creación - const openCreateModal = () => { + const openCreateModal = async () => { setForm({ archivo: null, contribuyente: '' }); setEditingId(null); + // Fetch importadores + try { + const res = await fetchWithAuth(`${import.meta.env.VITE_EFC_API_URL}/customs/importadores/`, { method: 'GET' }); + const data = await res.json(); + if (Array.isArray(data)) { + setImportadores(data); + } else { + setImportadores([]); + } + } catch { + setImportadores([]); + } setShowCreateModal(true); }; @@ -563,43 +576,132 @@ export default function Datastage() { {/* Modal de creación - estilo Users/Importers */} {showCreateModal && (
-
- {/* Header */} -
+ + {/* Header formal con gradiente */} +
-
-
- - +
+
+ + + +
-

Nuevo Datastage

-

Carga un archivo .zip y asigna un contribuyente

+

Nuevo Datastage

+

Carga un archivo .zip y asigna un contribuyente

- -
+ {/* Content */} -
- {error &&
{error}
} -
- - setForm(f => ({ ...f, archivo: e.target.files[0] }))} required /> +
+ {error && ( +
+ + + + {error} +
+ )} + + {/* Sección de Archivo */} +
+
+
+ + + +
+
+

Archivo de Datos

+

Selecciona el archivo ZIP a procesar

+
+
+
+ +
+ setForm(f => ({ ...f, archivo: e.target.files[0] }))} + required + className="w-full px-4 py-2.5 border-2 border-slate-300 border-dashed rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white text-slate-900 text-sm hover:border-blue-500 cursor-pointer file:mr-4 file:py-1 file:px-4 file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700 file:rounded-full hover:file:bg-blue-100" + /> +
+

+ Formato soportado: .ZIP (máximo 100MB) +

+
-
- - setForm(f => ({ ...f, contribuyente: e.target.value }))} required /> + + {/* Sección de Contribuyente */} +
+
+
+ + + +
+
+

Datos del Contribuyente

+

Selecciona el RFC del contribuyente

+
+
+
+ + +

+ Selecciona el RFC del contribuyente asociado a este datastage +

+
+ + {/* Botones de acción */}
- - + +
diff --git a/src/pages/Documents.jsx b/src/pages/Documents.jsx index 30238a3..5eaff5d 100644 --- a/src/pages/Documents.jsx +++ b/src/pages/Documents.jsx @@ -677,7 +677,7 @@ export default function Documents() { onChange={e => handleItemsPerPageChange(Number(e.target.value))} className="border border-gray-300 rounded px-2 py-1 text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white" > - {[5, 10, 20, 50, 100, 200, 400, 800, 1200, 2400, 10000].map(size => ( + {[5, 10, 20, 50, 100, 500, 1000].map(size => ( ))} diff --git a/src/pages/Expedientes.jsx b/src/pages/Expedientes.jsx index f042488..0000fac 100644 --- a/src/pages/Expedientes.jsx +++ b/src/pages/Expedientes.jsx @@ -380,16 +380,27 @@ export default function Documents() { )}
- +
+ + +
{success && (
@@ -425,6 +436,7 @@ export default function Documents() { Archivos Peso Total Expediente + Acciones @@ -507,6 +519,17 @@ export default function Documents() { )} + + + )) ) : ( diff --git a/src/pages/TableroAlmacenamiento.jsx b/src/pages/TableroAlmacenamiento.jsx index 28df813..b702318 100644 --- a/src/pages/TableroAlmacenamiento.jsx +++ b/src/pages/TableroAlmacenamiento.jsx @@ -1,326 +1,321 @@ -import React, { useState } from 'react'; -import { Line, Pie, Doughnut, Bar } from 'react-chartjs-2'; +import React, { useEffect, useState } from 'react'; +import fetchWithAuth from '../fetchWithAuth'; - -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - BarElement, - ArcElement, - Title, - Tooltip, - Legend -} from 'chart.js'; - -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, BarElement, ArcElement, Title, Tooltip, Legend); +const initialFilters = { + pedimento_app: '', + aduana: '', + patente: '', + regimen: '', + agente_aduanal: '', + tipo_operacion: '', + fecha_pago_gte: '', + fecha_pago_lte: '', + contribuyente__rfc: '', +}; export default function TableroAlmacenamiento() { - // Estado para la tabla de documentos y la opción seleccionada - const [selectedMetric, setSelectedMetric] = useState(''); - const [documentos, setDocumentos] = useState([ - { nombre: 'Factura_123.pdf', tipo: 'Factura', ext: 'PDF' }, - { nombre: 'Pedimento_456.xml', tipo: 'Pedimento', ext: 'XML' }, - { nombre: 'Manifiesto_789.docx', tipo: 'Manifiesto', ext: 'DOCX' }, - ]); + const [filters, setFilters] = useState(initialFilters); + const [summary, setSummary] = useState(null); + const [isLoading, setIsLoading] = useState(false); - // Por ahora solo cambia el estado seleccionado, no fetch - const handleMetricClick = (metric) => { - setSelectedMetric(metric); + // Fetch summary data + const fetchSummary = async () => { + setIsLoading(true); + try { + const params = Object.entries(filters) + .filter(([_, v]) => v) + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); + const url = `${import.meta.env.VITE_EFC_API_URL}/reports/dashboard/summary/${params ? `?${params}` : ''}`; + const res = await fetchWithAuth(url); + const data = await res.json(); + setSummary(data); + } catch (err) { + setSummary(null); + } + setIsLoading(false); }; - // Datos simulados para las nuevas gráficas y KPIs - const tiposArchivos = [ - { tipo: 'PDF', espacio: 220 }, - { tipo: 'XML', espacio: 120 }, - { tipo: 'DOCX', espacio: 80 }, - { tipo: 'JPG', espacio: 60 }, - { tipo: 'Otros', espacio: 32 }, - ]; - const topArchivos = [ - { nombre: 'Factura_123.pdf', size: 2.5 }, - { nombre: 'Reporte_2024.pdf', size: 2.1 }, - { nombre: 'Pedimento_456.xml', size: 1.8 }, - { nombre: 'Manifiesto_789.docx', size: 1.2 }, - { nombre: 'Imagen_001.jpg', size: 1.0 }, - ]; - const espacioTotal = 1024; // GB - const espacioOcupado = 512; // GB - const espacioLibre = espacioTotal - espacioOcupado; - const usuarios = [ - { nombre: 'Juan', docs: 120 }, - { nombre: 'Ana', docs: 90 }, - { nombre: 'Luis', docs: 70 }, - { nombre: 'Sofía', docs: 60 }, - { nombre: 'Carlos', docs: 40 }, - ]; - const docsEsteMes = 45; - const docsEliminados = 7; - const usuariosActivos = 4; - const porcentajeUsado = Math.round((espacioOcupado / espacioTotal) * 100); + // Fetch initial data + useEffect(() => { + fetchSummary(); + }, []); + + // Handle filter changes + const handleFilterChange = (e) => { + setFilters({ ...filters, [e.target.name]: e.target.value }); + }; + + // Card components for different sizes + const Card = ({ title, children, icon, small }) => ( +
+
+ {icon && {icon}} + {title} +
+
{children}
+
+ ); return ( -
- {/* Header animado */} -
-
-
- - +
+ {/* Header */} +
+
+
+ +
-
-

- Uso de Almacenamiento -

-

Visualiza y analiza el uso de almacenamiento de la plataforma

+
+

Resumen de Cumplimiento

- {/* Efecto decorativo de fondo */} -
- - - - - - - - - -
-
{/* Filtros */} -
- - - - - +
+
{ + e.preventDefault(); + fetchSummary(); + }} className="bg-white rounded-lg shadow-sm border border-slate-200 p-4"> +
+ {Object.keys(initialFilters).map((key) => ( +
+ + +
+ ))} +
+
+ +
+
- {/* Cards y KPIs */} -
-
- 1,234 - Total de Pedimentos -
-
- 8,765 - Total de Documentos -
-
- {espacioOcupado} GB - Espacio Utilizado -
-
- 2.5 GB - Archivo más grande -
-
- 120 MB - Tamaño promedio -
-
- {espacioLibre} GB - Espacio Libre -
-
- -
-
- {porcentajeUsado}% - % Espacio Usado -
-
- {docsEsteMes} - Docs subidos este mes -
-
- {docsEliminados} - Docs eliminados este mes -
-
- {usuariosActivos} - Usuarios activos este mes -
-
+ {/* Cards */} +
+ {isLoading ? ( +
+
+ Cargando resumen... +
+ ) : summary ? ( +
+ {/* Pedimentos */} + } + > +
{summary.pedimentos?.total ?? '-'}
+
+
+ Completos + {summary.pedimentos?.completos ?? '-'} +
+
+ Pendientes + {summary.pedimentos?.pendientes ?? '-'} +
+
+
+ Cumplimiento +
+
+
+ + {summary.pedimentos?.cumplimiento ?? 0}% + +
+ +
+ {/* Documentos */} +
+
+ + + + Documentos +
+
+
{summary.documentos?.descargados ?? '-'}
+ Descargados +
+
- {/* Gráficas */} -
- {/* Gráfica 1: Espacio utilizado a lo largo del tiempo */} -
-

Espacio utilizado a lo largo del tiempo

- -
- {/* Gráfica 2: Distribución de tipos de archivo */} -
-

Distribución por tipo de archivo

- t.tipo), - datasets: [ - { - data: tiposArchivos.map(t => t.espacio), - backgroundColor: ['#3b82f6', '#6366f1', '#f59e42', '#10b981', '#f472b6'], - }, - ], - }} - options={{ - plugins: { - legend: { display: true, position: 'bottom' }, - }, - }} - /> -
- {/* Gráfica 3: Espacio ocupado vs libre (donut) */} -
-

Espacio ocupado vs libre

- -
-
- {/* Gráficas adicionales */} -
- {/* Top archivos más grandes */} -
-

Top 5 archivos más grandes

- a.nombre), - datasets: [ - { - label: 'Tamaño (GB)', - data: topArchivos.map(a => a.size), - backgroundColor: '#f59e42', - }, - ], - }} - options={{ - indexAxis: 'y', - plugins: { - legend: { display: false }, - }, - scales: { - x: { title: { display: true, text: 'Tamaño (GB)' } }, - y: { title: { display: false } }, - }, - }} - /> -
- {/* Documentos subidos por usuario */} -
-

Documentos subidos por usuario

- u.nombre), - datasets: [ - { - label: 'Documentos', - data: usuarios.map(u => u.docs), - backgroundColor: '#6366f1', - }, - ], - }} - options={{ - plugins: { - legend: { display: false }, - }, - scales: { - x: { title: { display: false } }, - y: { title: { display: true, text: 'Documentos' } }, - }, - }} - /> -
-
+ {/* Remesas */} +
+
+ + + + Remesas +
+
+
{summary.remesas?.total ?? '-'}
+ Total +
+
+
+ + {/* Partidas */} + } + > +
{summary.partidas?.total ?? '-'}
+
+
+ Descargadas + {summary.partidas?.partidas_descargadas ?? '-'} +
+
+ Pendientes + {summary.partidas?.partidas_pendientes ?? '-'} +
+
+
+ Cumplimiento +
+
+
+ + {summary.partidas?.cumplimiento ?? 0}% + +
+ + + {/* COVES */} + } + > +
{summary.coves?.total ?? '-'}
+
+
+ Procesados + {summary.coves?.coves_procesados ?? '-'} +
+
+ Pendientes + {summary.coves?.coves_pendientes ?? '-'} +
+
+
+ Cumplimiento +
+
+
+ + {summary.coves?.coves_cumplimiento ?? 0}% + +
+
+ Acuses +
+
+ Procesados + {summary.coves?.acuse_coves_procesados ?? '-'} +
+
+ Pendientes + {summary.coves?.acuse_coves_pendientes ?? '-'} +
+
+
+
+
+ + {summary.coves?.acuse_coves_cumplimiento ?? 0}% + +
+ + + {/* EDocuments */} + } + > +
{summary.edocuments?.total ?? '-'}
+
+
+ Descargados + {summary.edocuments?.edocs_descargados ?? '-'} +
+
+ Pendientes + {summary.edocuments?.edocs_pendientes ?? '-'} +
+
+
+ Cumplimiento +
+
+
+ + {summary.edocuments?.edocs_cumplimiento ?? 0}% + +
+
+ Acuses +
+
+ Descargados + {summary.edocuments?.acuse_descargados ?? '-'} +
+
+ Pendientes + {summary.edocuments?.acuses_pendientes ?? '-'} +
+
+
+
+
+ + {summary.edocuments?.acuses_cumplimiento ?? 0}% + +
+ + + +
+ ) : ( +
No hay datos para mostrar.
+ )} +
); -} +} \ No newline at end of file diff --git a/src/pages/Users.jsx b/src/pages/Users.jsx index d80f388..f5c7361 100644 --- a/src/pages/Users.jsx +++ b/src/pages/Users.jsx @@ -13,6 +13,8 @@ const initialForm = { }; export default function Users() { + // Estado para RFC importadores + const [importadores, setImportadores] = useState([]); // Inyectar animaciones solo una vez en el cliente useEffect(() => { if (typeof window !== 'undefined' && !document.getElementById('users-animations')) { @@ -561,7 +563,22 @@ export default function Users() { Agente