Refactor components to enhance code clarity and maintainability by updating prop types to use Readonly for better immutability. Adjust Result class methods for consistency, and streamline query functions in ChannelDetail and other components to improve performance and readability.
This commit is contained in:
@@ -25,13 +25,13 @@ export function ApplyDialog({
|
||||
pending,
|
||||
onApply,
|
||||
onClose,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
utcOffsetMinutes: number
|
||||
pending: boolean
|
||||
onApply: () => void
|
||||
onClose: () => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const { data, isFetching } = useQuery({
|
||||
queryKey: qk.channels.diff(channelId),
|
||||
|
||||
@@ -13,13 +13,13 @@ export function BumperBackgroundField({
|
||||
backgroundImageId,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
templateId: string
|
||||
backgroundImageId: string | null
|
||||
onChanged: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [galleryOpen, setGalleryOpen] = useState(false)
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ export function BumperCard({
|
||||
bare,
|
||||
onSaved,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channel: ChannelDto
|
||||
bare?: boolean
|
||||
onSaved: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [bumpersEnabled, setBumpersEnabled] = useState(channel.bumpersEnabled)
|
||||
const [bumper, setBumper] = useState<BumperSettings>(channel.bumper)
|
||||
|
||||
@@ -15,7 +15,7 @@ export function BumperFileUpload({
|
||||
clear,
|
||||
onSaved,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
templateId: string
|
||||
kind: string
|
||||
@@ -27,7 +27,7 @@ export function BumperFileUpload({
|
||||
clear: (id: string, templateId: string) => Promise<void>
|
||||
onSaved: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const inputId = `bumper-${kind}-${templateId}`
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ export function BumperPreviewPlayer({
|
||||
templateId,
|
||||
variants,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
templateId: string
|
||||
variants: BumperTextVariantDto[]
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [ready, setReady] = useState(false)
|
||||
const [bust, setBust] = useState(0)
|
||||
|
||||
@@ -26,12 +26,12 @@ export function BumperTemplateEditor({
|
||||
template,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
template: BumperTemplateDto
|
||||
onChanged: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState(template.name)
|
||||
|
||||
@@ -16,14 +16,14 @@ export function BumperVariantEditor({
|
||||
canRemove,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
templateId: string
|
||||
variant: BumperTextVariantDto
|
||||
canRemove: boolean
|
||||
onChanged: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [form, setForm] = useState({
|
||||
name: variant.name,
|
||||
|
||||
@@ -9,14 +9,14 @@ export function CollapsibleCard({
|
||||
bare = false,
|
||||
contentClassName,
|
||||
children,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
title: string
|
||||
defaultOpen?: boolean
|
||||
/** Без своего заголовка и сворачивания — когда карточка и так лежит во вкладке с этим названием. */
|
||||
bare?: boolean
|
||||
contentClassName?: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
}>) {
|
||||
const [open, setOpen] = useState(defaultOpen)
|
||||
|
||||
if (bare)
|
||||
|
||||
@@ -19,11 +19,11 @@ export function EntryTraceDialog({
|
||||
entryId,
|
||||
utcOffsetMinutes,
|
||||
onClose,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
entryId: string
|
||||
utcOffsetMinutes: number
|
||||
onClose: () => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const { data } = useQuery({
|
||||
queryKey: qk.entries.trace(entryId),
|
||||
@@ -99,7 +99,7 @@ function strategySummary(data: EntryTraceDto, t: Translate) {
|
||||
])
|
||||
}
|
||||
|
||||
function Row({ label, children }: { label: string; children: React.ReactNode }) {
|
||||
function Row({ label, children }: Readonly<{ label: string; children: React.ReactNode }>) {
|
||||
return (
|
||||
<>
|
||||
<dt className="text-muted-foreground">{label}</dt>
|
||||
|
||||
@@ -38,13 +38,13 @@ export function GridTab({
|
||||
templateError,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
template: ScheduleTemplateDto | undefined
|
||||
templateError: unknown
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [draft, setDraft] = useState<SlotDraft | null>(null)
|
||||
const [activeLayerId, setActiveLayerId] = useState<string | null>(null)
|
||||
@@ -141,17 +141,17 @@ export function GridTab({
|
||||
slot,
|
||||
weekday,
|
||||
startMinutes,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
slot: SlotDto
|
||||
weekday: number
|
||||
startMinutes: number
|
||||
}) => updateSlot(slot.id, { ...toSlotBody(slot), weekday, targetStart: toTime(startMinutes) }),
|
||||
}>) => updateSlot(slot.id, { ...toSlotBody(slot), weekday, targetStart: toTime(startMinutes) }),
|
||||
onSuccess: onChanged,
|
||||
onError,
|
||||
})
|
||||
|
||||
const resizeSlotMutation = useMutation({
|
||||
mutationFn: ({ slot, minutes }: { slot: SlotDto; minutes: number }) =>
|
||||
mutationFn: ({ slot, minutes }: Readonly<{ slot: SlotDto; minutes: number }>) =>
|
||||
updateSlot(slot.id, { ...toSlotBody(slot), targetDurationMinutes: minutes }),
|
||||
onSuccess: onChanged,
|
||||
onError,
|
||||
@@ -159,7 +159,7 @@ export function GridTab({
|
||||
|
||||
/** Копирование дня: слоты «каждый день» не копируются — они и так есть во всех колонках. */
|
||||
const copyDayMutation = useMutation({
|
||||
mutationFn: async ({ from, to }: { from: number; to: number[] }) => {
|
||||
mutationFn: async ({ from, to }: Readonly<{ from: number; to: number[] }>) => {
|
||||
const sources = (template?.layers ?? []).flatMap((layer) =>
|
||||
layer.slots.filter((slot) => slot.weekday === from).map((slot) => ({ layer, slot })),
|
||||
)
|
||||
|
||||
@@ -44,14 +44,14 @@ export function JunctionElementDialog({
|
||||
onClose,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
junctionId: string
|
||||
element: JunctionElementDto
|
||||
bumperTemplates: BumperTemplateDto[]
|
||||
onClose: () => void
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [body, setBody] = useState<JunctionElementBody>(() => toBody(element))
|
||||
const { data: groups } = useQuery({ queryKey: qk.groups.all, queryFn: listGroups })
|
||||
|
||||
@@ -86,13 +86,13 @@ export function JunctionsCard({
|
||||
bare,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channel: ChannelDto
|
||||
template: ScheduleTemplateDto | undefined
|
||||
bare?: boolean
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [newName, setNewName] = useState('')
|
||||
|
||||
@@ -186,13 +186,13 @@ function JunctionChain({
|
||||
groups,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
junction: JunctionTemplateDto
|
||||
channel: ChannelDto
|
||||
groups: GroupSummaryDto[] | undefined
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState<string | null>(null)
|
||||
const [dragged, setDragged] = useState<string | null>(null)
|
||||
|
||||
@@ -29,12 +29,12 @@ export function LayerApplicabilityDialog({
|
||||
onClose,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
layer: GridLayerDto
|
||||
onClose: () => void
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState(layer.name)
|
||||
const [weekdays, setWeekdays] = useState<number[]>(layer.applicability?.weekdays ?? [])
|
||||
@@ -199,12 +199,12 @@ function Section({
|
||||
onAdd,
|
||||
empty,
|
||||
children,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
title: string
|
||||
onAdd: () => void
|
||||
empty: boolean
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -228,11 +228,11 @@ function MonthDay({
|
||||
value,
|
||||
prefix,
|
||||
onChange,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
value: AnnualRange
|
||||
prefix: 'from' | 'to'
|
||||
onChange: (part: Partial<AnnualRange>) => void
|
||||
}) {
|
||||
}>) {
|
||||
const month = prefix === 'from' ? value.fromMonth : value.toMonth
|
||||
const day = prefix === 'from' ? value.fromDay : value.toDay
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ export function RulesCard({
|
||||
bare,
|
||||
onChanged,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
template: ScheduleTemplateDto
|
||||
bare?: boolean
|
||||
onChanged: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [windows, setWindows] = useState<WindowRow[]>(() =>
|
||||
toRows(template.rules?.maxAudienceByTime ?? []),
|
||||
|
||||
@@ -56,7 +56,7 @@ export function ScheduleGrid({
|
||||
onMoveSlot,
|
||||
onResizeSlot,
|
||||
onCopyDay,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
template: ScheduleTemplateDto
|
||||
selectedSlotId: string | null
|
||||
/** Дата, на которую смотрим сетку («показать 25 декабря»); null — все слои разом. */
|
||||
@@ -67,7 +67,7 @@ export function ScheduleGrid({
|
||||
onMoveSlot: (slot: SlotDto, weekday: number, startMinutes: number) => void
|
||||
onResizeSlot: (slot: SlotDto, durationMinutes: number) => void
|
||||
onCopyDay: (fromWeekday: number) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const dayStart = template.dayStartTime.slice(0, 5)
|
||||
const dayStartMinutes = minutesOf(dayStart)
|
||||
@@ -280,7 +280,7 @@ export function LayerList({
|
||||
onToggle,
|
||||
onReorder,
|
||||
onEditApplicability,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
template: ScheduleTemplateDto
|
||||
activeLayerId: string | null
|
||||
viewDate: string | null
|
||||
@@ -289,7 +289,7 @@ export function LayerList({
|
||||
onToggle: (layer: GridLayerDto) => void
|
||||
onReorder: (layerIdsTopFirst: string[]) => void
|
||||
onEditApplicability: (layer: GridLayerDto) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [dragged, setDragged] = useState<string | null>(null)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Badge } from '@/shared/ui/badge'
|
||||
import { formatTime } from '../lib/format'
|
||||
|
||||
/** Что стоит в строке расписания: реклама, заставка-переход или программа с номером серии. */
|
||||
function EntryLabel({ entry }: { entry: ScheduleEntryDto }) {
|
||||
function EntryLabel({ entry }: Readonly<{ entry: ScheduleEntryDto }>) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (entry.kind === 'Ad') return <Badge variant="muted">{t('air.ad')}</Badge>
|
||||
@@ -35,7 +35,7 @@ function EntryLabel({ entry }: { entry: ScheduleEntryDto }) {
|
||||
}
|
||||
|
||||
/** «· S02E05» либо «· серия N» — что удалось распознать; ничего, если ни того ни другого нет. */
|
||||
function EpisodeSuffix({ entry }: { entry: ScheduleEntryDto }) {
|
||||
function EpisodeSuffix({ entry }: Readonly<{ entry: ScheduleEntryDto }>) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (entry.seasonEpisode)
|
||||
@@ -54,10 +54,10 @@ function EpisodeSuffix({ entry }: { entry: ScheduleEntryDto }) {
|
||||
export function SchedulePreview({
|
||||
entries,
|
||||
onShowTrace,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
entries: ScheduleEntryDto[]
|
||||
onShowTrace: (entryId: string) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
if (entries.length === 0)
|
||||
return <p className="text-muted-foreground">{t('admin.channels.noSchedule')}</p>
|
||||
|
||||
@@ -16,13 +16,13 @@ export function SettingsCard({
|
||||
bare,
|
||||
onSaved,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channel: ChannelDto
|
||||
readyAssets: { id: string; originalFileName: string }[]
|
||||
bare?: boolean
|
||||
onSaved: () => void
|
||||
onError: (e: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [name, setName] = useState(channel.name)
|
||||
const [isEnabled, setIsEnabled] = useState(channel.isEnabled)
|
||||
|
||||
@@ -68,12 +68,12 @@ export function SlotInspector({
|
||||
draft,
|
||||
onClose,
|
||||
onChanged,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
draft: SlotDraft
|
||||
onClose: () => void
|
||||
onChanged: () => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [body, setBody] = useState<SlotBody>(() =>
|
||||
draft.slot ? toSlotBody(draft.slot) : emptyBody(draft.defaults),
|
||||
|
||||
@@ -14,11 +14,11 @@ export function TemplateIssues({
|
||||
channelId,
|
||||
slotsById,
|
||||
onGoToSlot,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channelId: string
|
||||
slotsById: Map<string, SlotDto>
|
||||
onGoToSlot: (slot: SlotDto) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const { data: issues } = useQuery({
|
||||
queryKey: qk.channels.issues(channelId),
|
||||
@@ -53,11 +53,11 @@ function IssueRow({
|
||||
issue,
|
||||
slot,
|
||||
onGoToSlot,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
issue: TemplateIssueDto
|
||||
slot: SlotDto | undefined
|
||||
onGoToSlot: (slot: SlotDto) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const Icon = issue.severity === 'Error' ? CircleAlert : AlertTriangle
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const PROGRAMME_KINDS = new Set<PlannedItemKind>(['Program', 'Fallback', 'SignOf
|
||||
* Предпросмотр (см. 6.4): прогон генератора по текущим правилам без записи и без продвижения
|
||||
* курсоров. Заставки приходят резервом известной длины — реальный рендер только при применении.
|
||||
*/
|
||||
export function TemplatePreview({ channelId }: { channelId: string }) {
|
||||
export function TemplatePreview({ channelId }: Readonly<{ channelId: string }>) {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [days, setDays] = useState(1)
|
||||
@@ -97,7 +97,7 @@ export function TemplatePreview({ channelId }: { channelId: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
function Programme({ preview }: { preview: SchedulePreviewDto }) {
|
||||
function Programme({ preview }: Readonly<{ preview: SchedulePreviewDto }>) {
|
||||
const { t } = useTranslation()
|
||||
const items = preview.items.filter((i) => PROGRAMME_KINDS.has(i.kind))
|
||||
|
||||
@@ -143,7 +143,7 @@ function loadByHour(preview: SchedulePreviewDto): { hour: Date; minutes: number
|
||||
.map(([hour, minutes]) => ({ hour: new Date(hour), minutes }))
|
||||
}
|
||||
|
||||
function Tape({ preview }: { preview: SchedulePreviewDto }) {
|
||||
function Tape({ preview }: Readonly<{ preview: SchedulePreviewDto }>) {
|
||||
const { t } = useTranslation()
|
||||
const load = useMemo(() => loadByHour(preview), [preview])
|
||||
const peak = Math.max(1, ...load.map((l) => l.minutes))
|
||||
@@ -180,7 +180,10 @@ function Tape({ preview }: { preview: SchedulePreviewDto }) {
|
||||
)
|
||||
}
|
||||
|
||||
function TapeRow({ item, preview }: { item: PreviewItemDto; preview: SchedulePreviewDto }) {
|
||||
function TapeRow({
|
||||
item,
|
||||
preview,
|
||||
}: Readonly<{ item: PreviewItemDto; preview: SchedulePreviewDto }>) {
|
||||
const { t } = useTranslation()
|
||||
const minutes =
|
||||
(new Date(item.endsAtUtc).getTime() - new Date(item.startsAtUtc).getTime()) / 60_000
|
||||
@@ -200,7 +203,7 @@ function TapeRow({ item, preview }: { item: PreviewItemDto; preview: SchedulePre
|
||||
}
|
||||
|
||||
/** Предупреждения, сгруппированные по виду: десять однотипных строк читаются как одна проблема. */
|
||||
function Problems({ preview }: { preview: SchedulePreviewDto }) {
|
||||
function Problems({ preview }: Readonly<{ preview: SchedulePreviewDto }>) {
|
||||
const { t } = useTranslation()
|
||||
// Ключ строки — «вид + порядковый номер внутри вида»: у предупреждения нет своего id, а текст
|
||||
// повторяется (одна и та же причина на разных слотах), и позиция здесь — единственное, что его
|
||||
@@ -250,7 +253,7 @@ function Problems({ preview }: { preview: SchedulePreviewDto }) {
|
||||
* Тепловая карта повторов: матрица «шоу × вещательные сутки», яркость — число показов. Сразу видно,
|
||||
* что один фильм крутится четыре раза за неделю.
|
||||
*/
|
||||
function RepeatHeatmap({ preview }: { preview: SchedulePreviewDto }) {
|
||||
function RepeatHeatmap({ preview }: Readonly<{ preview: SchedulePreviewDto }>) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { days, rows } = useMemo(() => {
|
||||
|
||||
@@ -21,12 +21,12 @@ export function ViewerCard({
|
||||
bare,
|
||||
onSaved,
|
||||
onError,
|
||||
}: {
|
||||
}: Readonly<{
|
||||
channel: ChannelDto
|
||||
bare?: boolean
|
||||
onSaved: () => void
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const [viewer, setViewer] = useState<ViewerSettings>(channel.viewer)
|
||||
const [galleryOpen, setGalleryOpen] = useState(false)
|
||||
|
||||
Reference in New Issue
Block a user