Refactor AdminUserEndpoints and MediaEndpoints to include default parameter values for pagination and filtering, improving API usability. Update ManualInboxDialog to enhance show selection logic with auto-detection based on file names, and improve user feedback with new translations. Refactor PostgresFixture for better container management in integration tests.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { ChevronDown, ChevronRight, Folder } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { listShows } from '@/features/admin/shows/api'
|
||||
import { HttpError } from '@/shared/api/client'
|
||||
@@ -22,6 +22,7 @@ import { toast } from '@/shared/ui/toast-store'
|
||||
import { importManualInbox, listManualInbox } from './api'
|
||||
import { compareParsed, formatSeasonEpisode, isValidRegex, parseEpisodeName } from './episode-parse'
|
||||
import { buildEpisodeRegex, findNumbers, REGEX_PRESETS } from './episode-regex'
|
||||
import { matchShowByName } from './match-show'
|
||||
|
||||
/** Байты → «1,4 ГБ»: в ручном разборе размер — главный ориентир, что это за файл. */
|
||||
function formatSize(bytes: number): string {
|
||||
@@ -48,6 +49,8 @@ export function ManualInboxDialog({ onClose }: { onClose: () => void }) {
|
||||
const queryClient = useQueryClient()
|
||||
const [selected, setSelected] = useState<string[]>([])
|
||||
const [showId, setShowId] = useState('')
|
||||
// Ручной выбор шоу отключает автоопределение: перебивать решение человека нельзя.
|
||||
const [showPicked, setShowPicked] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
const [seasonStr, setSeasonStr] = useState('')
|
||||
const [regexStr, setRegexStr] = useState('')
|
||||
@@ -120,6 +123,25 @@ export function ManualInboxDialog({ onClose }: { onClose: () => void }) {
|
||||
parts.push({ text: sample.name.slice(cursor), number: null })
|
||||
return parts
|
||||
}, [sample])
|
||||
/**
|
||||
* Автоопределение шоу по имени релиза — то же, что в загрузке в шоу. Сначала пробуем имя файла,
|
||||
* затем имя папки: в раздачах название сериала обычно есть и там, и там («Mr.Pickles.S01.1080p»).
|
||||
*/
|
||||
const detectedShowId = useMemo(
|
||||
() =>
|
||||
shows && sample
|
||||
? (matchShowByName(sample.name, shows) ?? matchShowByName(sample.folder, shows))
|
||||
: undefined,
|
||||
[shows, sample],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (showPicked || showId || !detectedShowId) return
|
||||
setShowId(detectedShowId)
|
||||
}, [detectedShowId, showPicked, showId])
|
||||
|
||||
const autoDetected = !showPicked && !!detectedShowId && showId === detectedShowId
|
||||
|
||||
const recognized = selectable.filter(
|
||||
(f) => parsedByPath.get(f.relativePath)?.episode != null,
|
||||
).length
|
||||
@@ -180,7 +202,13 @@ export function ManualInboxDialog({ onClose }: { onClose: () => void }) {
|
||||
{/* Шоу — первое решение в этом диалоге: остальные поля лишь помогают разложить файлы. */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.media.manualShow')}</Label>
|
||||
<Select value={showId} onValueChange={setShowId}>
|
||||
<Select
|
||||
value={showId}
|
||||
onValueChange={(value) => {
|
||||
setShowPicked(true)
|
||||
setShowId(value)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('admin.media.manualPickShow')} />
|
||||
</SelectTrigger>
|
||||
@@ -192,6 +220,11 @@ export function ManualInboxDialog({ onClose }: { onClose: () => void }) {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{autoDetected && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.media.manualDetected')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
|
||||
@@ -212,6 +212,7 @@ export function MediaPanel() {
|
||||
sortKey="status"
|
||||
sort={sort}
|
||||
onToggle={sortColumn}
|
||||
className="whitespace-nowrap"
|
||||
/>
|
||||
<SortHeader
|
||||
label={t('admin.media.duration')}
|
||||
@@ -274,7 +275,7 @@ function MediaRow({ asset, onDelete }: { asset: MediaAssetDto; onDelete: () => v
|
||||
return (
|
||||
<tr className="border-b border-border last:border-0">
|
||||
<td className="px-4 py-2">{asset.originalFileName}</td>
|
||||
<td className="px-4 py-2">
|
||||
<td className="whitespace-nowrap px-4 py-2">
|
||||
<Badge variant={statusVariant[asset.status]} title={asset.errorMessage ?? undefined}>
|
||||
{t(`admin.media.statuses.${asset.status}`)}
|
||||
</Badge>
|
||||
|
||||
Reference in New Issue
Block a user