439 lines
26 KiB
JavaScript
439 lines
26 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { fetchWithAuth } from '../fetchWithAuth';
|
|
// Animación fade-in/slide-up para cards
|
|
const fadeInSlideUp = `@keyframes fadein-slideup {
|
|
0% { opacity: 0; transform: translateY(40px); }
|
|
100% { opacity: 1; transform: translateY(0); }
|
|
}`;
|
|
|
|
// Inyectar animación global si no existe
|
|
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-admin')) {
|
|
const style = document.createElement('style');
|
|
style.id = 'fadein-slideup-admin';
|
|
style.innerHTML = fadeInSlideUp;
|
|
document.head.appendChild(style);
|
|
}
|
|
import TestTailwind from '../components/TestTailwind';
|
|
import { colors } from '../theme';
|
|
|
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
|
|
|
export default function Admin() {
|
|
// Leer grupos del usuario desde localStorage
|
|
let userGroups = [];
|
|
if (typeof window !== 'undefined') {
|
|
try {
|
|
userGroups = JSON.parse(localStorage.getItem('user_groups') || '[]');
|
|
} catch {
|
|
userGroups = [];
|
|
}
|
|
}
|
|
// Si los grupos son exactamente [3,5]
|
|
const isGroup35 = Array.isArray(userGroups) && userGroups.length === 2 && userGroups.includes(3) && userGroups.includes(5);
|
|
// Estado de servicios
|
|
const [services, setServices] = useState(null);
|
|
// Estado de descargas
|
|
const [downloads, setDownloads] = useState(null);
|
|
// Últimos documentos
|
|
const [latestDocs, setLatestDocs] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
// Estado para análisis de actividad de usuario
|
|
const [userActivity, setUserActivity] = useState(null);
|
|
|
|
useEffect(() => {
|
|
async function fetchData() {
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
// Servicios
|
|
const resServices = await fetchWithAuth(`${API_URL}/cards/services-util-information/`);
|
|
if (!resServices.ok) throw new Error('Error al obtener estados de servicios');
|
|
const dataServices = await resServices.json();
|
|
setServices(dataServices);
|
|
|
|
// Descargas
|
|
const resDownloads = await fetchWithAuth(`${API_URL}/cards/document-util-information/`);
|
|
if (!resDownloads.ok) throw new Error('Error al obtener información de descargas');
|
|
const dataDownloads = await resDownloads.json();
|
|
setDownloads(dataDownloads);
|
|
|
|
// Últimos documentos
|
|
const resDocs = await fetchWithAuth(`${API_URL}/cards/downloaded-documents/`);
|
|
if (!resDocs.ok) throw new Error('Error al obtener últimos documentos');
|
|
const dataDocs = await resDocs.json();
|
|
setLatestDocs(dataDocs.documentos);
|
|
|
|
// Análisis de actividad de usuario
|
|
const resUserActivity = await fetchWithAuth(`${API_URL}/cards/user-activity-analysis/`);
|
|
if (!resUserActivity.ok) throw new Error('Error al obtener análisis de actividad de usuario');
|
|
const dataUserActivity = await resUserActivity.json();
|
|
setUserActivity(dataUserActivity);
|
|
} catch (err) {
|
|
console.error('Error fetching admin data:', err);
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
fetchData();
|
|
}, []);
|
|
|
|
// Helper para nombre de archivo
|
|
// Helper para nombre de archivo
|
|
function getFileName(path) {
|
|
return path.split('/').pop() || path;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 p-4 sm:p-6 lg:p-8">
|
|
<div className="max-w-7xl mx-auto">
|
|
{/* Header + Estado del Sistema alineados horizontalmente */}
|
|
<div className="mb-6 sm:mb-8 flex flex-col xl:flex-row xl:items-stretch gap-4 sm:gap-6">
|
|
{/* Header principal mejorado */}
|
|
<div className="relative overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8 flex items-center gap-4 sm:gap-6 flex-1 min-w-0 xl:w-[65%] animate-fadein-slideup opacity-0"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards',
|
|
}}
|
|
>
|
|
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-3 sm:p-4 shadow-lg animate-bounce-slow">
|
|
<svg className="h-8 w-8 sm:h-10 sm:w-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-white tracking-tight mb-1 flex flex-col sm:flex-row sm:items-center gap-2">
|
|
<span>Panel de Administración</span>
|
|
{services && (
|
|
<span className="inline-block bg-white/20 backdrop-blur-sm text-white text-xs sm:text-sm font-semibold px-3 py-1 rounded-full shadow-lg animate-fade-in">
|
|
{services.en_espera} en espera
|
|
</span>
|
|
)}
|
|
</h1>
|
|
<p className="text-sm sm:text-lg text-blue-100 font-medium leading-relaxed">
|
|
{typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true'
|
|
? 'Dashboard principal para gestión de Expediente electrónico'
|
|
: 'Dashboard principal para gestión de agencia aduanal'}
|
|
</p>
|
|
</div>
|
|
{/* Efectos decorativos de fondo modernos */}
|
|
<div className="absolute -top-10 -right-10 opacity-20 pointer-events-none select-none">
|
|
<div className="w-32 h-32 bg-white/10 rounded-full blur-xl"></div>
|
|
</div>
|
|
<div className="absolute -bottom-6 -left-6 opacity-15 pointer-events-none select-none">
|
|
<div className="w-24 h-24 bg-white/10 rounded-full blur-lg"></div>
|
|
</div>
|
|
{/* Partículas flotantes */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
<div className="absolute top-1/4 left-1/4 w-2 h-2 bg-white/30 rounded-full animate-ping"></div>
|
|
<div className="absolute top-3/4 right-1/3 w-1 h-1 bg-white/40 rounded-full animate-pulse"></div>
|
|
<div className="absolute top-1/2 right-1/4 w-3 h-3 bg-white/20 rounded-full animate-bounce"></div>
|
|
</div>
|
|
{/* Animación personalizada para el icono y contador */}
|
|
<style>{`
|
|
@keyframes bounce-slow {
|
|
0%, 100% { transform: translateY(0) scale(1); }
|
|
50% { transform: translateY(-8px) scale(1.05); }
|
|
}
|
|
.animate-bounce-slow {
|
|
animation: bounce-slow 3s infinite;
|
|
}
|
|
@keyframes fade-in {
|
|
from { opacity: 0; transform: scale(0.9) translateY(10px); }
|
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
}
|
|
.animate-fade-in {
|
|
animation: fade-in 0.8s ease-out;
|
|
}
|
|
@keyframes float {
|
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
|
33% { transform: translateY(-10px) rotate(2deg); }
|
|
66% { transform: translateY(-5px) rotate(-1deg); }
|
|
}
|
|
.animate-float {
|
|
animation: float 4s ease-in-out infinite;
|
|
}
|
|
`}</style>
|
|
</div>
|
|
{/* Estado del Sistema card a la derecha */}
|
|
<div className="xl:w-[35%] min-w-[280px] flex-shrink-0 animate-fadein-slideup opacity-0"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards',
|
|
}}
|
|
>
|
|
<div className="relative overflow-hidden rounded-3xl shadow-2xl bg-white border border-gray-100 p-4 sm:p-6 h-full flex flex-col justify-between backdrop-blur-sm">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="bg-gradient-to-br from-emerald-500 to-green-600 rounded-full p-3 shadow-lg animate-bounce-slow">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-xl sm:text-2xl font-extrabold text-gray-900 tracking-tight flex-1">Estado del Sistema</h3>
|
|
</div>
|
|
<div className="space-y-3 sm:space-y-4">
|
|
<div className="flex items-center justify-between p-3 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-100">
|
|
<span className="text-gray-700 font-medium text-sm sm:text-base">API Backend</span>
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800 border border-green-200 gap-1 shadow-sm">
|
|
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
|
Conectado
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between p-3 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-100">
|
|
<span className="text-gray-700 font-medium text-sm sm:text-base">API Servicios</span>
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800 border border-green-200 gap-1 shadow-sm">
|
|
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
|
Conectado
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between p-3 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-100">
|
|
<span className="text-gray-700 font-medium text-sm sm:text-base">Última Actualización</span>
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-800 border border-blue-200 shadow-sm">
|
|
Hace 2 min
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{/* Efecto decorativo de fondo modernizado */}
|
|
<div className="absolute -top-8 -right-8 opacity-10 pointer-events-none select-none">
|
|
<div className="w-24 h-24 bg-gradient-to-br from-green-400 to-blue-500 rounded-full blur-xl"></div>
|
|
</div>
|
|
{/* Animación personalizada para el icono */}
|
|
<style>{`
|
|
@keyframes bounce-slow {
|
|
0%, 100% { transform: translateY(0) scale(1); }
|
|
50% { transform: translateY(-8px) scale(1.05); }
|
|
}
|
|
.animate-bounce-slow {
|
|
animation: bounce-slow 3s infinite;
|
|
}
|
|
`}</style>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Cards con datos de endpoints */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
|
|
{/* Estados de servicios */}
|
|
<div className="group bg-white rounded-2xl shadow-lg border border-gray-100 p-4 sm:p-6 hover:shadow-2xl hover:scale-105 transition-all duration-500 transform animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.25s forwards',
|
|
}}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-shadow duration-300">
|
|
<svg className="w-6 h-6 sm:w-7 sm:h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 sm:ml-5 flex-1 min-w-0">
|
|
<p className="text-xs sm:text-sm font-medium text-gray-500 uppercase tracking-wide">Procesos en Espera</p>
|
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1">{services ? services.en_espera : '-'}</p>
|
|
<p className="text-xs sm:text-sm text-gray-400 mt-1">Total: <span className="font-semibold">{services ? services.procesos_filtrados : '-'}</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="group bg-white rounded-2xl shadow-lg border border-gray-100 p-4 sm:p-6 hover:shadow-2xl hover:scale-105 transition-all duration-500 transform animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.35s forwards',
|
|
}}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-emerald-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-gradient-to-br from-green-500 to-green-600 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-shadow duration-300">
|
|
<svg className="w-6 h-6 sm:w-7 sm:h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 sm:ml-5 flex-1 min-w-0">
|
|
<p className="text-xs sm:text-sm font-medium text-gray-500 uppercase tracking-wide">En Proceso</p>
|
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1">{services ? services.en_proceso : '-'}</p>
|
|
<p className="text-xs sm:text-sm text-gray-400 mt-1">Finalizados: <span className="font-semibold">{services ? services.finalizados : '-'}</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="group bg-white rounded-2xl shadow-lg border border-gray-100 p-4 sm:p-6 hover:shadow-2xl hover:scale-105 transition-all duration-500 transform animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.45s forwards',
|
|
}}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-orange-500/5 to-red-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-shadow duration-300">
|
|
<svg className="w-6 h-6 sm:w-7 sm:h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 sm:ml-5 flex-1 min-w-0">
|
|
<p className="text-xs sm:text-sm font-medium text-gray-500 uppercase tracking-wide">Con Error</p>
|
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1">{services ? services.con_error : '-'}</p>
|
|
<p className="text-xs sm:text-sm text-gray-400 mt-1">Finalizados: <span className="font-semibold">{services ? services.finalizados : '-'}</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* Descargas */}
|
|
<div className="group bg-white rounded-2xl shadow-lg border border-gray-100 p-4 sm:p-6 hover:shadow-2xl hover:scale-105 transition-all duration-500 transform animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.55s forwards',
|
|
}}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 to-indigo-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center">
|
|
<div className="flex-shrink-0">
|
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-shadow duration-300">
|
|
<svg className="w-6 h-6 sm:w-7 sm:h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="ml-4 sm:ml-5 flex-1 min-w-0">
|
|
<p className="text-xs sm:text-sm font-medium text-gray-500 uppercase tracking-wide">Descargados 1 día</p>
|
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1">{downloads ? downloads.archivos_ultimas_1_dia : '-'}</p>
|
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
<span className="text-xs text-gray-400">7 días: <span className="font-semibold">{downloads ? downloads.archivos_ultimos_7_dias : '-'}</span></span>
|
|
<span className="text-xs text-gray-400">| 30 días: <span className="font-semibold">{downloads ? downloads.archivos_ultimos_30_dias : '-'}</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 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"
|
|
style={{
|
|
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.75s forwards',
|
|
}}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-br from-slate-500/2 to-gray-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-slate-600 to-gray-700 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="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-xl sm:text-2xl font-bold text-gray-900">Últimos documentos agregados</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-slate-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>
|
|
) : (
|
|
<>
|
|
{/* Vista de tabla para pantallas grandes */}
|
|
<div className="hidden lg:block overflow-x-auto">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<thead className="bg-gradient-to-r from-gray-50 to-slate-50">
|
|
<tr>
|
|
<th className="px-4 sm:px-6 py-3 sm:py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider rounded-tl-xl">Archivo</th>
|
|
<th className="px-4 sm:px-6 py-3 sm:py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider">Pedimento</th>
|
|
<th className="px-4 sm:px-6 py-3 sm:py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider">Organización</th>
|
|
<th className="px-4 sm:px-6 py-3 sm:py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider rounded-tr-xl">Fecha</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-100">
|
|
{latestDocs.map((doc, index) => (
|
|
<tr key={doc.id} className="hover:bg-gradient-to-r hover:from-blue-50 hover:to-indigo-50 transition-all duration-200">
|
|
<td className="px-4 sm:px-6 py-3 sm:py-4 whitespace-nowrap">
|
|
<div className="flex items-center">
|
|
<div className="bg-blue-100 rounded-lg p-2 mr-3">
|
|
<svg className="h-4 w-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<div className="max-w-xs">
|
|
<div className="text-sm font-mono text-blue-800 truncate" title={getFileName(doc.archivo)}>
|
|
{getFileName(doc.archivo)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="px-4 sm:px-6 py-3 sm:py-4 whitespace-nowrap">
|
|
<span className="bg-gray-100 text-gray-800 px-2 py-1 rounded-lg text-sm font-semibold">{doc.pedimento}</span>
|
|
</td>
|
|
<td className="px-4 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-700 font-medium">{doc.organizacion}</td>
|
|
<td className="px-4 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{new Date(doc.created_at).toLocaleString('es-MX', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Vista de tarjetas para pantallas pequeñas y medianas */}
|
|
<div className="lg:hidden space-y-4">
|
|
{latestDocs.map((doc, index) => (
|
|
<div key={doc.id} className="bg-gradient-to-br from-gray-50 to-slate-50 rounded-2xl p-4 border border-gray-200 hover:shadow-lg transition-all duration-300">
|
|
<div className="flex items-start gap-3">
|
|
<div className="bg-blue-100 rounded-xl p-2 flex-shrink-0">
|
|
<svg className="h-5 w-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm font-mono text-blue-800 font-semibold mb-2 break-all" title={getFileName(doc.archivo)}>
|
|
{getFileName(doc.archivo)}
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span className="text-xs text-gray-500 font-medium">Pedimento:</span>
|
|
<span className="bg-gray-200 text-gray-800 px-2 py-1 rounded-lg text-xs font-semibold">{doc.pedimento}</span>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span className="text-xs text-gray-500 font-medium">Organización:</span>
|
|
<span className="text-sm text-gray-700 font-medium">{doc.organizacion}</span>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span className="text-xs text-gray-500 font-medium">Fecha:</span>
|
|
<span className="text-xs text-gray-600">
|
|
{new Date(doc.created_at).toLocaleString('es-MX', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|