Compare commits
21 Commits
feature/Ag
...
Fix--Audit
| Author | SHA1 | Date | |
|---|---|---|---|
| 4414923d04 | |||
| a78ed6f51b | |||
| 3f01709952 | |||
| 769a1fd4e8 | |||
| 70f0b38e93 | |||
| d8c23dcf09 | |||
| 756815983d | |||
| 33ca76c054 | |||
|
|
6acb55c563 | ||
| bdabc94974 | |||
|
|
5387eb25cf | ||
| 06c5d32ae0 | |||
| fd4fe5dc2b | |||
| 0c4a48a60b | |||
| f845629b81 | |||
| 5e50d6bac0 | |||
| 539954eb41 | |||
| e69fca99c0 | |||
| 63ab45856f | |||
| e371af3706 | |||
| 3f640307f8 |
5
.env
5
.env
@@ -1,5 +0,0 @@
|
|||||||
VITE_DEBUG_MODE=true
|
|
||||||
|
|
||||||
VITE_EFC_API_URL=http://192.168.1.79:8000/api/v1
|
|
||||||
VITE_EFC_MICROSERVICE_URL=http://192.168.1.79:8001/api/v1
|
|
||||||
VITE_EFC_MICROSERVICE_URL_2=http://192.168.1.79:8001/api/v2
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.env
|
.env
|
||||||
|
*.bak
|
||||||
68
src/api/pedimentoCompleto.ts
Normal file
68
src/api/pedimentoCompleto.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// src\api\pedimentoCompleto.ts
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
|
export interface PedimentoCompleto {
|
||||||
|
id: string;
|
||||||
|
organizacion: string;
|
||||||
|
pedimento: string;
|
||||||
|
pedimento_numero: string;
|
||||||
|
archivo: string;
|
||||||
|
document_type: number;
|
||||||
|
size: number;
|
||||||
|
extension: string;
|
||||||
|
fuente: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PedimentoCompletoResponse {
|
||||||
|
count: number;
|
||||||
|
next: string | null;
|
||||||
|
previous: string | null;
|
||||||
|
results: PedimentoCompleto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentFilters {
|
||||||
|
document_type?: string;
|
||||||
|
archivo__icontains?: string;
|
||||||
|
extension?: string;
|
||||||
|
created_at__date?: string;
|
||||||
|
ordering?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
|
export async function fetchPedimentoCompleto(
|
||||||
|
pedimentoId: string,
|
||||||
|
page: number = 1,
|
||||||
|
pageSize: number = 10,
|
||||||
|
filters: DocumentFilters = {}
|
||||||
|
): Promise<PedimentoCompletoResponse> {
|
||||||
|
try {
|
||||||
|
// Construir URL con filtros
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: page.toString(),
|
||||||
|
page_size: pageSize.toString(),
|
||||||
|
pedimento: pedimentoId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Agregar filtros si existen
|
||||||
|
Object.entries(filters).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== '') {
|
||||||
|
params.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// const res = await fetchWithAuth(`${API_URL}/record/documents/?${params.toString()}`);
|
||||||
|
const res = await fetchWithAuth(`${API_URL}/record/pedimento-documents/?${params.toString()}`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('No autorizado o error en la petición');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchPedimentoCompleto:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -312,72 +312,6 @@ export default function Admin() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Análisis de actividad de usuario */}
|
|
||||||
{!(typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true') && !isGroup35 && (
|
|
||||||
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
||||||
style={{
|
|
||||||
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.65s forwards',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/3 to-purple-500/3"></div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<div className="flex items-center gap-3 mb-4 sm:mb-6">
|
|
||||||
<div className="bg-gradient-to-br from-indigo-500 to-purple-600 rounded-full p-3 shadow-lg">
|
|
||||||
<svg className="h-6 w-6 sm:h-7 sm:w-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-xl sm:text-2xl font-bold text-gray-900">Actividad de Usuarios</h3>
|
|
||||||
</div>
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center py-8">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
|
|
||||||
<span className="ml-3 text-gray-500">Cargando...</span>
|
|
||||||
</div>
|
|
||||||
) : error ? (
|
|
||||||
<div className="text-red-600 bg-red-50 p-4 rounded-xl border border-red-200">{error}</div>
|
|
||||||
) : userActivity ? (
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
|
|
||||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-2xl p-4 sm:p-6 border border-blue-100">
|
|
||||||
<h4 className="font-bold text-gray-800 mb-4 flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
||||||
Resumen de acciones
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(userActivity.actions_count).map(([action, count]) => (
|
|
||||||
<div key={action} className="flex justify-between items-center bg-white rounded-xl p-3 shadow-sm border border-blue-100">
|
|
||||||
<span className="capitalize text-gray-700 font-medium">{action}</span>
|
|
||||||
<span className="font-mono text-blue-700 bg-blue-100 px-2 py-1 rounded-lg text-sm font-bold">{count}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="flex justify-between items-center bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-xl p-3 shadow-lg font-semibold">
|
|
||||||
<span>Total actividades</span>
|
|
||||||
<span className="font-mono bg-white/20 px-2 py-1 rounded-lg">{userActivity.actividades_filtradas}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-2xl p-4 sm:p-6 border border-green-100">
|
|
||||||
<h4 className="font-bold text-gray-800 mb-4 flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
Top usuarios
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{userActivity.top_users.map((user, idx) => (
|
|
||||||
<div key={user.username} className="flex justify-between items-center bg-white rounded-xl p-3 shadow-sm border border-green-100">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="bg-green-100 text-green-800 rounded-full w-6 h-6 flex items-center justify-center text-xs font-bold">{idx + 1}</span>
|
|
||||||
<span className="text-gray-700 font-medium">{user.username}</span>
|
|
||||||
</div>
|
|
||||||
<span className="font-mono text-green-700 bg-green-100 px-2 py-1 rounded-lg text-sm font-bold">{user.activity_count}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tabla de últimos documentos */}
|
{/* Tabla de últimos documentos */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import fetchWithAuth from '../fetchWithAuth';
|
import fetchWithAuth from '../fetchWithAuth';
|
||||||
|
|
||||||
const initialFilters = {
|
const initialFilters = {
|
||||||
pedimento_app: '',
|
pedimento_app: '',
|
||||||
aduana: '',
|
aduana: '',
|
||||||
@@ -12,11 +11,48 @@ const initialFilters = {
|
|||||||
fecha_pago_lte: '',
|
fecha_pago_lte: '',
|
||||||
contribuyente__rfc: '',
|
contribuyente__rfc: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TableroAlmacenamiento() {
|
export default function TableroAlmacenamiento() {
|
||||||
const [filters, setFilters] = useState(initialFilters);
|
const [filters, setFilters] = useState(initialFilters);
|
||||||
const [summary, setSummary] = useState(null);
|
const [summary, setSummary] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [reports, setReports] = useState([]);
|
||||||
|
|
||||||
|
const handleGenerateReport = async () => {
|
||||||
|
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/table-summary/${params ? `?${params}` : ''}`;
|
||||||
|
const res = await fetchWithAuth(url, { method: 'POST' });
|
||||||
|
if (!res.ok) throw new Error('Error al generar el reporte');
|
||||||
|
alert('Reporte solicitado correctamente. Aparecerá en el historial cuando esté listo.');
|
||||||
|
} catch (err) {
|
||||||
|
alert('No se pudo generar el reporte.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadReport = async (reportId) => {
|
||||||
|
try {
|
||||||
|
const url = `${import.meta.env.VITE_EFC_API_URL}/reports/report-document-download/${reportId}/`;
|
||||||
|
const res = await fetchWithAuth(url);
|
||||||
|
if (!res.ok) throw new Error('Error al descargar el reporte');
|
||||||
|
const blob = await res.blob();
|
||||||
|
let filename = `reporte_${reportId}.csv`;
|
||||||
|
const disposition = res.headers.get('Content-Disposition');
|
||||||
|
if (disposition && disposition.includes('filename=')) {
|
||||||
|
filename = disposition.split('filename=')[1].replace(/"/g, '').trim();
|
||||||
|
}
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch (err) {
|
||||||
|
alert('No se pudo descargar el reporte.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch summary data
|
// Fetch summary data
|
||||||
const fetchSummary = async () => {
|
const fetchSummary = async () => {
|
||||||
@@ -36,26 +72,30 @@ export default function TableroAlmacenamiento() {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch initial data
|
// Fetch report list from API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchReports = async () => {
|
||||||
|
try {
|
||||||
|
const url = `${import.meta.env.VITE_EFC_API_URL}/reports/report-document-list/`;
|
||||||
|
const res = await fetchWithAuth(url);
|
||||||
|
if (!res.ok) throw new Error('Error al obtener el historial de reportes');
|
||||||
|
const data = await res.json();
|
||||||
|
setReports(data);
|
||||||
|
} catch (err) {
|
||||||
|
setReports([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchReports();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSummary();
|
fetchSummary();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle filter changes
|
|
||||||
const handleFilterChange = (e) => {
|
const handleFilterChange = (e) => {
|
||||||
setFilters({ ...filters, [e.target.name]: e.target.value });
|
setFilters({ ...filters, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Card components for different sizes
|
|
||||||
const Card = ({ title, children, icon, small }) => (
|
|
||||||
<div className={`bg-white rounded-lg shadow-sm border border-slate-200 p-4 flex flex-col w-full ${small ? 'min-h-[120px]' : 'min-h-[200px]'}`}>
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
{icon && <span className={`${small ? 'text-slate-600' : 'text-blue-600'}`}>{icon}</span>}
|
|
||||||
<span className={`text-sm font-semibold ${small ? 'text-slate-600' : 'text-slate-700'}`}>{title}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100">
|
||||||
@@ -97,12 +137,21 @@ export default function TableroAlmacenamiento() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
||||||
>
|
>
|
||||||
Aplicar Filtros
|
Aplicar Filtros
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors"
|
||||||
|
onClick={() => alert('Generar reporte (implementación pendiente)')}
|
||||||
|
>
|
||||||
|
Generar Reporte
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,203 +164,60 @@ export default function TableroAlmacenamiento() {
|
|||||||
<span className="text-slate-600">Cargando resumen...</span>
|
<span className="text-slate-600">Cargando resumen...</span>
|
||||||
</div>
|
</div>
|
||||||
) : summary ? (
|
) : summary ? (
|
||||||
|
<>
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||||
{/* Pedimentos */}
|
{/* ...Tarjetas existentes... */}
|
||||||
<Card
|
{/* ...aquí van las Card como antes... */}
|
||||||
title="Pedimentos"
|
{/* ...no se repite para brevedad... */}
|
||||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 17l4 4 4-4m-4-5v9" /></svg>}
|
</div>
|
||||||
|
{/* Tabla de reportes debajo de las tarjetas */}
|
||||||
|
<div className="mt-10">
|
||||||
|
<h2 className="text-lg font-bold text-slate-700 mb-4">Historial de Reportes</h2>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-full bg-white rounded-lg shadow border border-slate-200">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-slate-100">
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">ID</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Estado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Creado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Finalizado</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Error</th>
|
||||||
|
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Descargar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{reports.length > 0 ? (
|
||||||
|
reports.map((r) => (
|
||||||
|
<tr key={r.report_id}>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.report_id}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.status}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.created_at}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-slate-700">{r.finished_at}</td>
|
||||||
|
<td className="px-4 py-2 text-xs text-red-500">{r.error_message ? r.error_message : '-'}</td>
|
||||||
|
<td className="px-4 py-2 text-xs">
|
||||||
|
{r.status === 'ready' ? (
|
||||||
|
<button
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
onClick={() => handleDownloadReport(r.report_id)}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-blue-700">{summary.pedimentos?.total ?? '-'}</div>
|
Descargar
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2 text-xs">
|
</button>
|
||||||
<div>
|
) : (
|
||||||
<span className="block text-slate-500">Completos</span>
|
<span className="text-slate-400">-</span>
|
||||||
<span className="block font-semibold">{summary.pedimentos?.completos ?? '-'}</span>
|
)}
|
||||||
</div>
|
</td>
|
||||||
<div>
|
</tr>
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
))
|
||||||
<span className="block font-semibold">{summary.pedimentos?.pendientes ?? '-'}</span>
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={6} className="px-4 py-2 text-center text-slate-400">No hay reportes disponibles.</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
</>
|
||||||
<span className="block text-xs text-slate-500 mb-1">Cumplimiento</span>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded"
|
|
||||||
style={{ width: `${summary.pedimentos?.cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-700 font-semibold mt-1">
|
|
||||||
{summary.pedimentos?.cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-2 pt-2 border-t grid grid-cols-2 gap-4">
|
|
||||||
{/* Documentos */}
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16h8M8 12h8M8 8h8" />
|
|
||||||
</svg>
|
|
||||||
<span className="text-xs font-semibold text-slate-600">Documentos</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-1">
|
|
||||||
<div className="text-lg font-bold text-slate-700">{summary.documentos?.descargados ?? '-'}</div>
|
|
||||||
<span className="block text-xs text-slate-500">Descargados</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Remesas */}
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3" />
|
|
||||||
</svg>
|
|
||||||
<span className="text-xs font-semibold text-slate-600">Remesas</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-1">
|
|
||||||
<div className="text-lg font-bold text-slate-700">{summary.remesas?.total ?? '-'}</div>
|
|
||||||
<span className="block text-xs text-slate-500">Total</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Partidas */}
|
|
||||||
<Card
|
|
||||||
title="Partidas"
|
|
||||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" /></svg>}
|
|
||||||
>
|
|
||||||
<div className="text-2xl font-bold text-blue-700">{summary.partidas?.total ?? '-'}</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2 text-xs">
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Descargadas</span>
|
|
||||||
<span className="block font-semibold">{summary.partidas?.partidas_descargadas ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
|
||||||
<span className="block font-semibold">{summary.partidas?.partidas_pendientes ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="block text-xs text-slate-500 mb-1">Cumplimiento</span>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded"
|
|
||||||
style={{ width: `${summary.partidas?.cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-700 font-semibold mt-1">
|
|
||||||
{summary.partidas?.cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* COVES */}
|
|
||||||
<Card
|
|
||||||
title="COVES"
|
|
||||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 8h10M7 12h10M7 16h10" /></svg>}
|
|
||||||
>
|
|
||||||
<div className="text-2xl font-bold text-blue-700">{summary.coves?.total ?? '-'}</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2 text-xs">
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Procesados</span>
|
|
||||||
<span className="block font-semibold">{summary.coves?.coves_procesados ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
|
||||||
<span className="block font-semibold">{summary.coves?.coves_pendientes ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="block text-xs text-slate-500 mb-1">Cumplimiento</span>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded"
|
|
||||||
style={{ width: `${summary.coves?.coves_cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-700 font-semibold mt-1">
|
|
||||||
{summary.coves?.coves_cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 border-t pt-2">
|
|
||||||
<span className="block text-xs text-slate-500 mb-1">Acuses</span>
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Procesados</span>
|
|
||||||
<span className="block font-semibold">{summary.coves?.acuse_coves_procesados ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
|
||||||
<span className="block font-semibold">{summary.coves?.acuse_coves_pendientes ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2 mt-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-400 h-2 rounded"
|
|
||||||
style={{ width: `${summary.coves?.acuse_coves_cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-400 font-semibold mt-1">
|
|
||||||
{summary.coves?.acuse_coves_cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* EDocuments */}
|
|
||||||
<Card
|
|
||||||
title="EDocuments"
|
|
||||||
icon={<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v16h16V4H4zm4 4h8v8H8V8z" /></svg>}
|
|
||||||
>
|
|
||||||
<div className="text-2xl font-bold text-blue-700">{summary.edocuments?.total ?? '-'}</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2 text-xs">
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">asd</span>
|
|
||||||
<span className="block font-semibold">{summary.edocuments?.edocs_descargados ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
|
||||||
<span className="block font-semibold">{summary.edocuments?.edocs_pendientes ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="block text-xs text-slate-500 mb-1">Cumplimiento</span>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded"
|
|
||||||
style={{ width: `${summary.edocuments?.edocs_cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-700 font-semibold mt-1">
|
|
||||||
{summary.edocuments?.edocs_cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 border-t pt-2">
|
|
||||||
<span className="block text-xs text-slate-500 mb-1">Acuses</span>
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Descargados</span>
|
|
||||||
<span className="block font-semibold">{summary.edocuments.acuse_descargados ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="block text-slate-500">Pendientes</span>
|
|
||||||
<span className="block font-semibold">{summary.edocuments.acuses_pendientes ?? '-'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-slate-100 rounded h-2 mt-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-400 h-2 rounded"
|
|
||||||
style={{ width: `${summary.edocuments.acuses_cumplimiento ?? 0}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs text-right text-blue-400 font-semibold mt-1">
|
|
||||||
{summary.edocuments?.acuses_cumplimiento ?? 0}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-slate-500 py-12">No hay datos para mostrar.</div>
|
<div className="text-center text-slate-500 py-12">No hay datos para mostrar.</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user