164 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|