import { useMemo, useState } from 'react' import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward' import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward' import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Dialog from '@mui/material/Dialog' import DialogActions from '@mui/material/DialogActions' import DialogContent from '@mui/material/DialogContent' import DialogTitle from '@mui/material/DialogTitle' import IconButton from '@mui/material/IconButton' import Paper from '@mui/material/Paper' import Stack from '@mui/material/Stack' import TextField from '@mui/material/TextField' import Typography from '@mui/material/Typography' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { fetchAdminCatalogSlider, putAdminCatalogSlider } from '@/entities/catalog-slider' import { fetchAdminGallery } from '@/entities/gallery' import type { GalleryImageItem } from '@/entities/gallery' import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys' type SlideDraft = { galleryImageId: string; caption: string; textColor: string } function SliderEditor({ initialSlides, galleryItems, }: { initialSlides: SlideDraft[] galleryItems: GalleryImageItem[] }) { const queryClient = useQueryClient() const [sliderDraft, setSliderDraft] = useState(initialSlides) const [pickOpen, setPickOpen] = useState(false) const usedIds = new Set(sliderDraft.map((s) => s.galleryImageId)) const pickCandidates = galleryItems.filter((i) => !usedIds.has(i.id) && i.isResized) const saveSliderMut = useMutation({ mutationFn: () => putAdminCatalogSlider({ slides: sliderDraft.map((s) => ({ galleryImageId: s.galleryImageId, caption: s.caption, textColor: s.textColor, })), }), onSuccess: async () => { await invalidateQueryKeys(queryClient, [['admin', 'catalog-slider'], ['catalog-slider']]) }, }) const moveSlide = (idx: number, dir: -1 | 1) => { const next = idx + dir if (next < 0 || next >= sliderDraft.length) return setSliderDraft((prev) => { const copy = [...prev] const t = copy[idx]! copy[idx] = copy[next]! copy[next] = t return copy }) } const updateDraft = (idx: number, patch: Partial) => { setSliderDraft((prev) => { const copy = [...prev] copy[idx] = { ...copy[idx]!, ...patch } return copy }) } return ( <> {sliderDraft.length === 0 && ( Нет слайдов. Добавьте изображения из галереи. )} {sliderDraft.map((row, idx) => { const img = galleryItems.find((g) => g.id === row.galleryImageId) return ( updateDraft(idx, { caption: e.target.value })} /> updateDraft(idx, { textColor: e.target.value })} sx={{ width: 80 }} slotProps={{ inputLabel: { shrink: true } }} /> moveSlide(idx, -1)} disabled={idx === 0}> moveSlide(idx, 1)} disabled={idx >= sliderDraft.length - 1} > setSliderDraft((prev) => prev.filter((_, i) => i !== idx))} > ) })} {saveSliderMut.isError && ( {saveSliderMut.error instanceof Error ? saveSliderMut.error.message : 'Ошибка сохранения'} )} setPickOpen(false)} fullWidth maxWidth="sm"> Выберите изображение {pickCandidates.length === 0 ? ( Нет доступных файлов (все уже в слайдере или галерея пуста). ) : ( {pickCandidates.map((item) => ( ))} )} ) } export function AdminSliderPage() { const sliderQuery = useQuery({ queryKey: ['admin', 'catalog-slider'], queryFn: fetchAdminCatalogSlider, }) const galleryQuery = useQuery({ queryKey: ['admin', 'gallery'], queryFn: fetchAdminGallery, }) const galleryItems: GalleryImageItem[] = galleryQuery.data?.items ?? [] const initialSlides = useMemo(() => { if (!sliderQuery.isSuccess) return [] return sliderQuery.data.slides.map((s) => ({ galleryImageId: s.galleryImageId, caption: s.caption, textColor: s.textColor || '#ffffff', })) }, [sliderQuery.isSuccess, sliderQuery.data?.slides]) if (sliderQuery.isLoading || galleryQuery.isLoading) { return Загрузка… } if (sliderQuery.isError) { return Не удалось загрузить слайдер. } return ( Слайдер Изображения для карусели на главной странице. Сначала загрузите фото в Галерею и обработайте их (Resize), затем добавьте в слайдер. Порядок строк = порядок показа. ) }