Add audience categorization for shows: implement ShowAudience type, update related API endpoints, and enhance frontend components for audience selection and display. Modify data structures to support audience information in show creation and retrieval processes.
This commit is contained in:
@@ -4,11 +4,12 @@ import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChevronLeft } from 'lucide-react'
|
||||
import { HttpError } from '@/shared/api/client'
|
||||
import type { MediaAssetDto } from '@/shared/api/types'
|
||||
import type { MediaAssetDto, ShowAudience } from '@/shared/api/types'
|
||||
import { Badge } from '@/shared/ui/badge'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { Pager } from '@/shared/ui/pager'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { listAllMedia } from '@/features/admin/media/api'
|
||||
import {
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
import { formatDuration } from '@/features/admin/media/MediaPanel'
|
||||
import { ShowMetadataCard } from './ShowMetadataCard'
|
||||
import { imageUrl } from '@/features/admin/images/api'
|
||||
import { addEpisode, getShow, removeEpisode } from './api'
|
||||
import { addEpisode, getShow, removeEpisode, setShowAudience } from './api'
|
||||
|
||||
type Candidate = { asset: MediaAssetDto; parsed: ParsedEpisode }
|
||||
|
||||
@@ -48,6 +49,12 @@ export function ShowDetail({ showId }: { showId: string }) {
|
||||
const onError = (error: unknown) =>
|
||||
toast.error(error instanceof HttpError ? error.detail : t('common.error'))
|
||||
|
||||
const audienceMutation = useMutation({
|
||||
mutationFn: (audience: ShowAudience) => setShowAudience(showId, audience),
|
||||
onSuccess: invalidate,
|
||||
onError,
|
||||
})
|
||||
|
||||
const removeMutation = useMutation({
|
||||
mutationFn: (episodeId: string) => removeEpisode(showId, episodeId),
|
||||
onSuccess: invalidate,
|
||||
@@ -141,6 +148,19 @@ export function ShowDetail({ showId }: { showId: string }) {
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<h2 className="crt-glow text-xl font-semibold">{show.name}</h2>
|
||||
<Badge variant="muted">{t(`admin.shows.kinds.${show.kind}`)}</Badge>
|
||||
<Select
|
||||
value={show.audience}
|
||||
onValueChange={(v) => audienceMutation.mutate(v as ShowAudience)}
|
||||
>
|
||||
<SelectTrigger className="h-7 w-32">
|
||||
<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>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{show.kind === 'Series' && (
|
||||
<Badge variant="muted">
|
||||
{t('admin.shows.seasons')}: {seasons.length}
|
||||
|
||||
@@ -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 { ShowKind } from '@/shared/api/types'
|
||||
import type { ShowAudience, ShowKind } from '@/shared/api/types'
|
||||
import { Badge } from '@/shared/ui/badge'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
@@ -21,6 +21,7 @@ export function ShowsPanel() {
|
||||
const [name, setName] = useState('')
|
||||
const [originalName, setOriginalName] = useState('')
|
||||
const [kind, setKind] = useState<ShowKind>('Series')
|
||||
const [audience, setAudience] = useState<ShowAudience>('General')
|
||||
const [query, setQuery] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const { sort, toggle } = useTableSort('name', false)
|
||||
@@ -44,6 +45,7 @@ export function ShowsPanel() {
|
||||
return sortRows(matched, sort, {
|
||||
name: (s) => s.name.toLowerCase(),
|
||||
kind: (s) => s.kind,
|
||||
audience: (s) => s.audience,
|
||||
seasons: (s) => s.seasonCount,
|
||||
episodes: (s) => s.episodeCount,
|
||||
})
|
||||
@@ -56,7 +58,12 @@ export function ShowsPanel() {
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
createShow({ name: name.trim(), kind, originalName: originalName.trim() || undefined }),
|
||||
createShow({
|
||||
name: name.trim(),
|
||||
kind,
|
||||
originalName: originalName.trim() || undefined,
|
||||
audience,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
setName('')
|
||||
setOriginalName('')
|
||||
@@ -92,6 +99,16 @@ export function ShowsPanel() {
|
||||
<SelectItem value="Single">{t('admin.shows.kinds.Single')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={audience} onValueChange={(v) => setAudience(v as ShowAudience)}>
|
||||
<SelectTrigger className="w-40">
|
||||
<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>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={!name.trim() || createMutation.isPending}
|
||||
@@ -127,6 +144,12 @@ export function ShowsPanel() {
|
||||
sort={sort}
|
||||
onToggle={sortColumn}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t('admin.shows.audience')}
|
||||
sortKey="audience"
|
||||
sort={sort}
|
||||
onToggle={sortColumn}
|
||||
/>
|
||||
<SortHeader
|
||||
label={t('admin.shows.seasons')}
|
||||
sortKey="seasons"
|
||||
@@ -145,7 +168,7 @@ export function ShowsPanel() {
|
||||
<tbody>
|
||||
{isLoading && (
|
||||
<tr>
|
||||
<td className="px-4 py-3 text-muted-foreground" colSpan={5}>
|
||||
<td className="px-4 py-3 text-muted-foreground" colSpan={6}>
|
||||
{t('common.loading')}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -164,6 +187,9 @@ export function ShowsPanel() {
|
||||
<td className="px-4 py-2">
|
||||
<Badge variant="muted">{t(`admin.shows.kinds.${show.kind}`)}</Badge>
|
||||
</td>
|
||||
<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.seasonCount}</td>
|
||||
<td className="px-4 py-2 text-muted-foreground">{show.episodeCount}</td>
|
||||
<td className="px-4 py-2">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { apiRequest } from '@/shared/api/client'
|
||||
import type {
|
||||
CreatedIdResponse,
|
||||
MetadataCandidate,
|
||||
ShowAudience,
|
||||
ShowDto,
|
||||
ShowKind,
|
||||
ShowSummaryDto,
|
||||
@@ -20,10 +21,15 @@ export function createShow(body: {
|
||||
kind: ShowKind
|
||||
description?: string
|
||||
originalName?: string
|
||||
audience?: ShowAudience
|
||||
}) {
|
||||
return apiRequest<CreatedIdResponse>('/admin/shows', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function setShowAudience(id: string, audience: ShowAudience) {
|
||||
return apiRequest<void>(`/admin/shows/${id}/audience`, { method: 'PUT', body: { audience } })
|
||||
}
|
||||
|
||||
export function renameShow(id: string, name: string) {
|
||||
return apiRequest<void>(`/admin/shows/${id}/name`, { method: 'PUT', body: { name } })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user