Enhance channel and settings functionalities: introduce viewer settings in ChannelEndpoints, update SiteSettings to include channel number toggling, and refactor related data structures. Implement new endpoints for validating templates and diffing scheduling changes, improving overall user experience and configuration management.
build / backend (push) Successful in 1m54s
build / frontend (push) Successful in 43s
tests / backend-tests (push) Successful in 2m4s

This commit is contained in:
Leonid Pershin
2026-07-26 14:50:32 +03:00
parent 8494eb5e6a
commit f699576582
54 changed files with 5857 additions and 1631 deletions
@@ -0,0 +1,88 @@
import { useQuery } from '@tanstack/react-query'
import { AlertTriangle, CircleAlert } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import type { SlotDto, TemplateIssueDto } from '@/shared/api/types'
import { cn } from '@/shared/lib/cn'
import { getTemplateIssues } from '../api'
/**
* Проверки по правилам (см. 5.1). Считаются на сервере по шаблону, без прогона генератора, поэтому
* показываются прямо в редакторе и обновляются вместе с сеткой.
*/
export function TemplateIssues({
channelId,
slotsById,
onGoToSlot,
}: {
channelId: string
slotsById: Map<string, SlotDto>
onGoToSlot: (slot: SlotDto) => void
}) {
const { t } = useTranslation()
const { data: issues } = useQuery({
queryKey: ['admin', 'channels', channelId, 'issues'],
queryFn: () => getTemplateIssues(channelId),
})
if (!issues || issues.length === 0) return null
const errors = issues.filter((i) => i.severity === 'Error')
const warnings = issues.filter((i) => i.severity === 'Warning')
return (
<div className="flex flex-col gap-1 rounded-md border border-border px-3 py-2 text-xs">
<span className="font-semibold uppercase tracking-wide text-muted-foreground">
{t('admin.channels.issues', { errors: errors.length, warnings: warnings.length })}
</span>
<ul className="flex flex-col gap-0.5">
{[...errors, ...warnings].map((issue, index) => (
<IssueRow
key={`${issue.kind}-${index}`}
issue={issue}
slot={issue.slotId ? slotsById.get(issue.slotId) : undefined}
onGoToSlot={onGoToSlot}
/>
))}
</ul>
</div>
)
}
function IssueRow({
issue,
slot,
onGoToSlot,
}: {
issue: TemplateIssueDto
slot: SlotDto | undefined
onGoToSlot: (slot: SlotDto) => void
}) {
const { t } = useTranslation()
const Icon = issue.severity === 'Error' ? CircleAlert : AlertTriangle
return (
<li className="flex items-start gap-1.5">
<Icon
className={cn(
'mt-0.5 h-3 w-3 shrink-0',
issue.severity === 'Error' ? 'text-red-500' : 'text-amber-500',
)}
/>
<span className="min-w-0 flex-1">
<span className="text-muted-foreground">
{t(`admin.channels.issueKinds.${issue.kind}`)}:{' '}
</span>
{issue.details}
</span>
{slot && (
<button
type="button"
className="shrink-0 text-primary hover:underline"
onClick={() => onGoToSlot(slot)}
>
{t('admin.channels.goToSlot')}
</button>
)}
</li>
)
}