Refactor group deletion logic to handle fallback group references
Updated the DeleteGroupCommandHandler to release fallback group references during group deletion, ensuring that groups can be removed even when used as fallback in templates. Adjusted related tests to verify the correct behavior of the deletion process, confirming that fallback references are cleared appropriately. Enhanced localization strings to provide clearer context for fallback group functionality in both English and Russian.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { Plus, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { listGroups } from '@/features/admin/groups/api'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import {
|
||||
SHOW_AUDIENCES,
|
||||
type AudienceWindow,
|
||||
@@ -55,6 +57,11 @@ export function RulesCard({
|
||||
const [breakCap, setBreakCap] = useState(() => template.rules?.maxBreakMinutesPerHour ?? 0)
|
||||
const [genreCap, setGenreCap] = useState(() => template.rules?.maxGenreSharePercent ?? 0)
|
||||
const [fallbackCap, setFallbackCap] = useState(() => template.rules?.maxFallbackSharePercent ?? 0)
|
||||
const [fallbackGroupId, setFallbackGroupId] = useState(() => template.fallbackGroupId)
|
||||
|
||||
// Аварийную группу проставляет и автосборка сетки, поэтому список нужен всегда: без него
|
||||
// ссылка остаётся невидимой, а снять её нечем — и группа не удаляется.
|
||||
const { data: groups } = useQuery({ queryKey: qk.groups.all, queryFn: listGroups })
|
||||
|
||||
useEffect(() => {
|
||||
setWindows(toRows(template.rules?.maxAudienceByTime ?? []))
|
||||
@@ -64,6 +71,7 @@ export function RulesCard({
|
||||
setBreakCap(template.rules?.maxBreakMinutesPerHour ?? 0)
|
||||
setGenreCap(template.rules?.maxGenreSharePercent ?? 0)
|
||||
setFallbackCap(template.rules?.maxFallbackSharePercent ?? 0)
|
||||
setFallbackGroupId(template.fallbackGroupId)
|
||||
}, [template])
|
||||
|
||||
const save = useMutation({
|
||||
@@ -78,7 +86,7 @@ export function RulesCard({
|
||||
}
|
||||
return updateTemplate(template.id, {
|
||||
name: template.name,
|
||||
fallbackGroupId: template.fallbackGroupId,
|
||||
fallbackGroupId,
|
||||
defaultJunctionId: template.defaultJunctionId,
|
||||
rules,
|
||||
})
|
||||
@@ -203,6 +211,25 @@ export function RulesCard({
|
||||
<p className="text-xs text-muted-foreground">{t('admin.channels.repeatLimitHint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 border-t border-border pt-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.channels.fallbackGroup')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={fallbackGroupId ?? ''}
|
||||
onChange={(e) => setFallbackGroupId(e.target.value || null)}
|
||||
>
|
||||
<option value="">{t('admin.channels.fallbackGroupNone')}</option>
|
||||
{(groups ?? []).map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
{group.name} · {group.itemCount}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.channels.fallbackGroupHint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 border-t border-border pt-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{t('admin.channels.postChecks')}
|
||||
|
||||
@@ -666,6 +666,10 @@ export const en = {
|
||||
'This channel grid will be replaced with the chosen channel grid. Continue?',
|
||||
templateCopied: 'Copied: {{layers}} layers, {{slots}} slots',
|
||||
copyDroppedBumpers: 'Breaks left without a bumper block: {{count}} — set them up by hand',
|
||||
fallbackGroup: 'Fallback group',
|
||||
fallbackGroupNone: 'None — channel filler only',
|
||||
fallbackGroupHint:
|
||||
'What fills a pause when even the background layer is empty. An empty grid plays it for the whole air. Set by grid auto-build unless you pick your own.',
|
||||
postChecks: 'Post-checks',
|
||||
breakLimit: 'Breaks per hour cap, min',
|
||||
genreShare: 'Genre share per day cap, %',
|
||||
|
||||
@@ -661,6 +661,10 @@ export const ru = {
|
||||
'Текущая сетка этого канала будет заменена сеткой выбранного канала. Продолжить?',
|
||||
templateCopied: 'Скопировано: слоёв {{layers}}, слотов {{slots}}',
|
||||
copyDroppedBumpers: 'Врезок без блока заставки: {{count}} — донастройте руками',
|
||||
fallbackGroup: 'Аварийная группа',
|
||||
fallbackGroupNone: 'Нет — только филлер канала',
|
||||
fallbackGroupHint:
|
||||
'Чем закрывается пауза, если пуст и фоновый слой. Её же играет пустая сетка — весь эфир, пока слотов нет. Проставляется автосборкой сетки, если своя не выбрана.',
|
||||
postChecks: 'Пост-проверки',
|
||||
breakLimit: 'Потолок врезок в час, мин',
|
||||
genreShare: 'Потолок доли жанра за сутки, %',
|
||||
|
||||
Reference in New Issue
Block a user