200 lines
6.9 KiB
TypeScript
200 lines
6.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Bell, Search, User, LogOut, Settings, ChevronDown, Menu } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import ClientSelector from './ClientSelector'
|
|
import { useSelectedClient } from '@/components/providers/SelectedClientProvider'
|
|
|
|
export type HeaderClient = { id: string; nombre: string; codigo: string }
|
|
|
|
interface HeaderProps {
|
|
user?: {
|
|
nombre: string
|
|
email: string
|
|
avatar?: string
|
|
rol: string
|
|
}
|
|
onLogout?: () => void
|
|
clients?: HeaderClient[]
|
|
showAllClientsOption?: boolean
|
|
onOpenSidebar?: () => void
|
|
}
|
|
|
|
export default function Header({
|
|
user,
|
|
onLogout,
|
|
clients = [],
|
|
showAllClientsOption = false,
|
|
onOpenSidebar,
|
|
}: HeaderProps) {
|
|
const [showUserMenu, setShowUserMenu] = useState(false)
|
|
const [showNotifications, setShowNotifications] = useState(false)
|
|
const { selectedClientId, setSelectedClientId } = useSelectedClient()
|
|
|
|
return (
|
|
<header className="h-16 bg-dark-400 border-b border-dark-100 flex items-center justify-between gap-2 px-4 sm:px-6">
|
|
<div className="flex items-center gap-2 sm:gap-4 flex-1 min-w-0">
|
|
{onOpenSidebar != null && (
|
|
<button
|
|
type="button"
|
|
onClick={onOpenSidebar}
|
|
aria-label="Abrir menú"
|
|
className="md:hidden shrink-0 p-2 rounded-lg hover:bg-dark-100 text-gray-400 hover:text-white transition-colors"
|
|
>
|
|
<Menu className="w-6 h-6" />
|
|
</button>
|
|
)}
|
|
<div className="relative w-full max-w-xs sm:max-w-none sm: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
|
|
clients={clients}
|
|
selectedId={selectedClientId}
|
|
onChange={setSelectedClientId}
|
|
showAll={showAllClientsOption}
|
|
/>
|
|
</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="/alerts" 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>
|
|
)
|
|
}
|