add preview
functionality for /sight
route
This commit is contained in:
parent
2fe19516f6
commit
24a8bcad0a
@ -1,7 +1,10 @@
|
||||
import {Autocomplete, Box, TextField} from '@mui/material'
|
||||
import {Autocomplete, Box, TextField, Typography, Paper} from '@mui/material'
|
||||
import {Create, useAutocomplete} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import {Link} from 'react-router'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {TOKEN_KEY} from '../../authProvider'
|
||||
|
||||
export const SightCreate = () => {
|
||||
const {
|
||||
@ -9,6 +12,7 @@ export const SightCreate = () => {
|
||||
refineCore: {formLoading},
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
formState: {errors},
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
@ -16,6 +20,17 @@ export const SightCreate = () => {
|
||||
},
|
||||
})
|
||||
|
||||
// Состояния для предпросмотра
|
||||
const [namePreview, setNamePreview] = useState('')
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({latitude: '', longitude: ''})
|
||||
const [cityPreview, setCityPreview] = useState('')
|
||||
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null)
|
||||
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(null)
|
||||
const [watermarkRDPreview, setWatermarkRDPreview] = useState<string | null>(null)
|
||||
const [leftArticlePreview, setLeftArticlePreview] = useState('')
|
||||
const [previewArticlePreview, setPreviewArticlePreview] = useState('')
|
||||
|
||||
// Автокомплиты
|
||||
const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({
|
||||
resource: 'city',
|
||||
onSearch: (value) => [
|
||||
@ -49,9 +64,64 @@ export const SightCreate = () => {
|
||||
],
|
||||
})
|
||||
|
||||
// Следим за изменениями во всех полях
|
||||
const nameContent = watch('name')
|
||||
const latitudeContent = watch('latitude')
|
||||
const longitudeContent = watch('longitude')
|
||||
const cityContent = watch('city_id')
|
||||
const thumbnailContent = watch('thumbnail')
|
||||
const watermarkLUContent = watch('watermark_lu')
|
||||
const watermarkRDContent = watch('watermark_rd')
|
||||
const leftArticleContent = watch('left_article')
|
||||
const previewArticleContent = watch('preview_article')
|
||||
|
||||
// Обновляем состояния при изменении полей
|
||||
useEffect(() => {
|
||||
setNamePreview(nameContent || '')
|
||||
}, [nameContent])
|
||||
|
||||
useEffect(() => {
|
||||
setCoordinatesPreview({
|
||||
latitude: latitudeContent || '',
|
||||
longitude: longitudeContent || '',
|
||||
})
|
||||
}, [latitudeContent, longitudeContent])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedCity = cityAutocompleteProps.options.find((option) => option.id === cityContent)
|
||||
setCityPreview(selectedCity?.name || '')
|
||||
}, [cityContent, cityAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedThumbnail = mediaAutocompleteProps.options.find((option) => option.id === thumbnailContent)
|
||||
setThumbnailPreview(selectedThumbnail ? `https://wn.krbl.ru/media/${selectedThumbnail.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [thumbnailContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedWatermarkLU = mediaAutocompleteProps.options.find((option) => option.id === watermarkLUContent)
|
||||
setWatermarkLUPreview(selectedWatermarkLU ? `https://wn.krbl.ru/media/${selectedWatermarkLU.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [watermarkLUContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedWatermarkRD = mediaAutocompleteProps.options.find((option) => option.id === watermarkRDContent)
|
||||
setWatermarkRDPreview(selectedWatermarkRD ? `https://wn.krbl.ru/media/${selectedWatermarkRD.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [watermarkRDContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedLeftArticle = articleAutocompleteProps.options.find((option) => option.id === leftArticleContent)
|
||||
setLeftArticlePreview(selectedLeftArticle?.heading || '')
|
||||
}, [leftArticleContent, articleAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedPreviewArticle = articleAutocompleteProps.options.find((option) => option.id === previewArticleContent)
|
||||
setPreviewArticlePreview(selectedPreviewArticle?.heading || '')
|
||||
}, [previewArticleContent, articleAutocompleteProps.options])
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Box sx={{display: 'flex', gap: 2}}>
|
||||
{/* Форма создания */}
|
||||
<Box component="form" sx={{flex: 1, display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<TextField
|
||||
{...register('name', {
|
||||
required: 'Это поле является обязательным',
|
||||
@ -245,6 +315,169 @@ export const SightCreate = () => {
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Блок предпросмотра */}
|
||||
<Paper
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 2,
|
||||
maxHeight: 'calc(100vh - 200px)',
|
||||
overflowY: 'auto',
|
||||
position: 'sticky',
|
||||
top: 16,
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'background.paper' : '#fff'),
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
Предпросмотр
|
||||
</Typography>
|
||||
|
||||
{/* Название */}
|
||||
<Typography variant="h4" gutterBottom sx={{color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800')}}>
|
||||
{namePreview}
|
||||
</Typography>
|
||||
|
||||
{/* Город */}
|
||||
<Typography variant="body1" sx={{mb: 2}}>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Город:{' '}
|
||||
</Box>
|
||||
<Box component="span" sx={{color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800')}}>
|
||||
{cityPreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Координаты */}
|
||||
<Typography variant="body1" sx={{mb: 2}}>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Координаты:{' '}
|
||||
</Box>
|
||||
<Box component="span" sx={{color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800')}}>
|
||||
{coordinatesPreview.latitude}, {coordinatesPreview.longitude}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Обложка */}
|
||||
{thumbnailPreview && (
|
||||
<Box sx={{mb: 2}}>
|
||||
<Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Обложка:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={thumbnailPreview}
|
||||
alt="Обложка"
|
||||
sx={{
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Водяные знаки */}
|
||||
<Box sx={{mb: 2}}>
|
||||
<Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Водяные знаки:
|
||||
</Typography>
|
||||
<Box sx={{display: 'flex', gap: 2}}>
|
||||
{watermarkLUPreview && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Левый верхний:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={watermarkLUPreview}
|
||||
alt="Водяной знак (ЛВ)"
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{watermarkRDPreview && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Правый нижний:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={watermarkRDPreview}
|
||||
alt="Водяной знак (ПН)"
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Связанные статьи */}
|
||||
<Box>
|
||||
<Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Связанные статьи:
|
||||
</Typography>
|
||||
{leftArticlePreview && (
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Левая статья:{' '}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch('left_article')}`}
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{leftArticlePreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
{previewArticlePreview && (
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Статья-предпросмотр:{' '}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch('preview_article')}`}
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{previewArticlePreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Create>
|
||||
)
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import {Autocomplete, Box, TextField} from '@mui/material'
|
||||
import {Autocomplete, Box, TextField, Paper, Typography} from '@mui/material'
|
||||
import {Edit, useAutocomplete} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import {useParams} from 'react-router'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {LinkedItems} from '../../components/LinkedItems'
|
||||
import {CreateSightArticle} from '../../components/CreateSightArticle'
|
||||
import {ArticleItem, articleFields} from './types'
|
||||
import {TOKEN_KEY} from '../../authProvider'
|
||||
import {Link} from 'react-router'
|
||||
|
||||
export const SightEdit = () => {
|
||||
const {id: sightId} = useParams<{id: string}>()
|
||||
@ -14,6 +17,7 @@ export const SightEdit = () => {
|
||||
saveButtonProps,
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
formState: {errors},
|
||||
} = useForm({})
|
||||
|
||||
@ -50,9 +54,77 @@ export const SightEdit = () => {
|
||||
],
|
||||
})
|
||||
|
||||
// Состояния для предпросмотра
|
||||
const [namePreview, setNamePreview] = useState('')
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
})
|
||||
const [cityPreview, setCityPreview] = useState('')
|
||||
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null)
|
||||
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(null)
|
||||
const [watermarkRDPreview, setWatermarkRDPreview] = useState<string | null>(null)
|
||||
const [leftArticlePreview, setLeftArticlePreview] = useState('')
|
||||
const [previewArticlePreview, setPreviewArticlePreview] = useState('')
|
||||
|
||||
// Следим за изменениями во всех полях
|
||||
const nameContent = watch('name')
|
||||
const latitudeContent = watch('latitude')
|
||||
const longitudeContent = watch('longitude')
|
||||
const cityContent = watch('city_id')
|
||||
const thumbnailContent = watch('thumbnail')
|
||||
const watermarkLUContent = watch('watermark_lu')
|
||||
const watermarkRDContent = watch('watermark_rd')
|
||||
const leftArticleContent = watch('left_article')
|
||||
const previewArticleContent = watch('preview_article')
|
||||
|
||||
// Обновляем состояния при изменении полей
|
||||
useEffect(() => {
|
||||
setNamePreview(nameContent || '')
|
||||
}, [nameContent])
|
||||
|
||||
useEffect(() => {
|
||||
setCoordinatesPreview({
|
||||
latitude: latitudeContent || '',
|
||||
longitude: longitudeContent || '',
|
||||
})
|
||||
}, [latitudeContent, longitudeContent])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedCity = cityAutocompleteProps.options.find((option) => option.id === cityContent)
|
||||
setCityPreview(selectedCity?.name || '')
|
||||
}, [cityContent, cityAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedThumbnail = mediaAutocompleteProps.options.find((option) => option.id === thumbnailContent)
|
||||
setThumbnailPreview(selectedThumbnail ? `https://wn.krbl.ru/media/${selectedThumbnail.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [thumbnailContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedWatermarkLU = mediaAutocompleteProps.options.find((option) => option.id === watermarkLUContent)
|
||||
setWatermarkLUPreview(selectedWatermarkLU ? `https://wn.krbl.ru/media/${selectedWatermarkLU.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [watermarkLUContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedWatermarkRD = mediaAutocompleteProps.options.find((option) => option.id === watermarkRDContent)
|
||||
setWatermarkRDPreview(selectedWatermarkRD ? `https://wn.krbl.ru/media/${selectedWatermarkRD.id}/download?token=${localStorage.getItem(TOKEN_KEY)}` : null)
|
||||
}, [watermarkRDContent, mediaAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedLeftArticle = articleAutocompleteProps.options.find((option) => option.id === leftArticleContent)
|
||||
setLeftArticlePreview(selectedLeftArticle?.heading || '')
|
||||
}, [leftArticleContent, articleAutocompleteProps.options])
|
||||
|
||||
useEffect(() => {
|
||||
const selectedPreviewArticle = articleAutocompleteProps.options.find((option) => option.id === previewArticleContent)
|
||||
setPreviewArticlePreview(selectedPreviewArticle?.heading || '')
|
||||
}, [previewArticleContent, articleAutocompleteProps.options])
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Box sx={{display: 'flex', gap: 2}}>
|
||||
{/* Форма редактирования */}
|
||||
<Box component="form" sx={{flex: 1, display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<TextField
|
||||
{...register('name', {
|
||||
required: 'Это поле является обязательным',
|
||||
@ -247,6 +319,176 @@ export const SightEdit = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Блок предпросмотра */}
|
||||
<Paper
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 2,
|
||||
maxHeight: 'calc(100vh - 200px)',
|
||||
overflowY: 'auto',
|
||||
position: 'sticky',
|
||||
top: 16,
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'background.paper' : '#fff'),
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
Предпросмотр
|
||||
</Typography>
|
||||
|
||||
{/* Название достопримечательности */}
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
mb: 3,
|
||||
}}
|
||||
>
|
||||
{namePreview}
|
||||
</Typography>
|
||||
|
||||
{/* Город */}
|
||||
<Typography variant="body1" sx={{mb: 2}}>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Город:{' '}
|
||||
</Box>
|
||||
<Box component="span" sx={{color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800')}}>
|
||||
{cityPreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Координаты */}
|
||||
<Typography variant="body1" sx={{mb: 2}}>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Координаты:{' '}
|
||||
</Box>
|
||||
<Box component="span" sx={{color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800')}}>
|
||||
{coordinatesPreview.latitude}, {coordinatesPreview.longitude}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Обложка */}
|
||||
{thumbnailPreview && (
|
||||
<Box sx={{mb: 2}}>
|
||||
<Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Обложка:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={thumbnailPreview}
|
||||
alt="Обложка"
|
||||
sx={{
|
||||
maxWidth: '100%',
|
||||
height: '40vh',
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Водяные знаки */}
|
||||
<Box sx={{mb: 2}}>
|
||||
<Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Водяные знаки:
|
||||
</Typography>
|
||||
<Box sx={{display: 'flex', gap: 2}}>
|
||||
{watermarkLUPreview && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Левый верхний:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={watermarkLUPreview}
|
||||
alt="Водяной знак (ЛВ)"
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{watermarkRDPreview && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Правый нижний:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
src={watermarkRDPreview}
|
||||
alt="Водяной знак (ПН)"
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Связанные статьи */}
|
||||
<Box>
|
||||
{/* <Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}>
|
||||
Связанные статьи:
|
||||
</Typography> */}
|
||||
{leftArticlePreview && (
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Левая статья:{' '}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch('left_article')}`}
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{leftArticlePreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
{previewArticlePreview && (
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Box component="span" sx={{color: 'text.secondary'}}>
|
||||
Статья-предпросмотр:{' '}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch('preview_article')}`}
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{previewArticlePreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{sightId && (
|
||||
<Box sx={{mt: 3}}>
|
||||
<LinkedItems<ArticleItem> type="edit" parentId={sightId} parentResource="sight" childResource="article" fields={articleFields} title="статьи" />
|
||||
|
Loading…
Reference in New Issue
Block a user