finalize sight, station, route, vehicle routes

This commit is contained in:
maxim 2025-03-16 23:47:26 +03:00
parent 34620d5a64
commit bcce7f9d73
13 changed files with 139 additions and 95 deletions

View File

@ -1,2 +1,9 @@
@import './stylesheets/hidden-functionality.css'; @import './stylesheets/hidden-functionality.css';
@import './stylesheets/markdown-editor.css'; @import './stylesheets/markdown-editor.css';
.limited-text {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}

View File

@ -52,7 +52,7 @@ export const CityCreate = () => {
fullWidth fullWidth
InputLabelProps={{shrink: true}} InputLabelProps={{shrink: true}}
type="text" type="text"
label={'Название *'} label={'Название'}
name="name" name="name"
/> />
</Box> </Box>

View File

@ -1,6 +1,7 @@
import {Stack, Typography} from '@mui/material' import {Stack, Typography} from '@mui/material'
import {useShow} from '@refinedev/core' import {useShow} from '@refinedev/core'
import {Show, TextFieldComponent as TextField} from '@refinedev/mui' import {Show, TextFieldComponent as TextField} from '@refinedev/mui'
import {MEDIA_TYPES} from '../../lib/constants' import {MEDIA_TYPES} from '../../lib/constants'
export const MediaShow = () => { export const MediaShow = () => {

View File

@ -1,4 +1,4 @@
import {Box, TextField, FormControlLabel, Checkbox} from '@mui/material' import {Box, TextField, FormControlLabel, Checkbox, Typography} from '@mui/material'
import {Create} from '@refinedev/mui' import {Create} from '@refinedev/mui'
import {useForm} from '@refinedev/react-hook-form' import {useForm} from '@refinedev/react-hook-form'
import {Controller} from 'react-hook-form' import {Controller} from 'react-hook-form'
@ -51,8 +51,13 @@ export const RouteCreate = () => {
name="route_direction" // boolean name="route_direction" // boolean
control={control} control={control}
defaultValue={false} defaultValue={false}
render={({field}: {field: any}) => <FormControlLabel control={<Checkbox {...field} checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} label="Направление маршрута" />} render={({field}: {field: any}) => <FormControlLabel label="Прямой маршрут?" control={<Checkbox {...field} checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} />}
/> />
<Typography variant="caption" color="textSecondary" sx={{mt: 0, mb: 1}}>
(Прямой / Обратный)
</Typography>
<TextField <TextField
{...register('path', { {...register('path', {
required: 'Это поле является обязательным', required: 'Это поле является обязательным',

View File

@ -1,4 +1,4 @@
import {Box, TextField, FormControlLabel, Checkbox} from '@mui/material' import {Box, TextField, FormControlLabel, Checkbox, Typography} from '@mui/material'
import {Edit} from '@refinedev/mui' import {Edit} from '@refinedev/mui'
import {useForm} from '@refinedev/react-hook-form' import {useForm} from '@refinedev/react-hook-form'
import {Controller} from 'react-hook-form' import {Controller} from 'react-hook-form'
@ -46,8 +46,13 @@ export const RouteEdit = () => {
name="route_direction" // boolean name="route_direction" // boolean
control={control} control={control}
defaultValue={false} defaultValue={false}
render={({field}: {field: any}) => <FormControlLabel control={<Checkbox {...field} checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} label="Направление маршрута" />} render={({field}: {field: any}) => <FormControlLabel label="Прямой маршрут?" control={<Checkbox {...field} checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} />}
/> />
<Typography variant="caption" color="textSecondary" sx={{mt: 0, mb: 1}}>
(Прямой / Обратный)
</Typography>
<TextField <TextField
{...register('path', { {...register('path', {
required: 'Это поле является обязательным', required: 'Это поле является обязательным',

View File

@ -10,9 +10,10 @@ export const RouteShow = () => {
const fields = [ const fields = [
// {label: 'ID', data: 'id'}, // {label: 'ID', data: 'id'},
{label: 'ID перевозчика', data: 'carrier_id'}, // {label: 'ID перевозчика', data: 'carrier_id'},
{label: 'Перевозчик', data: 'carrier'},
{label: 'Номер маршрута', data: 'route_number'}, {label: 'Номер маршрута', data: 'route_number'},
{label: 'Путь', data: 'path'}, {label: 'Путь', data: 'path'}, // #
] ]
return ( return (
@ -31,7 +32,7 @@ export const RouteShow = () => {
<Typography variant="body1" fontWeight="bold"> <Typography variant="body1" fontWeight="bold">
Направление маршрута Направление маршрута
</Typography> </Typography>
<TextField value={record?.route_direction ? 'ДА' : 'НЕТ'} /> <TextField style={{color: record?.route_direction ? '#48989f' : '#7f6b58'}} value={record?.route_direction ? 'прямое' : 'обратное'} />
</Stack> </Stack>
</Stack> </Stack>
</Show> </Show>

View File

@ -59,6 +59,7 @@ export const SightEdit = () => {
label={'Долгота'} label={'Долгота'}
name="longitude" name="longitude"
/> />
<Controller <Controller
control={control} control={control}
name="city_id" name="city_id"

View File

@ -99,9 +99,10 @@ export const SightShow = () => {
const fields = [ const fields = [
// {label: 'ID', data: 'id'}, // {label: 'ID', data: 'id'},
{label: 'Название', data: 'name'}, {label: 'Название', data: 'name'},
{label: 'Широта', data: 'latitude'}, // {label: 'Широта', data: 'latitude'}, #
{label: 'Долгота', data: 'longitude'}, // {label: 'Долгота', data: 'longitude'}, #
{label: 'ID города', data: 'city_id'}, // {label: 'ID города', data: 'city_id'},
{label: 'Город', data: 'city'},
] ]
return ( return (
@ -126,14 +127,23 @@ export const SightShow = () => {
<Typography>Загрузка статей...</Typography> <Typography>Загрузка статей...</Typography>
) : linkedArticles.length > 0 ? ( ) : linkedArticles.length > 0 ? (
linkedArticles.map((article) => ( linkedArticles.map((article) => (
<Box key={article.id} sx={{border: '2px solid #dddddd25', padding: '20px', marginBottom: '8px'}}> <Box key={article.id} sx={{border: '2px solid #dddddd25', padding: '14px', marginBottom: '8px', borderRadius: 2}}>
<Stack gap={1}> <Stack gap={1}>
<Typography variant="h5"> <Typography variant="h5">
<strong>{article.heading}</strong> <strong>{article.heading}</strong>
</Typography> </Typography>
<Typography>{article.body}</Typography>
<Button variant="outlined" color="error" onClick={() => deleteArticle(article.id)} sx={{mt: 2}}> <Typography
className="limited-text"
sx={{
whiteSpace: 'pre-wrap',
lineClamp: 3,
}}
>
{article.body}
</Typography>
<Button variant="outlined" color="error" onClick={() => deleteArticle(article.id)} sx={{mt: 1.5}}>
Отвязать статью Отвязать статью
</Button> </Button>
</Stack> </Stack>

View File

@ -13,6 +13,8 @@ type SightItem = {
latitude: number latitude: number
longitude: number longitude: number
city_id: number city_id: number
city: string
[key: string]: string | number
} }
export const StationShow = () => { export const StationShow = () => {
@ -98,17 +100,18 @@ export const StationShow = () => {
const fields = [ const fields = [
// {label: 'ID', data: 'id'}, // {label: 'ID', data: 'id'},
{label: 'Название', data: 'name'}, {label: 'Название', data: 'name'},
{label: 'Широта', data: 'latitude'}, // {label: 'Широта', data: 'latitude'}, #
{label: 'Долгота', data: 'longitude'}, // {label: 'Долгота', data: 'longitude'}, #
{label: 'Описание', data: 'description'}, {label: 'Описание', data: 'description'},
] ]
const sightFields = [ const sightFields: Array<{label: string; data: keyof SightItem}> = [
// {label: 'ID', data: 'id' as keyof SightItem}, // {label: 'ID', data: 'id'},
{label: 'Название', data: 'name' as keyof SightItem}, {label: 'Название', data: 'name'},
{label: 'Широта', data: 'latitude' as keyof SightItem}, // {label: 'Широта', data: 'latitude'}, #
{label: 'Долгота', data: 'longitude' as keyof SightItem}, // {label: 'Долгота', data: 'longitude'}, #
{label: 'ID города', data: 'city_id' as keyof SightItem}, // {label: 'ID города', data: 'city_id'},
{label: 'Город', data: 'city'},
] ]
return ( return (
@ -125,41 +128,41 @@ export const StationShow = () => {
<Stack gap={2}> <Stack gap={2}>
<Typography variant="body1" fontWeight="bold"> <Typography variant="body1" fontWeight="bold">
Привязанные виды Привязанные достопримечательности
</Typography> </Typography>
<Grid container gap={2}> <Grid container gap={2}>
{sightsLoading ? ( {sightsLoading ? (
<Typography>Загрузка видов...</Typography> <Typography>Загрузка достопримечательностей...</Typography>
) : linkedSights.length > 0 ? ( ) : linkedSights.length > 0 ? (
linkedSights.map((sight, index) => ( linkedSights.map((sight, index) => (
<Box key={index} sx={{border: '2px solid #dddddd25', padding: '20px', marginBottom: '8px'}}> <Box key={index} sx={{border: '2px solid #dddddd25', padding: '14px', marginBottom: '8px', borderRadius: 2}}>
<Stack gap={0.5}> <Stack gap={0.5}>
{sightFields.map(({label, data}) => ( {sightFields.map(({label, data}) => (
<Typography key={data}> <Typography key={data}>
<strong>{label}:</strong> {sight?.[data]} <strong>{label}:</strong> {sight[data]}
</Typography> </Typography>
))} ))}
<Button variant="outlined" color="error" onClick={() => deleteSight(sight?.id)} sx={{mt: 2}}> <Button variant="outlined" color="error" onClick={() => deleteSight(sight?.id)} sx={{mt: 1.5}}>
Отвязать Отвязать
</Button> </Button>
</Stack> </Stack>
</Box> </Box>
)) ))
) : ( ) : (
<Typography>Виды не найдены</Typography> <Typography>Достопримечательности не найдены</Typography>
)} )}
</Grid> </Grid>
<Stack gap={2}> <Stack gap={2}>
<Typography variant="body1" fontWeight="bold"> <Typography variant="body1" fontWeight="bold">
Привязать вид Привязать достопримечательность
</Typography> </Typography>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel>Вид</InputLabel> <InputLabel>Достопримечательность</InputLabel>
<Select value={selectedSightId} onChange={(e) => setSelectedSightId(Number(e.target.value))} label="Вид" fullWidth> <Select value={selectedSightId} onChange={(e) => setSelectedSightId(Number(e.target.value))} label="Достопримечательность" fullWidth>
{availableSights.map((sight) => ( {availableSights.map((sight) => (
<MenuItem key={sight.id} value={sight.id}> <MenuItem key={sight.id} value={sight.id}>
{sight.name} {sight.name}

View File

@ -1,4 +1,4 @@
import {Autocomplete, Box, TextField, FormControl, InputLabel, Select, MenuItem, Typography} from '@mui/material' import {Autocomplete, Box, TextField} from '@mui/material'
import {Create, useAutocomplete} from '@refinedev/mui' import {Create, useAutocomplete} from '@refinedev/mui'
import {useForm} from '@refinedev/react-hook-form' import {useForm} from '@refinedev/react-hook-form'
import {Controller} from 'react-hook-form' import {Controller} from 'react-hook-form'
@ -32,33 +32,34 @@ export const VehicleCreate = () => {
fullWidth fullWidth
InputLabelProps={{shrink: true}} InputLabelProps={{shrink: true}}
type="number" type="number"
label="Номер рейса" label="Бортовой номер"
name="tail_number" name="tail_number"
/> />
<FormControl fullWidth margin="normal" error={!!errors.type}> <Controller
<InputLabel id="vehicle-type-label">Выберите тип</InputLabel> control={control}
<Select name="type"
labelId="vehicle-type-label" rules={{
label="Выберите тип"
{...register('type', {
required: 'Это поле является обязательным', required: 'Это поле является обязательным',
valueAsNumber: true, }}
})} defaultValue={null}
defaultValue="" render={({field}) => (
> <Autocomplete
{VEHICLE_TYPES.map((option) => ( options={VEHICLE_TYPES}
<MenuItem key={option.value} value={option.value}> value={VEHICLE_TYPES.find((option) => option.value === field.value) || null}
{option.label} onChange={(_, value) => {
</MenuItem> field.onChange(value?.value || null)
))} }}
</Select> getOptionLabel={(item) => {
{errors.type && ( return item ? item.label : ''
<Typography variant="caption" color="error"> }}
{!!(errors as any)?.message} isOptionEqualToValue={(option, value) => {
</Typography> return option.value === value?.value
}}
renderInput={(params) => <TextField {...params} label="Выберите тип" margin="normal" variant="outlined" error={!!errors.type} helperText={(errors as any)?.type?.message} required />}
/>
)} )}
</FormControl> />
<Controller <Controller
control={control} control={control}

View File

@ -1,17 +1,23 @@
import {Autocomplete, Box, TextField, FormControl, InputLabel, Select, MenuItem, Typography} from '@mui/material' import {Autocomplete, Box, TextField} from '@mui/material'
import {Edit, useAutocomplete} from '@refinedev/mui' import {Edit, useAutocomplete} from '@refinedev/mui'
import {useForm} from '@refinedev/react-hook-form' import {useForm} from '@refinedev/react-hook-form'
import {Controller} from 'react-hook-form' import {Controller} from 'react-hook-form'
import {VEHICLE_TYPES} from '../../lib/constants' import {VEHICLE_TYPES} from '../../lib/constants'
type VehicleFormValues = {
tail_number: number
type: number
city_id: number
}
export const VehicleEdit = () => { export const VehicleEdit = () => {
const { const {
saveButtonProps, saveButtonProps,
register, register,
control, control,
formState: {errors}, formState: {errors},
} = useForm({}) } = useForm<VehicleFormValues>({})
const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({ const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({
resource: 'city', resource: 'city',
@ -31,33 +37,34 @@ export const VehicleEdit = () => {
fullWidth fullWidth
InputLabelProps={{shrink: true}} InputLabelProps={{shrink: true}}
type="number" type="number"
label="Номер рейса" label="Бортовой номер"
name="tail_number" name="tail_number"
/> />
<FormControl fullWidth margin="normal" error={!!errors.type}> <Controller
<InputLabel id="vehicle-type-label">Выберите тип</InputLabel> control={control}
<Select name="type"
labelId="vehicle-type-label" rules={{
label="Выберите тип"
{...register('type', {
required: 'Это поле является обязательным', required: 'Это поле является обязательным',
valueAsNumber: true, }}
})} defaultValue={null}
defaultValue="" render={({field}) => (
> <Autocomplete
{VEHICLE_TYPES.map((option) => ( options={VEHICLE_TYPES}
<MenuItem key={option.value} value={option.value}> value={VEHICLE_TYPES.find((option) => option.value === field.value) || null}
{option.label} onChange={(_, value) => {
</MenuItem> field.onChange(value?.value || null)
))} }}
</Select> getOptionLabel={(item) => {
{errors.type && ( return item ? item.label : ''
<Typography variant="caption" color="error"> }}
{!!(errors as any)?.message} isOptionEqualToValue={(option, value) => {
</Typography> return option.value === value?.value
}}
renderInput={(params) => <TextField {...params} label="Выберите тип" margin="normal" variant="outlined" error={!!errors.type} helperText={(errors as any)?.type?.message} required />}
/>
)} )}
</FormControl> />
<Controller <Controller
control={control} control={control}

View File

@ -20,9 +20,18 @@ export const VehicleList = () => {
align: 'left', align: 'left',
headerAlign: 'left', headerAlign: 'left',
}, },
{
field: 'city_id',
headerName: 'ID города',
type: 'string',
minWidth: 150,
display: 'flex',
align: 'left',
headerAlign: 'left',
},
{ {
field: 'tail_number', field: 'tail_number',
headerName: 'Номер рейса', headerName: 'Бортовой номер',
type: 'number', type: 'number',
minWidth: 150, minWidth: 150,
display: 'flex', display: 'flex',
@ -42,15 +51,6 @@ export const VehicleList = () => {
return VEHICLE_TYPES.find((type) => type.value === value)?.label || value return VEHICLE_TYPES.find((type) => type.value === value)?.label || value
}, },
}, },
{
field: 'city_id',
headerName: 'ID города',
type: 'string',
minWidth: 150,
display: 'flex',
align: 'left',
headerAlign: 'left',
},
{ {
field: 'city', field: 'city',
headerName: 'Город', headerName: 'Город',

View File

@ -2,6 +2,8 @@ import {Stack, Typography} from '@mui/material'
import {useShow} from '@refinedev/core' import {useShow} from '@refinedev/core'
import {Show, TextFieldComponent as TextField} from '@refinedev/mui' import {Show, TextFieldComponent as TextField} from '@refinedev/mui'
import {VEHICLE_TYPES} from '../../lib/constants'
export const VehicleShow = () => { export const VehicleShow = () => {
const {query} = useShow({}) const {query} = useShow({})
const {data, isLoading} = query const {data, isLoading} = query
@ -10,20 +12,21 @@ export const VehicleShow = () => {
const fields = [ const fields = [
// {label: 'ID', data: 'id'}, // {label: 'ID', data: 'id'},
{label: 'Номер рейса', data: 'tail_number'}, {label: 'Бортовой номер', data: 'tail_number'},
{label: 'Тип', data: 'type'}, {label: 'Тип', data: 'type', render: (value: number) => VEHICLE_TYPES.find((type) => type.value === value)?.label || value},
{label: 'ID города', data: 'city_id'}, // {label: 'ID города', data: 'city_id'},
{label: 'Город', data: 'city'},
] ]
return ( return (
<Show isLoading={isLoading}> <Show isLoading={isLoading}>
<Stack gap={4}> <Stack gap={4}>
{fields.map(({label, data}) => ( {fields.map(({label, data, render}) => (
<Stack key={data} gap={1}> <Stack key={data} gap={1}>
<Typography variant="body1" fontWeight="bold"> <Typography variant="body1" fontWeight="bold">
{label} {label}
</Typography> </Typography>
<TextField value={record?.[data]} /> <TextField value={render ? render(record?.[data]) : record?.[data]} />
</Stack> </Stack>
))} ))}
</Stack> </Stack>