[2] upgrade CreateSightArticle
for /sight
route
This commit is contained in:
@ -4,10 +4,19 @@ import {axiosInstance} from '../providers/data'
|
||||
import {BACKEND_URL} from '../lib/constants'
|
||||
import {useForm, Controller} from 'react-hook-form'
|
||||
import {MarkdownEditor} from './MarkdownEditor'
|
||||
import React from 'react'
|
||||
import React, {useState, useCallback} from 'react'
|
||||
import {useDropzone} from 'react-dropzone'
|
||||
import {ALLOWED_IMAGE_TYPES, ALLOWED_VIDEO_TYPES} from '../components/media/MediaFormUtils'
|
||||
|
||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor)
|
||||
|
||||
type MediaFile = {
|
||||
file: File
|
||||
preview: string
|
||||
uploading: boolean
|
||||
mediaId?: number
|
||||
}
|
||||
|
||||
type Props = {
|
||||
parentId: string | number
|
||||
parentResource: string
|
||||
@ -17,6 +26,7 @@ type Props = {
|
||||
|
||||
export const CreateSightArticle = ({parentId, parentResource, childResource, title}: Props) => {
|
||||
const theme = useTheme()
|
||||
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([])
|
||||
|
||||
const {
|
||||
register: registerItem,
|
||||
@ -39,32 +49,86 @@ export const CreateSightArticle = ({parentId, parentResource, childResource, tit
|
||||
[],
|
||||
)
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
const newFiles = acceptedFiles.map((file) => ({
|
||||
file,
|
||||
preview: URL.createObjectURL(file),
|
||||
uploading: false,
|
||||
}))
|
||||
setMediaFiles((prev) => [...prev, ...newFiles])
|
||||
}, [])
|
||||
|
||||
const {getRootProps, getInputProps, isDragActive} = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
'image/*': ALLOWED_IMAGE_TYPES,
|
||||
'video/*': ALLOWED_VIDEO_TYPES,
|
||||
},
|
||||
multiple: true,
|
||||
})
|
||||
|
||||
const uploadMedia = async (mediaFile: MediaFile) => {
|
||||
const formData = new FormData()
|
||||
formData.append('media_name', mediaFile.file.name)
|
||||
formData.append('filename', mediaFile.file.name)
|
||||
formData.append('type', mediaFile.file.type.startsWith('image/') ? '1' : '2')
|
||||
formData.append('file', mediaFile.file)
|
||||
|
||||
const response = await axiosInstance.post(`${BACKEND_URL}/media`, formData)
|
||||
return response.data.id
|
||||
}
|
||||
|
||||
const handleCreate = async (data: {heading: string; body: string}) => {
|
||||
try {
|
||||
// Создаем статью
|
||||
const response = await axiosInstance.post(`${BACKEND_URL}/${childResource}`, data)
|
||||
const itemId = response.data.id
|
||||
|
||||
// Получаем существующие статьи для определения порядкового номера
|
||||
const existingItemsResponse = await axiosInstance.get(`${BACKEND_URL}/${parentResource}/${parentId}/${childResource}`)
|
||||
const existingItems = existingItemsResponse.data || []
|
||||
|
||||
const nextPageNum = existingItems.length + 1
|
||||
|
||||
// Привязываем статью к достопримечательности
|
||||
await axiosInstance.post(`${BACKEND_URL}/${parentResource}/${parentId}/${childResource}/`, {
|
||||
[`${childResource}_id`]: itemId,
|
||||
page_num: nextPageNum,
|
||||
})
|
||||
|
||||
// Загружаем все медиа файлы и получаем их ID
|
||||
const mediaIds = await Promise.all(
|
||||
mediaFiles.map(async (mediaFile) => {
|
||||
return await uploadMedia(mediaFile)
|
||||
}),
|
||||
)
|
||||
|
||||
// Привязываем все медиа к статье
|
||||
await Promise.all(
|
||||
mediaIds.map((mediaId, index) =>
|
||||
axiosInstance.post(`${BACKEND_URL}/article/${itemId}/media/`, {
|
||||
media_id: mediaId,
|
||||
media_order: index + 1,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
resetItem()
|
||||
setMediaFiles([])
|
||||
window.location.reload()
|
||||
} catch (err: any) {
|
||||
console.error('Error creating item:', err)
|
||||
if (err?.response) {
|
||||
console.error('Error response:', err.response.data)
|
||||
console.error('Error status:', err.response.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeMedia = (index: number) => {
|
||||
setMediaFiles((prev) => {
|
||||
const newFiles = [...prev]
|
||||
URL.revokeObjectURL(newFiles[index].preview)
|
||||
newFiles.splice(index, 1)
|
||||
return newFiles
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
@ -96,6 +160,82 @@ export const CreateSightArticle = ({parentId, parentResource, childResource, tit
|
||||
|
||||
<Controller control={controlItem} name="body" rules={{required: 'Это поле является обязательным'}} defaultValue="" render={({field: {onChange, value}}) => <MemoizedSimpleMDE value={value} onChange={onChange} options={simpleMDEOptions} className="my-markdown-editor" />} />
|
||||
|
||||
{/* Dropzone для медиа файлов */}
|
||||
<Box sx={{mt: 2, mb: 2}}>
|
||||
<Box
|
||||
{...getRootProps()}
|
||||
sx={{
|
||||
border: '2px dashed',
|
||||
borderColor: isDragActive ? 'primary.main' : 'grey.300',
|
||||
borderRadius: 1,
|
||||
p: 2,
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Typography>{isDragActive ? 'Перетащите файлы сюда...' : 'Перетащите файлы сюда или кликните для выбора'}</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Превью загруженных файлов */}
|
||||
<Box sx={{mt: 2, display: 'flex', flexWrap: 'wrap', gap: 1}}>
|
||||
{mediaFiles.map((mediaFile, index) => (
|
||||
<Box
|
||||
key={mediaFile.preview}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: 100,
|
||||
height: 100,
|
||||
}}
|
||||
>
|
||||
{mediaFile.file.type.startsWith('image/') ? (
|
||||
<img
|
||||
src={mediaFile.preview}
|
||||
alt={mediaFile.file.name}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'grey.200',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption">{mediaFile.file.name}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => removeMedia(index)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
minWidth: 'auto',
|
||||
width: 20,
|
||||
height: 20,
|
||||
p: 0,
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{mt: 2, display: 'flex', gap: 2}}>
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
Создать
|
||||
@ -104,6 +244,8 @@ export const CreateSightArticle = ({parentId, parentResource, childResource, tit
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
resetItem()
|
||||
mediaFiles.forEach((file) => URL.revokeObjectURL(file.preview))
|
||||
setMediaFiles([])
|
||||
}}
|
||||
>
|
||||
Очистить
|
||||
|
Reference in New Issue
Block a user