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, DialogHeader, DialogTitle } from '@/shared/ui/dialog' import { getEntryTrace } from '../api' import { formatChannelTime } from '../lib/format' /** * «Почему это здесь» (см. 6.5): цепочка происхождения записи. Трейс пишется в момент генерации — * восстановить его потом нельзя, поэтому у старых записей часть строк будет пустой. */ export function EntryTraceDialog({ entryId, utcOffsetMinutes, onClose, }: Readonly<{ entryId: string utcOffsetMinutes: number onClose: () => void }>) { const { t } = useTranslation() const { data } = useQuery({ queryKey: qk.entries.trace(entryId), queryFn: () => getEntryTrace(entryId), }) return ( !open && onClose()}> {data ? `${data.showName ?? '—'} · ${formatChannelTime(data.startsAtUtc, utcOffsetMinutes)}` : t('common.loading')} {data && (
{layerSummary(data, t)} {slotSummary(data, t)} {groupSummary(data)} {data.collectionName} {strategySummary(data, t)} {data.junctionName}
)}
) } type Translate = ReturnType['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 }: Readonly<{ label: string; children: React.ReactNode }>) { return ( <>
{label}
{children || '—'}
) }