Show saved-video counts (not channel counts) in the Saved page sidebar
GET /api/feed/saved-counts aggregates, per category, how many videos have
a completed download (using each video's latest job only, same rule as
the existing ?downloaded=true feed filter). CategoryNav gains an optional
categoryCounts override so Feed/Channels keep showing channel_count while
Saved shows this instead.
Query key ['feed', 'saved-counts'] deliberately nests under 'feed' so
DownloadButton's existing invalidateQueries({queryKey: ['feed']}) on
download/delete refreshes these counts too, with no extra wiring.
1 new backend test (77 total).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
44f81f0f0d
commit
90eb012e0d
5 changed files with 85 additions and 18 deletions
|
|
@ -217,3 +217,13 @@ export function getFeed(
|
|||
const suffix = qs.toString() ? `?${qs.toString()}` : ''
|
||||
return request<{ items: FeedVideoDto[]; next_cursor: string | null }>(`/api/feed${suffix}`)
|
||||
}
|
||||
|
||||
export interface SavedCounts {
|
||||
all: number
|
||||
uncategorized: number
|
||||
categories: Record<string, number>
|
||||
}
|
||||
|
||||
export function getSavedCounts() {
|
||||
return request<SavedCounts>('/api/feed/saved-counts')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ interface Props {
|
|||
onChange: (filter: CategoryFilter) => void
|
||||
allCount?: number
|
||||
uncategorizedCount?: number
|
||||
// Overrides each category's displayed count (channel_count by default) --
|
||||
// e.g. the Saved page shows saved-video counts instead.
|
||||
categoryCounts?: Record<number, number>
|
||||
}
|
||||
|
||||
function CategoryNav({ categories, value, onChange, allCount, uncategorizedCount }: Props) {
|
||||
function CategoryNav({ categories, value, onChange, allCount, uncategorizedCount, categoryCounts }: Props) {
|
||||
return (
|
||||
<nav className="category-nav">
|
||||
<button className={value === 'all' ? 'active' : ''} onClick={() => onChange('all')}>
|
||||
|
|
@ -28,7 +31,9 @@ function CategoryNav({ categories, value, onChange, allCount, uncategorizedCount
|
|||
onClick={() => onChange(category.id)}
|
||||
>
|
||||
{category.name}
|
||||
<span className="category-nav-count">{category.channel_count}</span>
|
||||
<span className="category-nav-count">
|
||||
{categoryCounts ? categoryCounts[category.id] ?? 0 : category.channel_count}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getFeed, listCategories, listChannels } from '../api/client'
|
||||
import { getFeed, getSavedCounts, listCategories } from '../api/client'
|
||||
import CategoryNav, { type CategoryFilter } from '../components/CategoryNav'
|
||||
import VideoCard from '../components/VideoCard'
|
||||
import { useState } from 'react'
|
||||
|
|
@ -11,12 +11,13 @@ function Saved() {
|
|||
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
|
||||
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
|
||||
// Keyed under 'feed' so DownloadButton's existing invalidateQueries({
|
||||
// queryKey: ['feed'] }) on download/delete also refreshes these counts.
|
||||
const savedCountsQuery = useQuery({ queryKey: ['feed', 'saved-counts'], queryFn: getSavedCounts })
|
||||
const savedCounts = savedCountsQuery.data
|
||||
const categoryCounts = Object.fromEntries(
|
||||
Object.entries(savedCounts?.categories ?? {}).map(([id, count]) => [Number(id), count]),
|
||||
)
|
||||
|
||||
const feedQuery = useInfiniteQuery({
|
||||
queryKey: ['feed', 'saved', filter],
|
||||
|
|
@ -49,8 +50,9 @@ function Saved() {
|
|||
categories={categories}
|
||||
value={filter}
|
||||
onChange={setFilter}
|
||||
allCount={allChannels.length}
|
||||
uncategorizedCount={uncategorizedCount}
|
||||
allCount={savedCounts?.all}
|
||||
uncategorizedCount={savedCounts?.uncategorized}
|
||||
categoryCounts={categoryCounts}
|
||||
/>
|
||||
|
||||
<div className="content">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue