Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
build / backend (push) Successful in 7m40s
build / frontend (push) Failing after 39s
tests / backend-tests (push) Successful in 6m9s

This commit is contained in:
Leonid Pershin
2026-07-26 13:32:13 +03:00
parent c4ef954dea
commit 66040a8841
272 changed files with 27944 additions and 8699 deletions
@@ -3,7 +3,7 @@ import { Link } from '@tanstack/react-router'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { HttpError } from '@/shared/api/client'
import type { ShowAudience, ShowKind } from '@/shared/api/types'
import { SHOW_AUDIENCES, type ShowAudience, type ShowKind } from '@/shared/api/types'
import { Badge } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { Input } from '@/shared/ui/input'
@@ -11,6 +11,7 @@ import { Pager } from '@/shared/ui/pager'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
import { SortHeader, sortRows, useTableSort } from '@/shared/ui/sortable'
import { toast } from '@/shared/ui/toast-store'
import { listGenres } from '@/features/admin/genres/api'
import { createShow, deleteShow, listShows } from './api'
const PAGE_SIZE = 20
@@ -30,7 +31,13 @@ export function ShowsPanel() {
toggle(key)
}
const { data, isLoading } = useQuery({ queryKey: ['admin', 'shows'], queryFn: listShows })
// Фильтр по жанру — серверный: в списке видно только основной жанр, а отбирать нужно и по остальным.
const [genreFilter, setGenreFilter] = useState('all')
const { data: genres } = useQuery({ queryKey: ['admin', 'genres'], queryFn: listGenres })
const { data, isLoading } = useQuery({
queryKey: ['admin', 'shows', { genreId: genreFilter }],
queryFn: () => listShows(genreFilter === 'all' ? undefined : genreFilter),
})
// Список шоу обычно умещается в одну загрузку — фильтруем, сортируем и листаем на клиенте.
const filtered = useMemo(() => {
@@ -46,6 +53,7 @@ export function ShowsPanel() {
name: (s) => s.name.toLowerCase(),
kind: (s) => s.kind,
audience: (s) => s.audience,
genre: (s) => (s.primaryGenre ?? '').toLowerCase(),
seasons: (s) => s.seasonCount,
episodes: (s) => s.episodeCount,
})
@@ -104,9 +112,11 @@ export function ShowsPanel() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="General">{t('admin.shows.audiences.General')}</SelectItem>
<SelectItem value="Kids">{t('admin.shows.audiences.Kids')}</SelectItem>
<SelectItem value="Adult">{t('admin.shows.audiences.Adult')}</SelectItem>
{SHOW_AUDIENCES.map((value) => (
<SelectItem key={value} value={value}>
{t(`admin.shows.audiences.${value}`)}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
@@ -118,15 +128,36 @@ export function ShowsPanel() {
</Button>
</div>
<Input
className="max-w-xs"
placeholder={t('common.search')}
value={query}
onChange={(e) => {
setPage(1)
setQuery(e.target.value)
}}
/>
<div className="flex flex-wrap items-center gap-2">
<Input
className="max-w-xs"
placeholder={t('common.search')}
value={query}
onChange={(e) => {
setPage(1)
setQuery(e.target.value)
}}
/>
<Select
value={genreFilter}
onValueChange={(v) => {
setPage(1)
setGenreFilter(v)
}}
>
<SelectTrigger className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{t('admin.shows.allGenres')}</SelectItem>
{(genres ?? []).map((genre) => (
<SelectItem key={genre.id} value={genre.id}>
{genre.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="crt-panel overflow-x-auto rounded-md">
<table className="w-full text-sm">
@@ -150,6 +181,12 @@ export function ShowsPanel() {
sort={sort}
onToggle={sortColumn}
/>
<SortHeader
label={t('admin.shows.genre')}
sortKey="genre"
sort={sort}
onToggle={sortColumn}
/>
<SortHeader
label={t('admin.shows.seasons')}
sortKey="seasons"
@@ -168,7 +205,7 @@ export function ShowsPanel() {
<tbody>
{isLoading && (
<tr>
<td className="px-4 py-3 text-muted-foreground" colSpan={6}>
<td className="px-4 py-3 text-muted-foreground" colSpan={7}>
{t('common.loading')}
</td>
</tr>
@@ -190,6 +227,7 @@ export function ShowsPanel() {
<td className="px-4 py-2">
<Badge variant="muted">{t(`admin.shows.audiences.${show.audience}`)}</Badge>
</td>
<td className="px-4 py-2 text-muted-foreground">{show.primaryGenre ?? '—'}</td>
<td className="px-4 py-2 text-muted-foreground">{show.seasonCount}</td>
<td className="px-4 py-2 text-muted-foreground">{show.episodeCount}</td>
<td className="px-4 py-2">