Refactor group deletion logic to handle fallback group references
ci / build-backend (push) Successful in 1m35s
ci / build-frontend (push) Successful in 1m6s
ci / tests (push) Successful in 2m2s
ci / sonar (push) Successful in 4m29s

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:
Leonid Pershin
2026-07-28 14:01:05 +03:00
parent f924891f3b
commit 3908b6e722
7 changed files with 108 additions and 33 deletions
@@ -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')}