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:
135
src/components/layout/ClientSelector.tsx
Normal file
135
src/components/layout/ClientSelector.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Building2, ChevronDown, Check, Search } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface Client {
|
||||
id: string
|
||||
nombre: string
|
||||
codigo: string
|
||||
}
|
||||
|
||||
interface ClientSelectorProps {
|
||||
clients?: Client[]
|
||||
selectedId?: string | null
|
||||
onChange?: (clientId: string | null) => void
|
||||
showAll?: boolean
|
||||
}
|
||||
|
||||
export default function ClientSelector({
|
||||
clients = [],
|
||||
selectedId = null,
|
||||
onChange,
|
||||
showAll = true,
|
||||
}: ClientSelectorProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const selectedClient = selectedId
|
||||
? clients.find((c) => c.id === selectedId)
|
||||
: null
|
||||
|
||||
const filteredClients = clients.filter(
|
||||
(c) =>
|
||||
c.nombre.toLowerCase().includes(search.toLowerCase()) ||
|
||||
c.codigo.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
const handleSelect = (id: string | null) => {
|
||||
onChange?.(id)
|
||||
setOpen(false)
|
||||
setSearch('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-dark-300 border border-dark-100 rounded-lg hover:border-primary-500 transition-colors min-w-[200px]"
|
||||
>
|
||||
<Building2 className="w-4 h-4 text-gray-500" />
|
||||
<span className="flex-1 text-left text-sm">
|
||||
{selectedClient ? selectedClient.nombre : 'Todos los clientes'}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'w-4 h-4 text-gray-500 transition-transform',
|
||||
open && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setSearch('')
|
||||
}}
|
||||
/>
|
||||
<div className="absolute left-0 mt-2 w-72 bg-dark-200 border border-dark-100 rounded-lg shadow-lg z-50">
|
||||
{/* Search */}
|
||||
<div className="p-2 border-b border-dark-100">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Buscar cliente..."
|
||||
className="input py-1.5 pl-8 text-sm"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Options */}
|
||||
<div className="max-h-60 overflow-y-auto p-1">
|
||||
{showAll && (
|
||||
<button
|
||||
onClick={() => handleSelect(null)}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm hover:bg-dark-100 transition-colors',
|
||||
!selectedId && 'bg-primary-900/50 text-primary-400'
|
||||
)}
|
||||
>
|
||||
<Building2 className="w-4 h-4" />
|
||||
<span className="flex-1 text-left">Todos los clientes</span>
|
||||
{!selectedId && <Check className="w-4 h-4" />}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{filteredClients.length === 0 ? (
|
||||
<div className="px-3 py-4 text-center text-gray-500 text-sm">
|
||||
No se encontraron clientes
|
||||
</div>
|
||||
) : (
|
||||
filteredClients.map((client) => (
|
||||
<button
|
||||
key={client.id}
|
||||
onClick={() => handleSelect(client.id)}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm hover:bg-dark-100 transition-colors',
|
||||
selectedId === client.id && 'bg-primary-900/50 text-primary-400'
|
||||
)}
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-dark-100 flex items-center justify-center text-xs font-medium text-gray-400">
|
||||
{client.codigo.substring(0, 2).toUpperCase()}
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="font-medium">{client.nombre}</div>
|
||||
<div className="text-xs text-gray-500">{client.codigo}</div>
|
||||
</div>
|
||||
{selectedId === client.id && <Check className="w-4 h-4" />}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user