|
import React, { useState, useMemo } from 'react' |
|
import { useChannels } from '../contexts/ChannelContext' |
|
import { useKeyboard } from '../hooks/useKeyboard' |
|
import { Search, Heart, Tv, RefreshCw } from 'lucide-react' |
|
import { Channel } from '../types/channel' |
|
|
|
interface ChannelListProps { |
|
onChannelSelect?: () => void |
|
} |
|
|
|
export default function ChannelList({ onChannelSelect }: ChannelListProps) { |
|
const { |
|
channels, |
|
categories, |
|
currentChannel, |
|
favorites, |
|
searchTerm, |
|
selectedCategory, |
|
setCurrentChannel, |
|
setSearchTerm, |
|
setSelectedCategory, |
|
toggleFavorite, |
|
refreshChannels, |
|
isLoading |
|
} = useChannels() |
|
|
|
const [selectedIndex, setSelectedIndex] = useState(0) |
|
|
|
const filteredChannels = useMemo(() => { |
|
let filtered = channels |
|
|
|
|
|
if (selectedCategory === 'favorites') { |
|
filtered = filtered.filter(channel => favorites.includes(channel.id)) |
|
} else if (selectedCategory !== 'all') { |
|
filtered = filtered.filter(channel => channel.category === selectedCategory) |
|
} |
|
|
|
|
|
if (searchTerm) { |
|
filtered = filtered.filter(channel => |
|
channel.name.toLowerCase().includes(searchTerm.toLowerCase()) |
|
) |
|
} |
|
|
|
return filtered |
|
}, [channels, selectedCategory, favorites, searchTerm]) |
|
|
|
useKeyboard({ |
|
onArrowUp: () => setSelectedIndex(prev => Math.max(0, prev - 1)), |
|
onArrowDown: () => setSelectedIndex(prev => Math.min(filteredChannels.length - 1, prev + 1)), |
|
onEnter: () => { |
|
if (filteredChannels[selectedIndex]) { |
|
handleChannelSelect(filteredChannels[selectedIndex]) |
|
} |
|
} |
|
}) |
|
|
|
const handleChannelSelect = (channel: Channel) => { |
|
setCurrentChannel(channel) |
|
onChannelSelect?.() |
|
} |
|
|
|
const handleCategoryChange = (category: string) => { |
|
setSelectedCategory(category) |
|
setSelectedIndex(0) |
|
} |
|
|
|
return ( |
|
<div className="h-full flex flex-col bg-gray-800"> |
|
{/* Búsqueda */} |
|
<div className="p-4 border-b border-gray-700"> |
|
<div className="relative"> |
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" /> |
|
<input |
|
type="text" |
|
value={searchTerm} |
|
onChange={(e) => setSearchTerm(e.target.value)} |
|
placeholder="Buscar canales..." |
|
className="input-field w-full pl-10 text-sm" |
|
/> |
|
</div> |
|
</div> |
|
|
|
{/* Categorías */} |
|
<div className="p-4 border-b border-gray-700"> |
|
<div className="flex items-center justify-between mb-2"> |
|
<h3 className="text-sm font-medium text-gray-300">Categorías</h3> |
|
<button |
|
onClick={refreshChannels} |
|
disabled={isLoading} |
|
className="p-1 hover:bg-gray-700 rounded transition-colors" |
|
title="Actualizar canales" |
|
> |
|
<RefreshCw className={`h-4 w-4 text-gray-400 ${isLoading ? 'animate-spin' : ''}`} /> |
|
</button> |
|
</div> |
|
|
|
<div className="space-y-1"> |
|
<button |
|
onClick={() => handleCategoryChange('all')} |
|
className={`w-full text-left px-2 py-1 rounded text-sm transition-colors ${ |
|
selectedCategory === 'all' |
|
? 'bg-primary-600 text-white' |
|
: 'text-gray-300 hover:bg-gray-700' |
|
}`} |
|
> |
|
Todos ({channels.length}) |
|
</button> |
|
|
|
<button |
|
onClick={() => handleCategoryChange('favorites')} |
|
className={`w-full text-left px-2 py-1 rounded text-sm transition-colors flex items-center space-x-2 ${ |
|
selectedCategory === 'favorites' |
|
? 'bg-primary-600 text-white' |
|
: 'text-gray-300 hover:bg-gray-700' |
|
}`} |
|
> |
|
<Heart className="h-3 w-3" /> |
|
<span>Favoritos ({favorites.length})</span> |
|
</button> |
|
|
|
{categories.map(category => ( |
|
<button |
|
key={category.name} |
|
onClick={() => handleCategoryChange(category.name)} |
|
className={`w-full text-left px-2 py-1 rounded text-sm transition-colors ${ |
|
selectedCategory === category.name |
|
? 'bg-primary-600 text-white' |
|
: 'text-gray-300 hover:bg-gray-700' |
|
}`} |
|
> |
|
{category.name} ({category.count}) |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
|
|
{/* Lista de canales */} |
|
<div className="flex-1 overflow-y-auto"> |
|
{filteredChannels.length === 0 ? ( |
|
<div className="p-4 text-center text-gray-400"> |
|
<Tv className="h-8 w-8 mx-auto mb-2 opacity-50" /> |
|
<p className="text-sm">No se encontraron canales</p> |
|
</div> |
|
) : ( |
|
<div className="p-2 space-y-1"> |
|
{filteredChannels.map((channel, index) => ( |
|
<div |
|
key={channel.id} |
|
onClick={() => handleChannelSelect(channel)} |
|
className={`channel-item ${ |
|
currentChannel?.id === channel.id ? 'active' : '' |
|
} ${index === selectedIndex ? 'ring-2 ring-primary-500' : ''}`} |
|
> |
|
<div className="flex-1 min-w-0"> |
|
<div className="flex items-center space-x-2"> |
|
{channel.logo && ( |
|
<img |
|
src={channel.logo} |
|
alt={channel.name} |
|
className="w-8 h-8 rounded object-cover flex-shrink-0" |
|
onError={(e) => { |
|
e.currentTarget.style.display = 'none' |
|
}} |
|
/> |
|
)} |
|
<div className="flex-1 min-w-0"> |
|
<p className="text-sm font-medium text-white truncate"> |
|
{channel.name} |
|
</p> |
|
<p className="text-xs text-gray-400 truncate"> |
|
{channel.category} |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<button |
|
onClick={(e) => { |
|
e.stopPropagation() |
|
toggleFavorite(channel.id) |
|
}} |
|
className="p-1 hover:bg-gray-600 rounded transition-colors" |
|
> |
|
<Heart |
|
className={`h-4 w-4 ${ |
|
favorites.includes(channel.id) |
|
? 'text-red-500 fill-current' |
|
: 'text-gray-400' |
|
}`} |
|
/> |
|
</button> |
|
</div> |
|
))} |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
) |
|
} |