refactor: dashboard page with client filter and utils (getStatusBorderColor)
This commit is contained in:
@@ -1,11 +1,37 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
import { RefreshCw, Grid, List, Filter } from 'lucide-react'
|
import { RefreshCw, Grid, List, Filter } from 'lucide-react'
|
||||||
import KPICards from '@/components/dashboard/KPICards'
|
import KPICards from '@/components/dashboard/KPICards'
|
||||||
import DeviceGrid from '@/components/dashboard/DeviceGrid'
|
import DeviceGrid from '@/components/dashboard/DeviceGrid'
|
||||||
import AlertsFeed from '@/components/dashboard/AlertsFeed'
|
import AlertsFeed from '@/components/dashboard/AlertsFeed'
|
||||||
|
import { useSelectedClient } from '@/components/providers/SelectedClientProvider'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { trpc } from '@/lib/trpc-client'
|
||||||
|
|
||||||
|
type DeviceForGrid = {
|
||||||
|
id: string
|
||||||
|
nombre: string
|
||||||
|
tipo: string
|
||||||
|
estado: string
|
||||||
|
ip?: string | null
|
||||||
|
sistemaOperativo?: string | null
|
||||||
|
lastSeen?: Date | null
|
||||||
|
cpuUsage?: number | null
|
||||||
|
ramUsage?: number | null
|
||||||
|
cliente?: { nombre: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardAlert = {
|
||||||
|
id: string
|
||||||
|
severidad: 'INFO' | 'WARNING' | 'CRITICAL'
|
||||||
|
estado: 'ACTIVA' | 'RECONOCIDA' | 'RESUELTA'
|
||||||
|
titulo: string
|
||||||
|
mensaje: string
|
||||||
|
createdAt: Date
|
||||||
|
dispositivo: { nombre: string }
|
||||||
|
cliente: { nombre: string }
|
||||||
|
}
|
||||||
|
|
||||||
// Mock data - en produccion vendria de la API
|
// Mock data - en produccion vendria de la API
|
||||||
const mockStats = {
|
const mockStats = {
|
||||||
@@ -142,18 +168,100 @@ const mockAlerts = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const DEVICES_LIMIT = 12
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
const utils = trpc.useUtils()
|
||||||
const [stats, setStats] = useState(mockStats)
|
const { selectedClientId } = useSelectedClient()
|
||||||
const [devices, setDevices] = useState(mockDevices)
|
const clienteId = selectedClientId ?? undefined
|
||||||
const [alerts, setAlerts] = useState(mockAlerts)
|
|
||||||
|
const statsQuery = trpc.clientes.dashboardStats.useQuery(
|
||||||
|
{ clienteId },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
)
|
||||||
|
const stats = statsQuery.data ?? mockStats
|
||||||
|
|
||||||
|
const alertsQuery = trpc.alertas.list.useQuery(
|
||||||
|
{ page: 1, limit: 25, clienteId },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
)
|
||||||
|
const alerts: DashboardAlert[] = useMemo(() => {
|
||||||
|
const list = alertsQuery.data?.alertas ?? []
|
||||||
|
return list.map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
severidad: a.severidad,
|
||||||
|
estado: a.estado,
|
||||||
|
titulo: a.titulo,
|
||||||
|
mensaje: a.mensaje,
|
||||||
|
createdAt: a.createdAt instanceof Date ? a.createdAt : new Date(a.createdAt),
|
||||||
|
dispositivo: a.dispositivo ? { nombre: a.dispositivo.nombre } : { nombre: '—' },
|
||||||
|
cliente: { nombre: a.cliente.nombre },
|
||||||
|
}))
|
||||||
|
}, [alertsQuery.data])
|
||||||
|
|
||||||
|
const acknowledgeMutation = trpc.alertas.reconocer.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
utils.alertas.list.invalidate()
|
||||||
|
utils.clientes.dashboardStats.invalidate()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const resolveMutation = trpc.alertas.resolver.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
utils.alertas.list.invalidate()
|
||||||
|
utils.clientes.dashboardStats.invalidate()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const equiposQuery = trpc.equipos.list.useQuery(
|
||||||
|
{ page: 1, limit: DEVICES_LIMIT, clienteId },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
)
|
||||||
|
const redQuery = trpc.red.list.useQuery(
|
||||||
|
{ page: 1, limit: DEVICES_LIMIT, clienteId },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
)
|
||||||
|
const celularesQuery = trpc.celulares.list.useQuery(
|
||||||
|
{ page: 1, limit: DEVICES_LIMIT, clienteId },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
const devices: DeviceForGrid[] = useMemo(() => {
|
||||||
|
const eq = equiposQuery.data?.dispositivos ?? []
|
||||||
|
const rd = redQuery.data?.dispositivos ?? []
|
||||||
|
const cel = celularesQuery.data?.dispositivos ?? []
|
||||||
|
const all = [...eq, ...rd, ...cel]
|
||||||
|
return all.map((d) => ({
|
||||||
|
id: d.id,
|
||||||
|
nombre: d.nombre,
|
||||||
|
tipo: d.tipo,
|
||||||
|
estado: d.estado,
|
||||||
|
ip: d.ip ?? null,
|
||||||
|
sistemaOperativo: d.sistemaOperativo ?? null,
|
||||||
|
lastSeen: d.lastSeen ?? null,
|
||||||
|
cpuUsage: d.cpuUsage ?? null,
|
||||||
|
ramUsage: d.ramUsage ?? null,
|
||||||
|
cliente: d.cliente ? { nombre: d.cliente.nombre } : undefined,
|
||||||
|
}))
|
||||||
|
}, [equiposQuery.data, redQuery.data, celularesQuery.data])
|
||||||
|
|
||||||
|
const devicesLoading =
|
||||||
|
equiposQuery.isLoading || redQuery.isLoading || celularesQuery.isLoading
|
||||||
|
const isRefreshing =
|
||||||
|
statsQuery.isFetching ||
|
||||||
|
alertsQuery.isFetching ||
|
||||||
|
equiposQuery.isFetching ||
|
||||||
|
redQuery.isFetching ||
|
||||||
|
celularesQuery.isFetching
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
setIsRefreshing(true)
|
await Promise.all([
|
||||||
// TODO: Recargar datos de la API
|
statsQuery.refetch(),
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
alertsQuery.refetch(),
|
||||||
setIsRefreshing(false)
|
equiposQuery.refetch(),
|
||||||
|
redQuery.refetch(),
|
||||||
|
celularesQuery.refetch(),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeviceAction = (deviceId: string, action: string) => {
|
const handleDeviceAction = (deviceId: string, action: string) => {
|
||||||
@@ -162,17 +270,11 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAcknowledgeAlert = (alertId: string) => {
|
const handleAcknowledgeAlert = (alertId: string) => {
|
||||||
setAlerts((prev) =>
|
acknowledgeMutation.mutate({ id: alertId })
|
||||||
prev.map((a) => (a.id === alertId ? { ...a, estado: 'RECONOCIDA' as const } : a))
|
|
||||||
)
|
|
||||||
// TODO: Llamar API
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleResolveAlert = (alertId: string) => {
|
const handleResolveAlert = (alertId: string) => {
|
||||||
setAlerts((prev) =>
|
resolveMutation.mutate({ id: alertId })
|
||||||
prev.map((a) => (a.id === alertId ? { ...a, estado: 'RESUELTA' as const } : a))
|
|
||||||
)
|
|
||||||
// TODO: Llamar API
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -232,20 +334,37 @@ export default function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DeviceGrid
|
{devicesLoading ? (
|
||||||
devices={devices}
|
<div className="rounded-lg border border-dark-100 bg-dark-400 p-8 text-center text-gray-400">
|
||||||
viewMode={viewMode}
|
Cargando dispositivos...
|
||||||
onAction={handleDeviceAction}
|
</div>
|
||||||
/>
|
) : devices.length === 0 ? (
|
||||||
|
<div className="rounded-lg border border-dark-100 bg-dark-400 p-8 text-center text-gray-400">
|
||||||
|
No hay dispositivos. Agregue clientes y sincronice con MeshCentral, LibreNMS o Headwind.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<DeviceGrid
|
||||||
|
devices={devices}
|
||||||
|
viewMode={viewMode}
|
||||||
|
onAction={handleDeviceAction}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Alerts */}
|
{/* Alerts */}
|
||||||
<div>
|
<div>
|
||||||
<AlertsFeed
|
{alertsQuery.isLoading ? (
|
||||||
alerts={alerts}
|
<div className="card p-8 text-center text-gray-400">
|
||||||
onAcknowledge={handleAcknowledgeAlert}
|
Cargando alertas...
|
||||||
onResolve={handleResolveAlert}
|
</div>
|
||||||
/>
|
) : (
|
||||||
|
<AlertsFeed
|
||||||
|
alerts={alerts}
|
||||||
|
onAcknowledge={handleAcknowledgeAlert}
|
||||||
|
onResolve={handleResolveAlert}
|
||||||
|
maxItems={10}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,6 +81,21 @@ export function getStatusBgColor(status: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStatusBorderColor(status: string): string {
|
||||||
|
switch (status.toUpperCase()) {
|
||||||
|
case 'ONLINE':
|
||||||
|
return 'border-success/50'
|
||||||
|
case 'OFFLINE':
|
||||||
|
return 'border-gray-500/40'
|
||||||
|
case 'ALERTA':
|
||||||
|
return 'border-danger/50'
|
||||||
|
case 'MANTENIMIENTO':
|
||||||
|
return 'border-warning/50'
|
||||||
|
default:
|
||||||
|
return 'border-dark-100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getSeverityColor(severity: string): string {
|
export function getSeverityColor(severity: string): string {
|
||||||
switch (severity.toUpperCase()) {
|
switch (severity.toUpperCase()) {
|
||||||
case 'CRITICAL':
|
case 'CRITICAL':
|
||||||
|
|||||||
Reference in New Issue
Block a user