Files
MSP-CAS/src/components/dashboard/AlertsFeed.tsx

164 lines
4.4 KiB
TypeScript

'use client'
import Link from 'next/link'
import { AlertTriangle, CheckCircle, Info, Clock } from 'lucide-react'
import { cn, formatRelativeTime } from '@/lib/utils'
interface Alert {
id: string
severidad: 'INFO' | 'WARNING' | 'CRITICAL'
estado: 'ACTIVA' | 'RECONOCIDA' | 'RESUELTA'
titulo: string
mensaje: string
createdAt: Date
dispositivo?: { nombre: string } | null
cliente: { nombre: string }
}
interface AlertsFeedProps {
alerts: Alert[]
onAcknowledge?: (alertId: string) => void
onResolve?: (alertId: string) => void
maxItems?: number
}
export default function AlertsFeed({
alerts,
onAcknowledge,
onResolve,
maxItems = 10,
}: AlertsFeedProps) {
const displayAlerts = alerts.slice(0, maxItems)
if (displayAlerts.length === 0) {
return (
<div className="card p-8 text-center">
<CheckCircle className="w-12 h-12 text-success mx-auto mb-3" />
<p className="text-gray-400">No hay alertas activas</p>
</div>
)
}
return (
<div className="card overflow-hidden">
<div className="card-header flex items-center justify-between">
<h3 className="font-medium">Alertas Recientes</h3>
<Link href="/alerts" className="text-sm text-primary-500 hover:underline">
Ver todas
</Link>
</div>
<div className="divide-y divide-dark-100">
{displayAlerts.map((alert) => (
<AlertItem
key={alert.id}
alert={alert}
onAcknowledge={onAcknowledge}
onResolve={onResolve}
/>
))}
</div>
</div>
)
}
function AlertItem({
alert,
onAcknowledge,
onResolve,
}: {
alert: Alert
onAcknowledge?: (alertId: string) => void
onResolve?: (alertId: string) => void
}) {
const severityConfig = {
CRITICAL: {
icon: <AlertTriangle className="w-5 h-5" />,
color: 'text-danger',
bgColor: 'bg-danger/20',
borderColor: 'border-l-danger',
},
WARNING: {
icon: <AlertTriangle className="w-5 h-5" />,
color: 'text-warning',
bgColor: 'bg-warning/20',
borderColor: 'border-l-warning',
},
INFO: {
icon: <Info className="w-5 h-5" />,
color: 'text-info',
bgColor: 'bg-info/20',
borderColor: 'border-l-info',
},
}
const config = severityConfig[alert.severidad]
return (
<div
className={cn(
'p-4 border-l-4 hover:bg-dark-300/30 transition-colors',
config.borderColor,
alert.severidad === 'CRITICAL' && 'animate-pulse-slow'
)}
>
<div className="flex items-start gap-3">
<div className={cn('p-2 rounded-lg', config.bgColor)}>
<span className={config.color}>{config.icon}</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div>
<h4 className="font-medium text-sm">{alert.titulo}</h4>
<p className="text-xs text-gray-400 mt-0.5">{alert.mensaje}</p>
</div>
<span
className={cn(
'badge shrink-0',
alert.estado === 'ACTIVA' && 'badge-danger',
alert.estado === 'RECONOCIDA' && 'badge-warning',
alert.estado === 'RESUELTA' && 'badge-success'
)}
>
{alert.estado}
</span>
</div>
<div className="flex items-center gap-4 mt-2">
<div className="flex items-center gap-1 text-xs text-gray-500">
<Clock className="w-3 h-3" />
{formatRelativeTime(alert.createdAt)}
</div>
{alert.dispositivo && (
<span className="text-xs text-gray-500">
{alert.dispositivo.nombre}
</span>
)}
<span className="text-xs text-gray-600">
{alert.cliente.nombre}
</span>
</div>
{alert.estado === 'ACTIVA' && (
<div className="flex gap-2 mt-3">
<button
onClick={() => onAcknowledge?.(alert.id)}
className="btn btn-ghost btn-sm"
>
Reconocer
</button>
<button
onClick={() => onResolve?.(alert.id)}
className="btn btn-ghost btn-sm text-success"
>
Resolver
</button>
</div>
)}
</div>
</div>
</div>
)
}