Enhance EntryTraceDialog component by refactoring data display logic into dedicated summary functions for improved readability and maintainability. Update GridTab to streamline checkbox state management with a new toggle function. Refactor RulesCard to simplify window removal logic. Adjust CollectionsPanel, GenresPanel, GroupsPanel, RolesPanel, ShowsPanel, and UsersPanel to import sorting utilities from a centralized location, enhancing code organization. Update ThemeProvider to utilize a shared theme context for better consistency across the application.
ci / build-backend (push) Successful in 2m15s
ci / build-frontend (push) Successful in 50s
ci / tests (push) Successful in 2m34s
ci / sonar (push) Successful in 6m13s

This commit is contained in:
Leonid Pershin
2026-07-27 00:25:11 +03:00
parent f4926848d7
commit 419aff54fa
26 changed files with 355 additions and 251 deletions
@@ -1,6 +1,7 @@
import { useQuery } from '@tanstack/react-query'
import { qk } from '@/shared/api/query-keys'
import { useTranslation } from 'react-i18next'
import type { EntryTraceDto } from '@/shared/api/types'
import {
Dialog,
DialogContent,
@@ -42,52 +43,11 @@ export function EntryTraceDialog({
{data && (
<dl className="grid grid-cols-[110px_1fr] gap-x-3 gap-y-1.5 text-sm">
<Row label={t('admin.channels.traceLayer')}>
{data.layerName
? `${data.layerName}${data.layerPriority !== null ? ` (${t('admin.channels.priority')} ${data.layerPriority})` : ''}`
: null}
</Row>
<Row label={t('admin.channels.traceSlot')}>
{data.slotTitle
? [
data.slotTitle,
data.slotWeekday === null
? t('admin.channels.everyDay')
: t(`admin.channels.weekdays.${data.slotWeekday}`),
data.slotTargetStart?.slice(0, 5),
data.slotDurationMinutes ? `${data.slotDurationMinutes} мин` : null,
data.driftMinutes !== 0
? t('admin.channels.traceDrift', { minutes: data.driftMinutes })
: null,
data.snapped ? t('admin.channels.traceSnapped') : null,
]
.filter(Boolean)
.join(' · ')
: null}
</Row>
<Row label={t('admin.channels.traceGroup')}>
{data.groupName
? `${data.groupName}${data.groupItemCount !== null ? ` (${data.groupItemCount})` : ''}`
: null}
</Row>
<Row label={t('admin.channels.traceLayer')}>{layerSummary(data, t)}</Row>
<Row label={t('admin.channels.traceSlot')}>{slotSummary(data, t)}</Row>
<Row label={t('admin.channels.traceGroup')}>{groupSummary(data)}</Row>
<Row label={t('admin.channels.traceCollection')}>{data.collectionName}</Row>
<Row label={t('admin.channels.traceStrategy')}>
{data.strategy
? [
t(`admin.channels.strategies.${data.strategy}`),
data.cooldownDays
? t('admin.channels.traceCooldown', { days: data.cooldownDays })
: null,
data.candidatesAfterCooldown !== null
? t('admin.channels.traceCandidates', {
count: data.candidatesAfterCooldown,
})
: null,
]
.filter(Boolean)
.join(' · ')
: null}
</Row>
<Row label={t('admin.channels.traceStrategy')}>{strategySummary(data, t)}</Row>
<Row label={t('admin.channels.traceJunction')}>{data.junctionName}</Row>
</dl>
)}
@@ -96,6 +56,49 @@ export function EntryTraceDialog({
)
}
type Translate = ReturnType<typeof useTranslation>['t']
/** Склейка непустых частей строки трейса; пусто — значит строка не заполнена (покажем «—»). */
const joinParts = (parts: (string | null | undefined)[]) => parts.filter(Boolean).join(' · ') || null
function layerSummary(data: EntryTraceDto, t: Translate) {
if (!data.layerName) return null
const priority =
data.layerPriority !== null ? ` (${t('admin.channels.priority')} ${data.layerPriority})` : ''
return `${data.layerName}${priority}`
}
function slotSummary(data: EntryTraceDto, t: Translate) {
if (!data.slotTitle) return null
return joinParts([
data.slotTitle,
data.slotWeekday === null
? t('admin.channels.everyDay')
: t(`admin.channels.weekdays.${data.slotWeekday}`),
data.slotTargetStart?.slice(0, 5),
data.slotDurationMinutes ? `${data.slotDurationMinutes} мин` : null,
data.driftMinutes !== 0 ? t('admin.channels.traceDrift', { minutes: data.driftMinutes }) : null,
data.snapped ? t('admin.channels.traceSnapped') : null,
])
}
function groupSummary(data: EntryTraceDto) {
if (!data.groupName) return null
const count = data.groupItemCount !== null ? ` (${data.groupItemCount})` : ''
return `${data.groupName}${count}`
}
function strategySummary(data: EntryTraceDto, t: Translate) {
if (!data.strategy) return null
return joinParts([
t(`admin.channels.strategies.${data.strategy}`),
data.cooldownDays ? t('admin.channels.traceCooldown', { days: data.cooldownDays }) : null,
data.candidatesAfterCooldown !== null
? t('admin.channels.traceCandidates', { count: data.candidatesAfterCooldown })
: null,
])
}
function Row({ label, children }: { label: string; children: React.ReactNode }) {
return (
<>
@@ -55,6 +55,9 @@ export function GridTab({
const [copyTargets, setCopyTargets] = useState<number[]>([])
const [copyFromChannel, setCopyFromChannel] = useState('')
const toggleCopyTarget = (day: number, checked: boolean) =>
setCopyTargets((current) => (checked ? [...current, day] : current.filter((d) => d !== day)))
const { data: channels } = useQuery({ queryKey: qk.channels.all, queryFn: listChannels })
const addLayerMutation = useMutation({
@@ -318,13 +321,7 @@ export function GridTab({
<input
type="checkbox"
checked={copyTargets.includes(day)}
onChange={(e) =>
setCopyTargets((current) =>
e.target.checked
? [...current, day]
: current.filter((d) => d !== day),
)
}
onChange={(e) => toggleCopyTarget(day, e.target.checked)}
/>
{t(`admin.channels.weekdays.${day}`)}
</label>
@@ -87,6 +87,9 @@ export function RulesCard({
onError,
})
const removeWindow = (key: string) =>
setWindows((current) => current.filter((row) => row.key !== key))
const patchWindow = (key: string, part: Partial<AudienceWindow>) =>
setWindows((current) =>
current.map((row) =>
@@ -156,7 +159,7 @@ export function RulesCard({
<Button
size="sm"
variant="ghost"
onClick={() => setWindows((c) => c.filter((row) => row.key !== key))}
onClick={() => removeWindow(key)}
>
<Trash2 className="h-4 w-4" />
</Button>