add preview functionality for /sight route

This commit is contained in:
maxim 2025-04-07 00:01:21 +03:00
parent 2fe19516f6
commit 24a8bcad0a
2 changed files with 849 additions and 374 deletions

View File

@ -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,201 +64,419 @@ 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">
<TextField
{...register('name', {
required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="text"
label={'Название *'}
name="name"
/>
<TextField
{...register('latitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Широта *'}
name="latitude"
/>
<TextField
{...register('longitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.longitude}
helperText={(errors as any)?.longitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Долгота *'}
name="longitude"
/>
<Box sx={{display: 'flex', gap: 2}}>
{/* Форма создания */}
<Box component="form" sx={{flex: 1, display: 'flex', flexDirection: 'column'}} autoComplete="off">
<TextField
{...register('name', {
required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="text"
label={'Название *'}
name="name"
/>
<TextField
{...register('latitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Широта *'}
name="latitude"
/>
<TextField
{...register('longitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.longitude}
helperText={(errors as any)?.longitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Долгота *'}
name="longitude"
/>
<Controller
control={control}
name="city_id"
rules={{required: 'Это поле является обязательным'}}
defaultValue={null}
render={({field}) => (
<Autocomplete
{...cityAutocompleteProps}
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите город" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
/>
)}
/>
<Controller
control={control}
name="city_id"
rules={{required: 'Это поле является обязательным'}}
defaultValue={null}
render={({field}) => (
<Autocomplete
{...cityAutocompleteProps}
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите город" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
/>
)}
/>
<Controller
control={control}
name="thumbnail"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите обложку" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="thumbnail"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите обложку" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_lu"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Левый верх)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_lu"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Левый верх)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_rd"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Правый низ)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_rd"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Правый низ)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Левая статья" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Левая статья" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Cтатья-предпросмотр" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Cтатья-предпросмотр" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
</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>
)

View File

@ -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,201 +54,439 @@ 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">
<TextField
{...register('name', {
required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="text"
label={'Название *'}
name="name"
/>
<TextField
{...register('latitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Широта *'}
name="latitude"
/>
<TextField
{...register('longitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.longitude}
helperText={(errors as any)?.longitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Долгота *'}
name="longitude"
/>
<Box sx={{display: 'flex', gap: 2}}>
{/* Форма редактирования */}
<Box component="form" sx={{flex: 1, display: 'flex', flexDirection: 'column'}} autoComplete="off">
<TextField
{...register('name', {
required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="text"
label={'Название *'}
name="name"
/>
<TextField
{...register('latitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Широта *'}
name="latitude"
/>
<TextField
{...register('longitude', {
required: 'Это поле является обязательным',
valueAsNumber: true,
})}
error={!!(errors as any)?.longitude}
helperText={(errors as any)?.longitude?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
type="number"
label={'Долгота *'}
name="longitude"
/>
<Controller
control={control}
name="city_id"
rules={{required: 'Это поле является обязательным'}}
defaultValue={null}
render={({field}) => (
<Autocomplete
{...cityAutocompleteProps}
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите город" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
/>
)}
/>
<Controller
control={control}
name="city_id"
rules={{required: 'Это поле является обязательным'}}
defaultValue={null}
render={({field}) => (
<Autocomplete
{...cityAutocompleteProps}
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите город" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
/>
)}
/>
<Controller
control={control}
name="thumbnail"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите обложку" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="thumbnail"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите обложку" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_lu"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Левый верх)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_lu"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Левый верх)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_rd"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Правый низ)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="watermark_rd"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...mediaAutocompleteProps}
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.media_name : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Выберите водный знак (Правый низ)" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Левая статья" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Левая статья" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Cтатья-предпросмотр" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({field}) => (
<Autocomplete
{...articleAutocompleteProps}
value={articleAutocompleteProps.options.find((option) => option.id === field.value) || null}
onChange={(_, value) => {
field.onChange(value?.id || '')
}}
getOptionLabel={(item) => {
return item ? item.heading : ''
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id
}}
filterOptions={(options, {inputValue}) => {
return options.filter((option) => option.heading.toLowerCase().includes(inputValue.toLowerCase()))
}}
renderInput={(params) => <TextField {...params} label="Cтатья-предпросмотр" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} required />}
/>
)}
/>
</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 && (