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>
This commit is contained in:
parent
cdb1c2ded2
commit
44f81f0f0d
5 changed files with 62 additions and 6 deletions
|
|
@ -63,6 +63,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-nav button {
|
.category-nav button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -76,6 +80,14 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category-nav-count {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,20 @@ interface Props {
|
||||||
categories: CategoryDto[]
|
categories: CategoryDto[]
|
||||||
value: CategoryFilter
|
value: CategoryFilter
|
||||||
onChange: (filter: CategoryFilter) => void
|
onChange: (filter: CategoryFilter) => void
|
||||||
|
allCount?: number
|
||||||
|
uncategorizedCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function CategoryNav({ categories, value, onChange }: Props) {
|
function CategoryNav({ categories, value, onChange, allCount, uncategorizedCount }: Props) {
|
||||||
return (
|
return (
|
||||||
<nav className="category-nav">
|
<nav className="category-nav">
|
||||||
<button className={value === 'all' ? 'active' : ''} onClick={() => onChange('all')}>
|
<button className={value === 'all' ? 'active' : ''} onClick={() => onChange('all')}>
|
||||||
Все
|
Все
|
||||||
|
{allCount != null && <span className="category-nav-count">{allCount}</span>}
|
||||||
</button>
|
</button>
|
||||||
<button className={value === 'uncategorized' ? 'active' : ''} onClick={() => onChange('uncategorized')}>
|
<button className={value === 'uncategorized' ? 'active' : ''} onClick={() => onChange('uncategorized')}>
|
||||||
Без категории
|
Без категории
|
||||||
|
{uncategorizedCount != null && <span className="category-nav-count">{uncategorizedCount}</span>}
|
||||||
</button>
|
</button>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -24,6 +28,7 @@ function CategoryNav({ categories, value, onChange }: Props) {
|
||||||
onClick={() => onChange(category.id)}
|
onClick={() => onChange(category.id)}
|
||||||
>
|
>
|
||||||
{category.name}
|
{category.name}
|
||||||
|
<span className="category-nav-count">{category.channel_count}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ function Channels() {
|
||||||
|
|
||||||
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
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({
|
const channelsQuery = useQuery({
|
||||||
queryKey: ['channels', filter],
|
queryKey: ['channels', filter],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
|
|
@ -74,7 +81,13 @@ function Channels() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<CategoryNav categories={categories} value={filter} onChange={setFilter} />
|
<CategoryNav
|
||||||
|
categories={categories}
|
||||||
|
value={filter}
|
||||||
|
onChange={setFilter}
|
||||||
|
allCount={allChannels.length}
|
||||||
|
uncategorizedCount={uncategorizedCount}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMutation, useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { getFeed, getSyncStatus, listCategories, logout, syncVideos } from '../api/client'
|
import { getFeed, getSyncStatus, listCategories, listChannels, logout, syncVideos } from '../api/client'
|
||||||
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
|
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
|
||||||
import VideoCard from '../components/VideoCard'
|
import VideoCard from '../components/VideoCard'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
@ -12,6 +12,13 @@ function Feed() {
|
||||||
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
||||||
const categories = categoriesQuery.data ?? []
|
const categories = categoriesQuery.data ?? []
|
||||||
|
|
||||||
|
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 feedQuery = useInfiniteQuery({
|
const feedQuery = useInfiniteQuery({
|
||||||
queryKey: ['feed', filter],
|
queryKey: ['feed', filter],
|
||||||
queryFn: ({ pageParam }) =>
|
queryFn: ({ pageParam }) =>
|
||||||
|
|
@ -69,7 +76,13 @@ function Feed() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<CategoryNav categories={categories} value={filter} onChange={setFilter} />
|
<CategoryNav
|
||||||
|
categories={categories}
|
||||||
|
value={filter}
|
||||||
|
onChange={setFilter}
|
||||||
|
allCount={allChannels.length}
|
||||||
|
uncategorizedCount={uncategorizedCount}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{feedQuery.isLoading && <p>Загрузка...</p>}
|
{feedQuery.isLoading && <p>Загрузка...</p>}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { getFeed, listCategories } from '../api/client'
|
import { getFeed, listCategories, listChannels } from '../api/client'
|
||||||
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
|
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
|
||||||
import VideoCard from '../components/VideoCard'
|
import VideoCard from '../components/VideoCard'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
@ -11,6 +11,13 @@ function Saved() {
|
||||||
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
||||||
const categories = categoriesQuery.data ?? []
|
const categories = categoriesQuery.data ?? []
|
||||||
|
|
||||||
|
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 feedQuery = useInfiniteQuery({
|
const feedQuery = useInfiniteQuery({
|
||||||
queryKey: ['feed', 'saved', filter],
|
queryKey: ['feed', 'saved', filter],
|
||||||
queryFn: ({ pageParam }) =>
|
queryFn: ({ pageParam }) =>
|
||||||
|
|
@ -38,7 +45,13 @@ function Saved() {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<CategoryNav categories={categories} value={filter} onChange={setFilter} />
|
<CategoryNav
|
||||||
|
categories={categories}
|
||||||
|
value={filter}
|
||||||
|
onChange={setFilter}
|
||||||
|
allCount={allChannels.length}
|
||||||
|
uncategorizedCount={uncategorizedCount}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{feedQuery.isLoading && <p>Загрузка...</p>}
|
{feedQuery.isLoading && <p>Загрузка...</p>}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue