Implement pagination for candidates and episodes in ShowDetail component: add Pager component for both lists, update state management for page navigation, and ensure safe page handling during filtering and updates.
This commit is contained in:
@@ -8,6 +8,7 @@ import type { MediaAssetDto } from '@/shared/api/types'
|
|||||||
import { Badge } from '@/shared/ui/badge'
|
import { Badge } from '@/shared/ui/badge'
|
||||||
import { Button } from '@/shared/ui/button'
|
import { Button } from '@/shared/ui/button'
|
||||||
import { Input } from '@/shared/ui/input'
|
import { Input } from '@/shared/ui/input'
|
||||||
|
import { Pager } from '@/shared/ui/pager'
|
||||||
import { toast } from '@/shared/ui/toast-store'
|
import { toast } from '@/shared/ui/toast-store'
|
||||||
import { listMedia } from '@/features/admin/media/api'
|
import { listMedia } from '@/features/admin/media/api'
|
||||||
import {
|
import {
|
||||||
@@ -23,12 +24,16 @@ import { addEpisode, getShow, removeEpisode } from './api'
|
|||||||
|
|
||||||
type Candidate = { asset: MediaAssetDto; parsed: ParsedEpisode }
|
type Candidate = { asset: MediaAssetDto; parsed: ParsedEpisode }
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
export function ShowDetail({ showId }: { showId: string }) {
|
export function ShowDetail({ showId }: { showId: string }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const [filter, setFilter] = useState('')
|
const [filter, setFilter] = useState('')
|
||||||
const [deselected, setDeselected] = useState<Set<string>>(new Set())
|
const [deselected, setDeselected] = useState<Set<string>>(new Set())
|
||||||
const [adding, setAdding] = useState<{ current: number; total: number } | null>(null)
|
const [adding, setAdding] = useState<{ current: number; total: number } | null>(null)
|
||||||
|
const [candPage, setCandPage] = useState(1)
|
||||||
|
const [epPage, setEpPage] = useState(1)
|
||||||
|
|
||||||
const { data: show, isLoading } = useQuery({
|
const { data: show, isLoading } = useQuery({
|
||||||
queryKey: ['admin', 'shows', showId],
|
queryKey: ['admin', 'shows', showId],
|
||||||
@@ -84,6 +89,16 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
|
|
||||||
const isSingle = show.kind === 'Single'
|
const isSingle = show.kind === 'Single'
|
||||||
const canAdd = !isSingle || show.episodes.length === 0
|
const canAdd = !isSingle || show.episodes.length === 0
|
||||||
|
|
||||||
|
// Постраничный вывод длинных списков (кандидаты на добавление и сами серии). Страницу зажимаем в
|
||||||
|
// допустимый диапазон — чтобы после удаления/фильтрации не застрять на пустой странице.
|
||||||
|
const candTotalPages = Math.max(1, Math.ceil(candidates.length / PAGE_SIZE))
|
||||||
|
const candPageSafe = Math.min(candPage, candTotalPages)
|
||||||
|
const candItems = candidates.slice((candPageSafe - 1) * PAGE_SIZE, candPageSafe * PAGE_SIZE)
|
||||||
|
const epTotalPages = Math.max(1, Math.ceil(show.episodes.length / PAGE_SIZE))
|
||||||
|
const epPageSafe = Math.min(epPage, epTotalPages)
|
||||||
|
const epOffset = (epPageSafe - 1) * PAGE_SIZE
|
||||||
|
const epItems = show.episodes.slice(epOffset, epOffset + PAGE_SIZE)
|
||||||
const toggle = (id: string) =>
|
const toggle = (id: string) =>
|
||||||
setDeselected((prev) => {
|
setDeselected((prev) => {
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
@@ -151,7 +166,10 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
className="max-w-md"
|
className="max-w-md"
|
||||||
placeholder={t('admin.shows.filterAssets')}
|
placeholder={t('admin.shows.filterAssets')}
|
||||||
value={filter}
|
value={filter}
|
||||||
onChange={(e) => setFilter(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setCandPage(1)
|
||||||
|
setFilter(e.target.value)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Button size="sm" variant="outline" onClick={() => setDeselected(new Set())}>
|
<Button size="sm" variant="outline" onClick={() => setDeselected(new Set())}>
|
||||||
{t('admin.shows.selectAll')}
|
{t('admin.shows.selectAll')}
|
||||||
@@ -175,7 +193,7 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
<p className="px-4 py-3 text-sm text-muted-foreground">{t('admin.shows.noMatches')}</p>
|
<p className="px-4 py-3 text-sm text-muted-foreground">{t('admin.shows.noMatches')}</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="divide-y divide-border text-sm">
|
<ul className="divide-y divide-border text-sm">
|
||||||
{candidates.map(({ asset, parsed }) => {
|
{candItems.map(({ asset, parsed }) => {
|
||||||
const label = formatSeasonEpisode(parsed)
|
const label = formatSeasonEpisode(parsed)
|
||||||
return (
|
return (
|
||||||
<li key={asset.id}>
|
<li key={asset.id}>
|
||||||
@@ -194,6 +212,7 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<Pager page={candPageSafe} totalPages={candTotalPages} onChange={setCandPage} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -209,7 +228,7 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{show.episodes.map((episode, index) => {
|
{epItems.map((episode, index) => {
|
||||||
const parsed =
|
const parsed =
|
||||||
episode.season != null && episode.episode != null
|
episode.season != null && episode.episode != null
|
||||||
? { season: episode.season, episode: episode.episode }
|
? { season: episode.season, episode: episode.episode }
|
||||||
@@ -217,7 +236,7 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
const label = formatSeasonEpisode(parsed)
|
const label = formatSeasonEpisode(parsed)
|
||||||
return (
|
return (
|
||||||
<tr key={episode.id} className="border-b border-border last:border-0">
|
<tr key={episode.id} className="border-b border-border last:border-0">
|
||||||
<td className="px-4 py-2 text-muted-foreground">{index + 1}</td>
|
<td className="px-4 py-2 text-muted-foreground">{epOffset + index + 1}</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{episode.stillImageId && (
|
{episode.stillImageId && (
|
||||||
@@ -270,6 +289,7 @@ export function ShowDetail({ showId }: { showId: string }) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<Pager page={epPageSafe} totalPages={epTotalPages} onChange={setEpPage} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user