Enhance collection and group suggestions to manage existing items visibility
Updated the CollectionSuggestions and GroupSuggestions components to include a toggle for displaying already created suggestions. Introduced new state management for handling visibility of existing items, along with localization updates for relevant UI strings in both English and Russian. This improves user experience by allowing admins to see both new and existing suggestions clearly.
This commit is contained in:
@@ -13,12 +13,16 @@ import { createCollectionFromSuggestion, suggestCollections } from './api'
|
|||||||
/**
|
/**
|
||||||
* Что стоит собрать во франшизу. Состав виден целиком до создания — по названиям иногда
|
* Что стоит собрать во франшизу. Состав виден целиком до создания — по названиям иногда
|
||||||
* промахивается, и проверить глазами должно быть проще, чем разбирать потом.
|
* промахивается, и проверить глазами должно быть проще, чем разбирать потом.
|
||||||
|
*
|
||||||
|
* Уже созданное по умолчанию скрыто: строка без кнопки — шум. Счётчик скрытого остаётся видимым,
|
||||||
|
* чтобы предложение не выглядело пропавшим бесследно.
|
||||||
*/
|
*/
|
||||||
export function CollectionSuggestions() {
|
export function CollectionSuggestions() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const onError = useApiError()
|
const onError = useApiError()
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
const [showExisting, setShowExisting] = useState(false)
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: qk.collections.suggestions,
|
queryKey: qk.collections.suggestions,
|
||||||
@@ -38,6 +42,10 @@ export function CollectionSuggestions() {
|
|||||||
|
|
||||||
if (isLoading || !data || data.length === 0) return null
|
if (isLoading || !data || data.length === 0) return null
|
||||||
|
|
||||||
|
const fresh = data.filter((s) => !s.alreadyExists)
|
||||||
|
const existing = data.length - fresh.length
|
||||||
|
const visible = showExisting ? data : fresh
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -48,14 +56,23 @@ export function CollectionSuggestions() {
|
|||||||
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||||
<Wand2 className="h-4 w-4" />
|
<Wand2 className="h-4 w-4" />
|
||||||
{t('admin.collections.suggestions.title')}
|
{t('admin.collections.suggestions.title')}
|
||||||
<Badge variant="muted">{data.filter((s) => !s.alreadyExists).length}</Badge>
|
<Badge variant="muted">{fresh.length}</Badge>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
<>
|
||||||
<p className="text-xs text-muted-foreground">{t('admin.collections.suggestions.hint')}</p>
|
<p className="text-xs text-muted-foreground">{t('admin.collections.suggestions.hint')}</p>
|
||||||
<ul className="crt-panel divide-y divide-border rounded-md text-sm">
|
{visible.length === 0 && (
|
||||||
{data.map((suggestion) => (
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('admin.collections.suggestions.allCreated')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<ul
|
||||||
|
className={`crt-panel divide-y divide-border rounded-md text-sm ${
|
||||||
|
visible.length === 0 ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{visible.map((suggestion) => (
|
||||||
<li key={suggestion.key} className="flex flex-col gap-1 px-4 py-2">
|
<li key={suggestion.key} className="flex flex-col gap-1 px-4 py-2">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<span className="min-w-0 flex-1 truncate font-medium">{suggestion.name}</span>
|
<span className="min-w-0 flex-1 truncate font-medium">{suggestion.name}</span>
|
||||||
@@ -88,6 +105,17 @@ export function CollectionSuggestions() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
{existing > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="self-start text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
|
||||||
|
onClick={() => setShowExisting((v) => !v)}
|
||||||
|
>
|
||||||
|
{showExisting
|
||||||
|
? t('admin.collections.suggestions.hideExisting')
|
||||||
|
: t('admin.collections.suggestions.showExisting', { count: existing })}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ import { DurationLabel } from './DurationLabel'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Что имеет смысл завести группой при нынешней библиотеке. Разбор делает сервер (жанры, типы,
|
* Что имеет смысл завести группой при нынешней библиотеке. Разбор делает сервер (жанры, типы,
|
||||||
* рейтинги, крупные сериалы), здесь — только показ и кнопка. Уже созданное остаётся в списке
|
* рейтинги, крупные сериалы), здесь — только показ и кнопка.
|
||||||
* помеченным: исчезнувшее без следа предложение читается как сбой, а «уже есть» объясняет,
|
*
|
||||||
* почему кнопки нет.
|
* Уже созданное по умолчанию скрыто: строка без кнопки — это шум, а список должен состоять из того,
|
||||||
|
* что можно сделать. Но и пропадать бесследно оно не должно, иначе непонятно, куда делось
|
||||||
|
* предложение, — поэтому счётчик скрытого виден и раскрывается.
|
||||||
*/
|
*/
|
||||||
export function GroupSuggestions() {
|
export function GroupSuggestions() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const onError = useApiError()
|
const onError = useApiError()
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
const [showExisting, setShowExisting] = useState(false)
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: qk.groups.suggestions,
|
queryKey: qk.groups.suggestions,
|
||||||
@@ -40,6 +43,10 @@ export function GroupSuggestions() {
|
|||||||
// Пока предлагать нечего (пустая библиотека или всё уже создано) — секции нет вовсе.
|
// Пока предлагать нечего (пустая библиотека или всё уже создано) — секции нет вовсе.
|
||||||
if (isLoading || !data || data.length === 0) return null
|
if (isLoading || !data || data.length === 0) return null
|
||||||
|
|
||||||
|
const fresh = data.filter((s) => !s.alreadyExists)
|
||||||
|
const existing = data.length - fresh.length
|
||||||
|
const visible = showExisting ? data : fresh
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
@@ -51,15 +58,24 @@ export function GroupSuggestions() {
|
|||||||
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||||
<Wand2 className="h-4 w-4" />
|
<Wand2 className="h-4 w-4" />
|
||||||
{t('admin.groups.suggestions.title')}
|
{t('admin.groups.suggestions.title')}
|
||||||
<Badge variant="muted">{data.filter((s) => !s.alreadyExists).length}</Badge>
|
<Badge variant="muted">{fresh.length}</Badge>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
<>
|
||||||
<p className="text-xs text-muted-foreground">{t('admin.groups.suggestions.hint')}</p>
|
<p className="text-xs text-muted-foreground">{t('admin.groups.suggestions.hint')}</p>
|
||||||
<ul className="crt-panel divide-y divide-border rounded-md text-sm">
|
{visible.length === 0 && (
|
||||||
{data.map((suggestion) => (
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('admin.groups.suggestions.allCreated')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<ul
|
||||||
|
className={`crt-panel divide-y divide-border rounded-md text-sm ${
|
||||||
|
visible.length === 0 ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{visible.map((suggestion) => (
|
||||||
<li key={suggestion.key} className="flex flex-wrap items-center gap-2 px-4 py-2">
|
<li key={suggestion.key} className="flex flex-wrap items-center gap-2 px-4 py-2">
|
||||||
<span className="min-w-0 flex-1 truncate font-medium">{suggestion.name}</span>
|
<span className="min-w-0 flex-1 truncate font-medium">{suggestion.name}</span>
|
||||||
<Badge variant="muted">
|
<Badge variant="muted">
|
||||||
@@ -90,6 +106,17 @@ export function GroupSuggestions() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
{existing > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="self-start text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
|
||||||
|
onClick={() => setShowExisting((v) => !v)}
|
||||||
|
>
|
||||||
|
{showExisting
|
||||||
|
? t('admin.groups.suggestions.hideExisting')
|
||||||
|
: t('admin.groups.suggestions.showExisting', { count: existing })}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ export const en = {
|
|||||||
stats: '{{shows}} shows · {{units}} units',
|
stats: '{{shows}} shows · {{units}} units',
|
||||||
exists: 'already exists',
|
exists: 'already exists',
|
||||||
created: 'Group created',
|
created: 'Group created',
|
||||||
|
allCreated: 'Every suggestion has been created already.',
|
||||||
|
showExisting: 'Show already created ({{count}})',
|
||||||
|
hideExisting: 'Hide already created',
|
||||||
kinds: {
|
kinds: {
|
||||||
Genre: 'genre',
|
Genre: 'genre',
|
||||||
ShowKind: 'kind',
|
ShowKind: 'kind',
|
||||||
@@ -172,6 +175,9 @@ export const en = {
|
|||||||
parts: '{{count}} parts',
|
parts: '{{count}} parts',
|
||||||
exists: 'already exists',
|
exists: 'already exists',
|
||||||
created: 'Collection created',
|
created: 'Collection created',
|
||||||
|
allCreated: 'Every suggestion has been created already.',
|
||||||
|
showExisting: 'Show already created ({{count}})',
|
||||||
|
hideExisting: 'Hide already created',
|
||||||
sources: { Metadata: 'from metadata', NameGuess: 'by names' },
|
sources: { Metadata: 'from metadata', NameGuess: 'by names' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ export const ru = {
|
|||||||
stats: '{{shows}} шоу · {{units}} ед.',
|
stats: '{{shows}} шоу · {{units}} ед.',
|
||||||
exists: 'уже есть',
|
exists: 'уже есть',
|
||||||
created: 'Группа создана',
|
created: 'Группа создана',
|
||||||
|
allCreated: 'Все предложения уже созданы.',
|
||||||
|
showExisting: 'Показать уже созданные ({{count}})',
|
||||||
|
hideExisting: 'Скрыть уже созданные',
|
||||||
kinds: {
|
kinds: {
|
||||||
Genre: 'жанр',
|
Genre: 'жанр',
|
||||||
ShowKind: 'тип',
|
ShowKind: 'тип',
|
||||||
@@ -172,6 +175,9 @@ export const ru = {
|
|||||||
parts: '{{count}} частей',
|
parts: '{{count}} частей',
|
||||||
exists: 'уже есть',
|
exists: 'уже есть',
|
||||||
created: 'Коллекция создана',
|
created: 'Коллекция создана',
|
||||||
|
allCreated: 'Все предложения уже созданы.',
|
||||||
|
showExisting: 'Показать уже созданные ({{count}})',
|
||||||
|
hideExisting: 'Скрыть уже созданные',
|
||||||
sources: { Metadata: 'из метаданных', NameGuess: 'по названиям' },
|
sources: { Metadata: 'из метаданных', NameGuess: 'по названиям' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user