feat(client): remove direct upload from product form, filter gallery to resized
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Alert from '@mui/material/Alert'
|
import Alert from '@mui/material/Alert'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
@@ -31,10 +31,8 @@ import {
|
|||||||
fetchAdminProducts,
|
fetchAdminProducts,
|
||||||
fetchCategories,
|
fetchCategories,
|
||||||
updateProduct,
|
updateProduct,
|
||||||
uploadAdminProductImages,
|
|
||||||
} from '@/entities/product/api/product-api'
|
} from '@/entities/product/api/product-api'
|
||||||
import type { Category, Product } from '@/entities/product/model/types'
|
import type { Category, Product } from '@/entities/product/model/types'
|
||||||
import { formatAdminImageMaxSizeHint } from '@/shared/constants/upload-limits'
|
|
||||||
import { formatPriceRub } from '@/shared/lib/format-price'
|
import { formatPriceRub } from '@/shared/lib/format-price'
|
||||||
import { getErrorMessage } from '@/shared/lib/get-error-message'
|
import { getErrorMessage } from '@/shared/lib/get-error-message'
|
||||||
import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
|
import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
|
||||||
@@ -203,20 +201,7 @@ export function AdminProductsPage() {
|
|||||||
else createMut.mutate()
|
else createMut.mutate()
|
||||||
}
|
}
|
||||||
|
|
||||||
const productImagesInputRef = useRef<HTMLInputElement>(null)
|
const mutationError = createMut.error ?? updateMut.error ?? deleteMut.error
|
||||||
|
|
||||||
const uploadImagesMut = useMutation({
|
|
||||||
mutationFn: (picked: File[]) => uploadAdminProductImages(picked),
|
|
||||||
onSuccess: (urls) => {
|
|
||||||
const current = productForm.getValues('imageUrls')
|
|
||||||
productForm.setValue('imageUrls', [...current, ...urls], { shouldDirty: true })
|
|
||||||
if (productImagesInputRef.current) {
|
|
||||||
productImagesInputRef.current.value = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const mutationError = createMut.error ?? updateMut.error ?? deleteMut.error ?? uploadImagesMut.error
|
|
||||||
|
|
||||||
const removeImage = (url: string) => {
|
const removeImage = (url: string) => {
|
||||||
const current = productForm.getValues('imageUrls')
|
const current = productForm.getValues('imageUrls')
|
||||||
@@ -401,11 +386,11 @@ export function AdminProductsPage() {
|
|||||||
/>
|
/>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" sx={{ mb: 0.5 }}>
|
<Typography variant="subtitle2" sx={{ mb: 0.5 }}>
|
||||||
Фото (загрузка)
|
Фото (из галереи)
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormHelperText sx={{ mt: 0, mb: 1 }}>
|
<FormHelperText sx={{ mt: 0, mb: 1 }}>
|
||||||
PNG, JPEG или WebP, до {formatAdminImageMaxSizeHint()} на файл. Крестик на превью убирает фото только из
|
Выберите обработанные изображения из галереи. Крестик на превью убирает фото только из карточки; файл
|
||||||
карточки; файл остаётся на сервере и в галерее.
|
остаётся на сервере и в галерее.
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -416,21 +401,6 @@ export function AdminProductsPage() {
|
|||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button component="label" variant="outlined" disabled={uploadImagesMut.isPending}>
|
|
||||||
Выбрать файлы
|
|
||||||
<input
|
|
||||||
ref={productImagesInputRef}
|
|
||||||
hidden
|
|
||||||
type="file"
|
|
||||||
accept="image/png,image/jpeg,image/webp"
|
|
||||||
multiple
|
|
||||||
onChange={(e) => {
|
|
||||||
const files = e.target.files
|
|
||||||
if (!files || files.length === 0) return
|
|
||||||
uploadImagesMut.mutate(Array.from(files))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -440,8 +410,6 @@ export function AdminProductsPage() {
|
|||||||
>
|
>
|
||||||
Из галереи
|
Из галереи
|
||||||
</Button>
|
</Button>
|
||||||
{uploadImagesMut.isPending && <Typography color="text.secondary">Загрузка…</Typography>}
|
|
||||||
{uploadImagesMut.isError && <Typography color="error">Не удалось загрузить фото</Typography>}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{productForm.watch('imageUrls').length > 0 && (
|
{productForm.watch('imageUrls').length > 0 && (
|
||||||
@@ -558,6 +526,14 @@ export function AdminProductsPage() {
|
|||||||
{galleryForPickQuery.data?.items.length === 0 && !galleryForPickQuery.isLoading && (
|
{galleryForPickQuery.data?.items.length === 0 && !galleryForPickQuery.isLoading && (
|
||||||
<Typography color="text.secondary">В галерее пока нет файлов. Загрузите их в разделе «Галерея».</Typography>
|
<Typography color="text.secondary">В галерее пока нет файлов. Загрузите их в разделе «Галерея».</Typography>
|
||||||
)}
|
)}
|
||||||
|
{galleryForPickQuery.data &&
|
||||||
|
galleryForPickQuery.data.items.length > 0 &&
|
||||||
|
galleryForPickQuery.data.items.filter((i) => i.isResized).length === 0 &&
|
||||||
|
!galleryForPickQuery.isLoading && (
|
||||||
|
<Typography color="text.secondary">
|
||||||
|
В галерее нет обработанных изображений. Сначала обработайте их в разделе «Галерея».
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@@ -566,33 +542,35 @@ export function AdminProductsPage() {
|
|||||||
pt: 1,
|
pt: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(galleryForPickQuery.data?.items ?? []).map((item) => {
|
{(galleryForPickQuery.data?.items ?? [])
|
||||||
const alreadyInCard = productForm.watch('imageUrls').includes(item.url)
|
.filter((item) => item.isResized)
|
||||||
return (
|
.map((item) => {
|
||||||
<FormControlLabel
|
const alreadyInCard = productForm.watch('imageUrls').includes(item.url)
|
||||||
key={item.id}
|
return (
|
||||||
sx={{ m: 0, alignItems: 'flex-start' }}
|
<FormControlLabel
|
||||||
control={
|
key={item.id}
|
||||||
<Checkbox
|
sx={{ m: 0, alignItems: 'flex-start' }}
|
||||||
checked={alreadyInCard || gallerySelectedUrls.has(item.url)}
|
control={
|
||||||
disabled={alreadyInCard}
|
<Checkbox
|
||||||
onChange={() => toggleGalleryPickUrl(item.url)}
|
checked={alreadyInCard || gallerySelectedUrls.has(item.url)}
|
||||||
/>
|
disabled={alreadyInCard}
|
||||||
}
|
onChange={() => toggleGalleryPickUrl(item.url)}
|
||||||
label={
|
|
||||||
<Box sx={{ width: '100%', maxHeight: 100, borderRadius: 1, overflow: 'hidden' }}>
|
|
||||||
<OptimizedImage
|
|
||||||
src={item.url}
|
|
||||||
alt=""
|
|
||||||
widths={[320, 640]}
|
|
||||||
sizes="120px"
|
|
||||||
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
}
|
||||||
}
|
label={
|
||||||
/>
|
<Box sx={{ width: '100%', maxHeight: 100, borderRadius: 1, overflow: 'hidden' }}>
|
||||||
)
|
<OptimizedImage
|
||||||
})}
|
src={item.url}
|
||||||
|
alt=""
|
||||||
|
widths={[320, 640]}
|
||||||
|
sizes="120px"
|
||||||
|
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
Reference in New Issue
Block a user