Initial commit: MSP Monitor Dashboard
- Next.js 14 frontend with dark cyan/navy theme - tRPC API with Prisma ORM - MeshCentral, LibreNMS, Headwind MDM integrations - Multi-tenant architecture - Alert system with email/SMS/webhook notifications - Docker Compose deployment - Complete documentation
This commit is contained in:
173
src/components/layout/Header.tsx
Normal file
173
src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Bell, Search, User, LogOut, Settings, ChevronDown } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import ClientSelector from './ClientSelector'
|
||||
|
||||
interface HeaderProps {
|
||||
user?: {
|
||||
nombre: string
|
||||
email: string
|
||||
avatar?: string
|
||||
rol: string
|
||||
}
|
||||
onLogout?: () => void
|
||||
}
|
||||
|
||||
export default function Header({ user, onLogout }: HeaderProps) {
|
||||
const [showUserMenu, setShowUserMenu] = useState(false)
|
||||
const [showNotifications, setShowNotifications] = useState(false)
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-dark-400 border-b border-dark-100 flex items-center justify-between px-6">
|
||||
{/* Search */}
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<div className="relative w-96">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar dispositivos, clientes..."
|
||||
className="input pl-10 bg-dark-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Client Selector */}
|
||||
<ClientSelector />
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Notifications */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowNotifications(!showNotifications)}
|
||||
className="relative p-2 rounded-lg hover:bg-dark-100 text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<Bell className="w-5 h-5" />
|
||||
<span className="absolute top-1 right-1 w-2 h-2 bg-danger rounded-full" />
|
||||
</button>
|
||||
|
||||
{showNotifications && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setShowNotifications(false)}
|
||||
/>
|
||||
<div className="dropdown w-80 right-0 z-50">
|
||||
<div className="px-4 py-3 border-b border-dark-100">
|
||||
<h3 className="font-medium">Notificaciones</h3>
|
||||
</div>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
<NotificationItem
|
||||
type="critical"
|
||||
title="Servidor principal offline"
|
||||
message="El servidor SRV-01 no responde"
|
||||
time="hace 5 min"
|
||||
/>
|
||||
<NotificationItem
|
||||
type="warning"
|
||||
title="CPU alta en PC-ADMIN"
|
||||
message="Uso de CPU al 95%"
|
||||
time="hace 15 min"
|
||||
/>
|
||||
<NotificationItem
|
||||
type="info"
|
||||
title="Backup completado"
|
||||
message="Backup diario finalizado"
|
||||
time="hace 1 hora"
|
||||
/>
|
||||
</div>
|
||||
<div className="px-4 py-3 border-t border-dark-100">
|
||||
<a href="/alertas" className="text-primary-500 text-sm hover:underline">
|
||||
Ver todas las alertas
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* User menu */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowUserMenu(!showUserMenu)}
|
||||
className="flex items-center gap-3 p-2 rounded-lg hover:bg-dark-100 transition-colors"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium">
|
||||
{user?.avatar ? (
|
||||
<img src={user.avatar} alt="" className="w-full h-full rounded-full object-cover" />
|
||||
) : (
|
||||
user?.nombre?.charAt(0).toUpperCase() || 'U'
|
||||
)}
|
||||
</div>
|
||||
<div className="text-left hidden sm:block">
|
||||
<div className="text-sm font-medium text-gray-200">{user?.nombre || 'Usuario'}</div>
|
||||
<div className="text-xs text-gray-500">{user?.rol || 'Rol'}</div>
|
||||
</div>
|
||||
<ChevronDown className="w-4 h-4 text-gray-500" />
|
||||
</button>
|
||||
|
||||
{showUserMenu && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setShowUserMenu(false)}
|
||||
/>
|
||||
<div className="dropdown z-50">
|
||||
<div className="px-4 py-3 border-b border-dark-100">
|
||||
<div className="text-sm font-medium">{user?.nombre}</div>
|
||||
<div className="text-xs text-gray-500">{user?.email}</div>
|
||||
</div>
|
||||
<a href="/perfil" className="dropdown-item flex items-center gap-2">
|
||||
<User className="w-4 h-4" />
|
||||
Mi perfil
|
||||
</a>
|
||||
<a href="/configuracion" className="dropdown-item flex items-center gap-2">
|
||||
<Settings className="w-4 h-4" />
|
||||
Configuracion
|
||||
</a>
|
||||
<div className="h-px bg-dark-100 my-1" />
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className="dropdown-item flex items-center gap-2 w-full text-left text-danger"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Cerrar sesion
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
interface NotificationItemProps {
|
||||
type: 'critical' | 'warning' | 'info'
|
||||
title: string
|
||||
message: string
|
||||
time: string
|
||||
}
|
||||
|
||||
function NotificationItem({ type, title, message, time }: NotificationItemProps) {
|
||||
const colors = {
|
||||
critical: 'bg-danger/20 border-danger',
|
||||
warning: 'bg-warning/20 border-warning',
|
||||
info: 'bg-info/20 border-info',
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'px-4 py-3 border-l-4 hover:bg-dark-100 cursor-pointer transition-colors',
|
||||
colors[type]
|
||||
)}
|
||||
>
|
||||
<div className="font-medium text-sm">{title}</div>
|
||||
<div className="text-xs text-gray-400">{message}</div>
|
||||
<div className="text-xs text-gray-500 mt-1">{time}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user