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,102 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/shared/ui/dialog'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
import { getApplyDiff } from '../api'
|
||||
import { formatChannelTime } from '../lib/format'
|
||||
|
||||
/**
|
||||
* Диф перед применением (см. 6.6). Изменения в ближайшие сутки подсвечены отдельно — это самая
|
||||
* частая причина случайного ущерба: у зрителя из-под носа уезжает то, что он уже видит в программе.
|
||||
*/
|
||||
export function ApplyDialog({
|
||||
channelId,
|
||||
utcOffsetMinutes,
|
||||
pending,
|
||||
onApply,
|
||||
onClose,
|
||||
}: {
|
||||
channelId: string
|
||||
utcOffsetMinutes: number
|
||||
pending: boolean
|
||||
onApply: () => void
|
||||
onClose: () => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { data, isFetching } = useQuery({
|
||||
queryKey: ['admin', 'channels', channelId, 'diff'],
|
||||
queryFn: () => getApplyDiff(channelId),
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
})
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.channels.apply')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isFetching || !data
|
||||
? t('common.loading')
|
||||
: t('admin.channels.diffSummary', { total: data.total, changed: data.changed })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{data && (
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
{data.changedSoon > 0 && (
|
||||
<p className="flex items-center gap-1.5 text-amber-500">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0" />
|
||||
{t('admin.channels.diffSoon', { count: data.changedSoon })}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{data.changes.length === 0 ? (
|
||||
<p className="text-muted-foreground">{t('admin.channels.diffNoChanges')}</p>
|
||||
) : (
|
||||
<ul className="max-h-80 divide-y divide-border overflow-y-auto text-xs">
|
||||
{data.changes.map((change, index) => (
|
||||
<li
|
||||
key={`${change.startsAtUtc}-${index}`}
|
||||
className={cn('flex items-center gap-2 py-1', change.soon && 'text-amber-500')}
|
||||
>
|
||||
<span className="w-24 shrink-0 tabular-nums text-muted-foreground">
|
||||
{formatChannelTime(change.startsAtUtc, utcOffsetMinutes)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{change.before ?? '—'}</span>
|
||||
<span className="shrink-0 text-muted-foreground">→</span>
|
||||
<span className="min-w-0 flex-1 truncate">{change.after ?? '—'}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{data.changed > data.changes.length && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.channels.andMore', { count: data.changed - data.changes.length })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button size="sm" variant="outline" onClick={onClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button size="sm" disabled={pending} onClick={onApply}>
|
||||
{t('admin.channels.apply')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user