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.
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user