This commit is contained in:
@kirill.komarov
2026-05-10 17:38:04 +05:00
parent df4435dd67
commit 20096c1eec
12 changed files with 361 additions and 1 deletions
@@ -0,0 +1,138 @@
import { useRef } from 'react'
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import IconButton from '@mui/material/IconButton'
import Stack from '@mui/material/Stack'
import Tooltip from '@mui/material/Tooltip'
import Typography from '@mui/material/Typography'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { deleteGalleryImage, fetchAdminGallery } from '@/entities/gallery/api/gallery-api'
import { uploadAdminProductImages } from '@/entities/product/api/product-api'
import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
export function AdminGalleryPage() {
const queryClient = useQueryClient()
const fileInputRef = useRef<HTMLInputElement>(null)
const galleryQuery = useQuery({
queryKey: ['admin', 'gallery'],
queryFn: fetchAdminGallery,
})
const uploadMut = useMutation({
mutationFn: (files: File[]) => uploadAdminProductImages(files),
onSuccess: () => {
void invalidateQueryKeys(queryClient, [['admin', 'gallery']])
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
},
})
const deleteMut = useMutation({
mutationFn: (id: string) => deleteGalleryImage(id),
onSuccess: () => {
void invalidateQueryKeys(queryClient, [['admin', 'gallery']])
},
})
const items = galleryQuery.data?.items ?? []
return (
<Box>
<Typography variant="h4" gutterBottom>
Галерея
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Изображения без привязки к товару можно загружать здесь; их же можно добавить в карточку товара через «Из
галереи». Удаление из списка стирает файл с диска, если оно не используется в товаре.
</Typography>
<Stack direction="row" spacing={2} sx={{ mb: 3, alignItems: 'center', flexWrap: 'wrap' }}>
<Button variant="contained" component="label" disabled={uploadMut.isPending}>
Загрузить файлы
<input
ref={fileInputRef}
hidden
type="file"
accept="image/png,image/jpeg,image/webp"
multiple
onChange={(e) => {
const files = e.target.files
if (!files?.length) return
uploadMut.mutate(Array.from(files))
}}
/>
</Button>
{uploadMut.isPending && <Typography color="text.secondary">Загрузка</Typography>}
{uploadMut.isError && (
<Typography color="error">
{uploadMut.error instanceof Error ? uploadMut.error.message : 'Ошибка загрузки'}
</Typography>
)}
{deleteMut.isError && (
<Typography color="error">
{deleteMut.error instanceof Error ? deleteMut.error.message : 'Ошибка удаления'}
</Typography>
)}
</Stack>
{galleryQuery.isError && (
<Typography color="error" sx={{ mb: 2 }}>
Не удалось загрузить список.
</Typography>
)}
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
gap: 2,
}}
>
{items.map((item) => (
<Box
key={item.id}
sx={{
position: 'relative',
borderRadius: 1,
overflow: 'hidden',
border: 1,
borderColor: 'divider',
aspectRatio: '1',
}}
>
<Box
component="img"
src={item.url}
alt=""
sx={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
/>
<Tooltip title="Удалить из галереи">
<IconButton
size="small"
color="error"
sx={{
position: 'absolute',
top: 4,
right: 4,
bgcolor: 'background.paper',
'&:hover': { bgcolor: 'error.light', color: 'error.contrastText' },
}}
disabled={deleteMut.isPending}
onClick={() => deleteMut.mutate(item.id)}
>
<DeleteOutlineOutlinedIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
))}
</Box>
{!galleryQuery.isLoading && items.length === 0 && (
<Typography color="text.secondary">Пока нет загруженных изображений.</Typography>
)}
</Box>
)
}