myYouTube/frontend/src/pages/Channels.tsx
vrubelroman 44f81f0f0d Show channel counts in the left CategoryNav sidebar (Feed/Channels/Saved)
Same "channels per category" counts as the categories management page,
now next to each button in the sidebar used across all three pages.
Categories already carry channel_count; "Все"/"Без категории" counts are
derived client-side from one extra unfiltered channels query, reused
across the three pages via a shared ['channels', 'all-for-counts'] key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 20:38:06 +00:00

121 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMemo, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { getSyncStatus, listCategories, listChannels, logout, syncSubscriptions } from '../api/client'
import ChannelCard from '../components/ChannelCard'
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
function Channels() {
const [search, setSearch] = useState('')
const [filter, setFilter] = useState<CategoryFilter>('all')
const queryClient = useQueryClient()
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
const allChannelsQuery = useQuery({
queryKey: ['channels', 'all-for-counts'],
queryFn: () => listChannels({ subscribed: true }),
})
const allChannels = allChannelsQuery.data ?? []
const uncategorizedCount = allChannels.filter((c) => c.category_ids.length === 0).length
const channelsQuery = useQuery({
queryKey: ['channels', filter],
queryFn: () =>
listChannels({
subscribed: true,
categoryId: typeof filter === 'number' ? filter : undefined,
uncategorized: filter === 'uncategorized',
}),
})
const filteredChannels = useMemo(() => {
const data = channelsQuery.data ?? []
if (!search.trim()) return data
const needle = search.trim().toLowerCase()
return data.filter((c) => c.title.toLowerCase().includes(needle))
}, [channelsQuery.data, search])
const syncStatusQuery = useQuery({
queryKey: ['sync-status'],
queryFn: getSyncStatus,
refetchInterval: (query) => (query.state.data?.subscriptions.running ? 1500 : false),
})
const syncMutation = useMutation({
mutationFn: syncSubscriptions,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['channels'] })
queryClient.invalidateQueries({ queryKey: ['sync-status'] })
},
})
const logoutMutation = useMutation({
mutationFn: logout,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['auth-status'] })
},
})
const running = syncStatusQuery.data?.subscriptions.running
const categories = categoriesQuery.data ?? []
return (
<div className="channels-screen">
<header>
<h1>Каналы</h1>
<div className="actions">
<Link to="/">Лента</Link>
<Link to="/saved">Сохранённые</Link>
<Link to="/categories">Категории</Link>
<button onClick={() => syncMutation.mutate()} disabled={running}>
{running ? 'Синхронизация...' : 'Обновить сейчас'}
</button>
<button onClick={() => logoutMutation.mutate()}>Выйти</button>
</div>
</header>
{syncMutation.isError && <p className="error">{(syncMutation.error as Error).message}</p>}
{syncStatusQuery.data?.subscriptions.status === 'failed' && (
<p className="error">Последняя синхронизация завершилась ошибкой: {syncStatusQuery.data.subscriptions.error}</p>
)}
<div className="layout">
<CategoryNav
categories={categories}
value={filter}
onChange={setFilter}
allCount={allChannels.length}
uncategorizedCount={uncategorizedCount}
/>
<div className="content">
<input
type="text"
placeholder="Поиск по названию канала"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
{channelsQuery.isLoading && <p>Загрузка...</p>}
{channelsQuery.isError && <p className="error">Не удалось загрузить каналы</p>}
<ul className="channel-list">
{filteredChannels.map((channel) => (
<ChannelCard key={channel.id} channel={channel} categories={categories} />
))}
</ul>
{channelsQuery.data && channelsQuery.data.length === 0 && (
<p>Каналы не найдены{filter === 'all' ? '. Нажми "Обновить сейчас".' : ' в этой категории.'}</p>
)}
{channelsQuery.data && channelsQuery.data.length > 0 && filteredChannels.length === 0 && (
<p>Ничего не найдено по запросу "{search}".</p>
)}
</div>
</div>
</div>
)
}
export default Channels