import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { ChevronLeft, GripVertical, Trash2 } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { imageUrl } from '@/features/admin/images/api' import { ImageGallery } from '@/features/admin/images/ImageGallery' import { listShows } from '@/features/admin/shows/api' import { ShowPicker } from '@/features/admin/shows/ShowPicker' import { qk } from '@/shared/api/query-keys' import { useApiError } from '@/shared/lib/use-api-error' import { Badge } from '@/shared/ui/badge' import { Button } from '@/shared/ui/button' import { Input } from '@/shared/ui/input' import { Label } from '@/shared/ui/label' import { addCollectionShow, getCollection, removeCollectionShow, reorderCollection, setCollectionPoster, updateCollection, } from './api' export function CollectionDetail({ collectionId }: Readonly<{ collectionId: string }>) { const { t } = useTranslation() const queryClient = useQueryClient() const [galleryOpen, setGalleryOpen] = useState(false) const [dragged, setDragged] = useState(null) const [pendingShow, setPendingShow] = useState('') const [name, setName] = useState(null) const [description, setDescription] = useState(null) const { data: collection, isLoading } = useQuery({ queryKey: qk.collections.detail(collectionId), queryFn: () => getCollection(collectionId), }) const { data: shows } = useQuery({ queryKey: qk.shows.all, queryFn: () => listShows() }) const invalidate = () => { void queryClient.invalidateQueries({ queryKey: qk.collections.all }) } const onError = useApiError() const saveMutation = useMutation({ mutationFn: () => updateCollection(collectionId, { name: (name ?? collection?.name ?? '').trim(), description: description ?? collection?.description ?? null, }), onSuccess: invalidate, onError, }) const posterMutation = useMutation({ mutationFn: (imageId: string | null) => setCollectionPoster(collectionId, imageId), onSuccess: invalidate, onError, }) const addMutation = useMutation({ mutationFn: (showId: string) => addCollectionShow(collectionId, showId), onSuccess: () => { setPendingShow('') invalidate() }, onError, }) const removeMutation = useMutation({ mutationFn: (showId: string) => removeCollectionShow(collectionId, showId), onSuccess: invalidate, onError, }) const reorderMutation = useMutation({ mutationFn: (order: string[]) => reorderCollection(collectionId, order), onSuccess: invalidate, onError, }) if (isLoading || !collection) return

{t('common.loading')}

const memberIds = new Set(collection.items.map((i) => i.showId)) const available = (shows ?? []).filter((s) => !memberIds.has(s.id)) /** Порядок пересобирается целиком и уходит одним запросом — сервер сам расставит позиции. */ const dropOn = (targetShowId: string) => { if (!dragged || dragged === targetShowId) return const order = collection.items.map((i) => i.showId).filter((id) => id !== dragged) const at = order.indexOf(targetShowId) order.splice(at, 0, dragged) setDragged(null) reorderMutation.mutate(order) } return (
{collection.posterImageId ? ( ) : ( {t('admin.metadata.noPoster')} )}
{collection.posterImageId && ( )} posterMutation.mutate(img.id)} />
setName(e.target.value)} />
setDescription(e.target.value)} />

{t('admin.collections.parts')}

{/* Только те, кого в коллекции ещё нет: повторно добавлять некого. */}

{t('admin.collections.orderHint')}

{collection.items.length === 0 ? (

{t('admin.collections.empty')}

) : (
    {collection.items.map((item, index) => (
  • setDragged(item.showId)} onDragOver={(e) => e.preventDefault()} onDrop={() => dropOn(item.showId)} className="flex items-center gap-3 px-4 py-2" > {index + 1} {item.showName} {item.year && {item.year}} {t(`admin.shows.kinds.${item.showKind}`)} {t('admin.shows.episodes')}: {item.episodeCount}
  • ))}
)}
) }