Enhance channel settings to include icon image support for IPTV
Updated the Channel and related DTOs to incorporate an optional IconImageId property, allowing for the specification of a channel icon for external IPTV players. Modified the UpdateChannelSettings command and its handler to accommodate this new property. Adjusted frontend components to manage the icon image, including selection and display functionality. Updated localization files to support new UI elements related to the IPTV icon. This enhancement improves the user experience by providing a visual representation of channels in IPTV applications.
This commit is contained in:
@@ -43,6 +43,7 @@ type ChannelSettingsBody = {
|
||||
name: string
|
||||
isEnabled: boolean
|
||||
fillerAssetId: string | null
|
||||
iconImageId: string | null
|
||||
}
|
||||
|
||||
export function updateChannelSettings(id: string, body: ChannelSettingsBody) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { imageUrl } from '@/features/admin/images/api'
|
||||
import { ImageGallery } from '@/features/admin/images/ImageGallery'
|
||||
import type { ChannelDto } from '@/shared/api/types'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
@@ -27,6 +29,8 @@ export function SettingsCard({
|
||||
const [name, setName] = useState(channel.name)
|
||||
const [isEnabled, setIsEnabled] = useState(channel.isEnabled)
|
||||
const [fillerAssetId, setFillerAssetId] = useState(channel.fillerAssetId ?? '')
|
||||
const [iconImageId, setIconImageId] = useState(channel.iconImageId ?? '')
|
||||
const [iconGalleryOpen, setIconGalleryOpen] = useState(false)
|
||||
const [number, setNumber] = useState(channel.number?.toString() ?? '')
|
||||
const [offsetHours, setOffsetHours] = useState(channel.utcOffsetMinutes / 60)
|
||||
// Начало вещательных суток приходит как «06:00:00» — в поле нужен формат «06:00».
|
||||
@@ -36,6 +40,7 @@ export function SettingsCard({
|
||||
setName(channel.name)
|
||||
setIsEnabled(channel.isEnabled)
|
||||
setFillerAssetId(channel.fillerAssetId ?? '')
|
||||
setIconImageId(channel.iconImageId ?? '')
|
||||
setNumber(channel.number?.toString() ?? '')
|
||||
setOffsetHours(channel.utcOffsetMinutes / 60)
|
||||
setDayStart(channel.dayStartTime.slice(0, 5))
|
||||
@@ -48,6 +53,7 @@ export function SettingsCard({
|
||||
name: name.trim(),
|
||||
isEnabled,
|
||||
fillerAssetId: fillerAssetId || null,
|
||||
iconImageId: iconImageId || null,
|
||||
})
|
||||
await updateChannelTime(channel.id, {
|
||||
number: number.trim() === '' ? null : Number(number),
|
||||
@@ -119,6 +125,35 @@ export function SettingsCard({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.channels.iptvIcon')}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-md border border-border bg-muted/30">
|
||||
{iconImageId ? (
|
||||
<img src={imageUrl(iconImageId)} alt="" className="h-full w-full object-contain" />
|
||||
) : (
|
||||
<span className="text-center text-[10px] leading-tight text-muted-foreground">
|
||||
{t('admin.channels.noIptvIcon')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={() => setIconGalleryOpen(true)}>
|
||||
{t('admin.channels.pickIptvIcon')}
|
||||
</Button>
|
||||
{iconImageId && (
|
||||
<Button size="sm" variant="ghost" onClick={() => setIconImageId('')}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.channels.iptvIconHint')}</p>
|
||||
<ImageGallery
|
||||
open={iconGalleryOpen}
|
||||
onOpenChange={setIconGalleryOpen}
|
||||
category="Library"
|
||||
onSelect={(image) => setIconImageId(image.id)}
|
||||
/>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -651,6 +651,8 @@ export type ChannelDto = {
|
||||
dayStartTime: string
|
||||
templateId: string | null
|
||||
fillerAssetId: string | null
|
||||
/** Иконка канала для внешних IPTV-плееров (M3U/XMLTV); null — отдаётся фавикон-заглушка. */
|
||||
iconImageId: string | null
|
||||
viewer: ViewerSettings
|
||||
}
|
||||
|
||||
|
||||
@@ -785,6 +785,11 @@ export const en = {
|
||||
settings: 'Settings',
|
||||
filler: 'Filler',
|
||||
noFiller: 'No filler',
|
||||
iptvIcon: 'IPTV icon',
|
||||
noIptvIcon: 'none',
|
||||
pickIptvIcon: 'Pick an icon',
|
||||
iptvIconHint:
|
||||
'Channel logo in M3U/EPG for external players. If unset, the site favicon is served.',
|
||||
noSchedule: 'Schedule not built yet',
|
||||
airToday: 'Today',
|
||||
airCount: 'Entries for the day: {{count}}',
|
||||
|
||||
@@ -780,6 +780,11 @@ export const ru = {
|
||||
settings: 'Настройки',
|
||||
filler: 'Заглушка',
|
||||
noFiller: 'Без заглушки',
|
||||
iptvIcon: 'Иконка для IPTV',
|
||||
noIptvIcon: 'нет',
|
||||
pickIptvIcon: 'Выбрать иконку',
|
||||
iptvIconHint:
|
||||
'Логотип канала в M3U/EPG для внешних плееров. Если не задан — отдаётся фавикон сайта.',
|
||||
noSchedule: 'Расписание ещё не построено',
|
||||
airToday: 'Сегодня',
|
||||
airCount: 'Записей за сутки: {{count}}',
|
||||
|
||||
Reference in New Issue
Block a user