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.
build / backend (push) Successful in 2m26s
build / frontend (push) Successful in 1m5s
tests / backend-tests (push) Successful in 2m27s

This commit is contained in:
Leonid Pershin
2026-07-26 16:17:24 +03:00
parent 8fa4ea02fe
commit 2387223f0b
9 changed files with 297 additions and 82 deletions
@@ -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">