120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
import { useState, useEffect } from 'react'; // Importar useState y useEffect
|
|
import { Clock, PauseCircle, Hourglass } from 'lucide-react';
|
|
import { format, formatDistanceToNow, isPast, addSeconds } from 'date-fns';
|
|
import { es } from 'date-fns/locale';
|
|
|
|
interface ExecutionCardProps {
|
|
ultimaEjecucion: string | null | undefined;
|
|
proximaEjecucion: string | null | undefined;
|
|
enEjecucion: boolean;
|
|
}
|
|
|
|
export default function ExecutionCard({ ultimaEjecucion, proximaEjecucion, enEjecucion }: ExecutionCardProps) {
|
|
// Estado para forzar la actualización de la UI cada X segundos
|
|
const [, setTick] = useState(0);
|
|
|
|
useEffect(() => {
|
|
// Actualizar cada 5 segundos para que el "hace X tiempo" y el estado "En cola"
|
|
// se sientan en tiempo real sin esperar al refetch del backend.
|
|
const interval = setInterval(() => {
|
|
setTick(t => t + 1);
|
|
}, 5000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return format(new Date(dateString), "dd/MM, HH:mm", { locale: es });
|
|
};
|
|
|
|
const formatRelative = (dateString: string) => {
|
|
return formatDistanceToNow(new Date(dateString), { addSuffix: true, locale: es });
|
|
};
|
|
|
|
// Esta lógica se recalculará cada 5 segundos gracias al setTick
|
|
const isOverdue = proximaEjecucion ? isPast(addSeconds(new Date(proximaEjecucion), -10)) : false;
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 relative overflow-hidden flex flex-col h-full hover:shadow-md transition-shadow">
|
|
{/* Barra de estado */}
|
|
<div className={`h-1 w-full ${enEjecucion ? 'bg-green-500' : 'bg-gray-300'}`} />
|
|
|
|
<div className="p-4 flex-1 flex flex-col">
|
|
|
|
{/* Encabezado Compacto */}
|
|
<div className="flex justify-between items-center mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="w-4 h-4 text-gray-400" />
|
|
<span className="text-xs font-bold text-gray-500 uppercase tracking-wider">
|
|
Sincronización
|
|
</span>
|
|
</div>
|
|
|
|
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded border ${enEjecucion
|
|
? 'bg-green-50 text-green-700 border-green-200'
|
|
: 'bg-gray-50 text-gray-600 border-gray-200'
|
|
}`}>
|
|
{enEjecucion ? 'AUTO' : 'MANUAL'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="space-y-4 pl-1 flex-1 flex flex-col justify-center">
|
|
|
|
{/* ÚLTIMA EJECUCIÓN */}
|
|
<div className="relative pl-5 border-l-2 border-gray-100">
|
|
<div className="absolute -left-[5px] top-1.5 w-2 h-2 rounded-full bg-gray-300 ring-2 ring-white" />
|
|
|
|
<p className="text-[10px] text-gray-400 font-medium uppercase mb-0.5">Última</p>
|
|
{ultimaEjecucion ? (
|
|
<div>
|
|
<p className="text-sm font-semibold text-gray-800">
|
|
{formatRelative(ultimaEjecucion)}
|
|
</p>
|
|
<p className="text-[10px] text-gray-400">
|
|
{formatDate(ultimaEjecucion)}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<p className="text-xs text-gray-400 italic">--</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* PRÓXIMA EJECUCIÓN */}
|
|
<div className="relative pl-5 border-l-2 border-transparent">
|
|
<div className={`absolute -left-[5px] top-1.5 w-2 h-2 rounded-full ring-2 ring-white transition-colors duration-500 ${enEjecucion ? (isOverdue ? 'bg-amber-500' : 'bg-green-500') : 'bg-gray-300'
|
|
}`} />
|
|
|
|
<p className="text-[10px] text-gray-400 font-medium uppercase mb-0.5">Próxima</p>
|
|
|
|
{enEjecucion && proximaEjecucion ? (
|
|
<div className="animate-in fade-in duration-500">
|
|
{isOverdue ? (
|
|
<div>
|
|
<p className="text-sm font-bold text-amber-600 flex items-center gap-1.5">
|
|
En cola...
|
|
<Hourglass className="w-3 h-3 animate-spin-slow" />
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div>
|
|
<p className="text-sm font-bold text-primary-700">
|
|
{formatDate(proximaEjecucion)} hs
|
|
</p>
|
|
<p className="text-[10px] text-primary-600/70">
|
|
aprox. {formatRelative(proximaEjecucion)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="text-xs text-gray-400 font-medium flex items-center gap-1">
|
|
<PauseCircle className="w-3 h-3" /> Detenido
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |