import { useEffect, useState } from 'react' import { Plus, Trash2, Edit2, List, Star, Server, Search } from 'lucide-react' import api from '../lib/api' interface User { id: number; username: string; max_connections: number; expiry_date: string | null; is_active: boolean; is_priority: boolean; created_at: string } interface GroupedCategory { name: string; type: string; category_ids: number[]; provider_count: number } interface CatalogEntry { id: number; category_id: number; category_name: string; type: string } interface ProviderPref { id: number; provider_account_id: number; provider_name: string; priority: number } interface Provider { id: number; name: string } interface CustomCat { id: number; name: string; type: string } const TYPE_LABELS: Record = { live: 'Canales en directo', movie: 'Películas', series: 'Series' } export default function Users() { const [users, setUsers] = useState([]) const [showForm, setShowForm] = useState(false) const [editUser, setEditUser] = useState(null) const [catalogUser, setCatalogUser] = useState(null) const [providerUser, setProviderUser] = useState(null) const [allCategories, setAllCategories] = useState([]) const [selectedCats, setSelectedCats] = useState>(new Set()) const [allCustomCats, setAllCustomCats] = useState([]) const [selectedCustomCats, setSelectedCustomCats] = useState>(new Set()) const [catSearch, setCatSearch] = useState('') const [allProviders, setAllProviders] = useState([]) const [userProviderPrefs, setUserProviderPrefs] = useState([]) const [form, setForm] = useState({ username: '', password: '', max_connections: 1, expiry_date: '', is_active: true, is_priority: false }) async function load() { const { data } = await api.get('/users/') setUsers(data) } useEffect(() => { load() }, []) async function submit(e: React.FormEvent) { e.preventDefault() const payload = { ...form, expiry_date: form.expiry_date || null } if (editUser) { await api.put(`/users/${editUser.id}`, payload) } else { await api.post('/users/', payload) } resetForm() load() } function resetForm() { setForm({ username: '', password: '', max_connections: 1, expiry_date: '', is_active: true, is_priority: false }) setShowForm(false) setEditUser(null) } function startEdit(u: User) { setForm({ username: u.username, password: '', max_connections: u.max_connections, expiry_date: u.expiry_date?.split('T')[0] || '', is_active: u.is_active, is_priority: u.is_priority }) setEditUser(u) setShowForm(true) } async function del(id: number) { if (!confirm('¿Eliminar usuario?')) return await api.delete(`/users/${id}`) load() } async function togglePriority(u: User) { await api.put(`/users/${u.id}`, { is_priority: !u.is_priority }) load() } async function openCatalog(u: User) { setCatalogUser(u) setCatSearch('') const [grouped, catalog, customCats, userCustomCatalog] = await Promise.all([ api.get('/catalogs/grouped').then(r => r.data as GroupedCategory[]), api.get(`/users/${u.id}/catalog`).then(r => r.data as CatalogEntry[]), api.get('/custom-categories/').then(r => r.data as CustomCat[]), api.get(`/users/${u.id}/custom-catalog`).then(r => r.data as number[]), ]) setAllCategories(grouped) setSelectedCats(new Set(catalog.map((c: CatalogEntry) => c.category_id))) setAllCustomCats(customCats) setSelectedCustomCats(new Set(userCustomCatalog)) } function isGroupSelected(g: GroupedCategory): boolean { return g.category_ids.some(id => selectedCats.has(id)) } function toggleGroup(g: GroupedCategory, checked: boolean) { setSelectedCats(prev => { const n = new Set(prev) if (checked) { g.category_ids.forEach(id => n.add(id)) } else { g.category_ids.forEach(id => n.delete(id)) } return n }) } function selectAllFiltered(cats: GroupedCategory[]) { setSelectedCats(prev => { const n = new Set(prev) cats.forEach((g: GroupedCategory) => g.category_ids.forEach((id: number) => n.add(id))) return n }) // Also select all filtered custom cats const filteredCC = allCustomCats.filter(c => !catSearch || c.name.toLowerCase().includes(catSearch.toLowerCase())) setSelectedCustomCats(prev => { const n = new Set(prev) filteredCC.forEach(c => n.add(c.id)) return n }) } function selectNoneFiltered(cats: GroupedCategory[]) { setSelectedCats(prev => { const n = new Set(prev) cats.forEach((g: GroupedCategory) => g.category_ids.forEach((id: number) => n.delete(id))) return n }) const filteredCC = allCustomCats.filter(c => !catSearch || c.name.toLowerCase().includes(catSearch.toLowerCase())) setSelectedCustomCats(prev => { const n = new Set(prev) filteredCC.forEach(c => n.delete(c.id)) return n }) } async function saveCatalog() { if (!catalogUser) return await Promise.all([ api.post(`/users/${catalogUser.id}/catalog`, Array.from(selectedCats)), api.post(`/users/${catalogUser.id}/custom-catalog`, Array.from(selectedCustomCats)), ]) setCatalogUser(null) } async function openProviderPrefs(u: User) { setProviderUser(u) const [providers, prefs] = await Promise.all([ api.get('/providers/').then(r => r.data as Provider[]), api.get(`/users/${u.id}/providers`).then(r => r.data as ProviderPref[]), ]) setAllProviders(providers) setUserProviderPrefs(prefs) } async function addProviderPref(providerId: number) { if (!providerUser) return await api.post(`/users/${providerUser.id}/providers`, { provider_account_id: providerId, priority: userProviderPrefs.length }) const { data } = await api.get(`/users/${providerUser.id}/providers`) setUserProviderPrefs(data) } async function removeProviderPref(providerId: number) { if (!providerUser) return await api.delete(`/users/${providerUser.id}/providers/${providerId}`) const { data } = await api.get(`/users/${providerUser.id}/providers`) setUserProviderPrefs(data) } const prefProviderIds = new Set(userProviderPrefs.map(p => p.provider_account_id)) // Catalog modal filtered + grouped by type const filteredCats = allCategories.filter(c => !catSearch || c.name.toLowerCase().includes(catSearch.toLowerCase()) ) const filteredCustomCats = allCustomCats.filter(c => !catSearch || c.name.toLowerCase().includes(catSearch.toLowerCase()) ) const selectedGroupCount = allCategories.filter(g => isGroupSelected(g)).length + selectedCustomCats.size return (

Usuarios

{showForm && (

{editUser ? 'Editar usuario' : 'Nuevo usuario'}

setForm(f => ({ ...f, username: e.target.value }))} required disabled={!!editUser} className="w-full bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm" />
setForm(f => ({ ...f, password: e.target.value }))} required={!editUser} className="w-full bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm" />
setForm(f => ({ ...f, max_connections: +e.target.value }))} className="w-full bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm" />
setForm(f => ({ ...f, expiry_date: e.target.value }))} className="w-full bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm" />
)} {/* Catalog assignment modal */} {catalogUser && (

Catálogo de {catalogUser.username}

{selectedGroupCount} de {allCategories.length + allCustomCats.length} categorías seleccionadas

{/* Search + bulk actions */}
setCatSearch(e.target.value)} placeholder="Buscar categoría..." className="w-full bg-dark-700 border border-dark-600 rounded-lg pl-9 pr-3 py-2 text-sm focus:outline-none focus:border-primary-500" />
{(['live', 'movie', 'series'] as const).map(type => { const cats = filteredCats.filter(c => c.type === type) if (!cats.length) return null return (

{TYPE_LABELS[type]} {cats.filter(g => isGroupSelected(g)).length}/{cats.length}

{cats.map(cat => ( ))}
) })} {filteredCats.length === 0 && filteredCustomCats.length === 0 && (
Sin resultados
)} {/* Custom categories section */} {filteredCustomCats.length > 0 && (

Categorías personalizadas {filteredCustomCats.filter(c => selectedCustomCats.has(c.id)).length}/{filteredCustomCats.length}

{filteredCustomCats.map(cat => ( ))}
)}
)} {/* Provider preferences modal */} {providerUser && (

Proveedores preferidos — {providerUser.username}

Los proveedores marcados se intentan primero al asignar un slot. Si ninguno está disponible, se usa cualquiera con slot libre.

{allProviders.map(p => { const isPref = prefProviderIds.has(p.id) return (
{p.name}
) })} {allProviders.length === 0 && (
No hay proveedores configurados
)}
)}
{users.map(u => ( ))} {users.length === 0 && ( )}
Usuario Conexiones Expiración Estado Acciones
{u.is_priority && } {u.username}
{u.max_connections} {u.expiry_date ? new Date(u.expiry_date).toLocaleDateString('es-ES') : '—'} {u.is_active ? 'Activo' : 'Inactivo'}
No hay usuarios
) }