import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { GripVertical, Play, Trash2, Upload } from 'lucide-react' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { deleteCollection } from '@/features/admin/collections/api' import { useUploadStore } from '@/features/admin/media/upload-store' import { deleteShow, renameShow } from '@/features/admin/shows/api' import { qk } from '@/shared/api/query-keys' import type { InterstitialDto } from '@/shared/api/types' import { useApiError } from '@/shared/lib/use-api-error' import { Badge } from '@/shared/ui/badge' import { Button } from '@/shared/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/shared/ui/dialog' import { HlsVideo } from '@/shared/ui/hls-video' import { Input } from '@/shared/ui/input' import { BlockBuilder } from './BlockBuilder' import { ClipGroupPanel } from './ClipGroupPanel' import { listInterstitialBlocks, listInterstitials, mediaPreviewUrl } from './api' import { setDragItem } from './dnd' import { formatClock } from './format' /** * Экран роликов (см. 6.7). Под капотом это `Show(Kind = Interstitial)` и коллекции, но сценарий * другой: массовая загрузка, длительности вместо метаданных и сборка блока перетаскиванием. */ export function InterstitialsPanel() { const { t } = useTranslation() const queryClient = useQueryClient() const enqueue = useUploadStore((s) => s.enqueue) const fileInput = useRef(null) const [query, setQuery] = useState('') const [preview, setPreview] = useState(null) const [renaming, setRenaming] = useState<{ id: string; name: string } | null>(null) const { data: clips, isLoading } = useQuery({ queryKey: qk.interstitials.all, queryFn: listInterstitials, }) const { data: blocks } = useQuery({ queryKey: qk.interstitials.blocks, queryFn: listInterstitialBlocks, }) const invalidate = () => { void queryClient.invalidateQueries({ queryKey: qk.interstitials.all }) } const onError = useApiError() const renameMutation = useMutation({ mutationFn: ({ id, name }: Readonly<{ id: string; name: string }>) => renameShow(id, name), onSuccess: () => { setRenaming(null) invalidate() }, onError, }) const deleteClipMutation = useMutation({ mutationFn: (id: string) => deleteShow(id), onSuccess: invalidate, onError, }) const deleteBlockMutation = useMutation({ mutationFn: deleteCollection, onSuccess: invalidate, onError, }) const filtered = useMemo(() => { const q = query.trim().toLowerCase() const all = clips ?? [] return q ? all.filter((c) => c.name.toLowerCase().includes(q)) : all }, [clips, query]) const pickFiles = (files: FileList | null) => { if (!files || files.length === 0) return void enqueue(Array.from(files), { interstitial: true }) } return (

{t('admin.interstitials.title')}

{ pickFiles(e.target.files) e.target.value = '' }} />

{t('admin.interstitials.hint')}

setQuery(e.target.value)} />
{isLoading && ( )} {!isLoading && filtered.length === 0 && ( )} {filtered.map((clip) => ( setDragItem(e, { kind: 'clip', id: clip.id, name: clip.name, seconds: clip.durationSeconds ?? 0, }) } className="border-b border-border last:border-0" > ))}
{t('admin.interstitials.name')} {t('admin.interstitials.duration')} {t('admin.media.status')} {t('common.actions')}
{t('common.loading')}
{t('admin.interstitials.empty')}
{renaming?.id === clip.id ? ( setRenaming({ id: clip.id, name: e.target.value })} onBlur={() => renaming.name.trim() && renaming.name !== clip.name ? renameMutation.mutate({ id: clip.id, name: renaming.name.trim() }) : setRenaming(null) } onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur() if (e.key === 'Escape') setRenaming(null) }} /> ) : ( )}
{formatClock(clip.durationSeconds)} {clip.assetStatus ? t(`admin.media.statuses.${clip.assetStatus}`) : t('admin.interstitials.noAsset')}

{t('admin.interstitials.blocks')}

{(blocks ?? []).length === 0 ? (

{t('admin.interstitials.noBlocks')}

) : (
    {(blocks ?? []).map((block) => (
  • setDragItem(e, { kind: 'block', id: block.id, name: block.name, seconds: block.durationSeconds, }) } className="flex items-center gap-2 px-4 py-2" > {block.name} {t('admin.interstitials.clipsCount', { count: block.itemCount })} {formatClock(block.durationSeconds)}
  • ))}
)}
!open && setPreview(null)}> {preview?.name} {/* Кнопка в списке — это «плей», а не «открыть карточку»: жать ещё раз внутри окна незачем, ролик на тридцать секунд. */} {preview?.mediaAssetId && ( )}
) }