update route preview
This commit is contained in:
@ -130,20 +130,32 @@ export const RouteCreate = () => {
|
||||
required: "Это поле является обязательным",
|
||||
setValueAs: (value: string) => {
|
||||
try {
|
||||
// Парсим строку в массив массивов
|
||||
return JSON.parse(value);
|
||||
// Разбиваем строку на строки и парсим каждую строку как пару координат
|
||||
const lines = value.trim().split("\n");
|
||||
return lines.map((line) => {
|
||||
const [lat, lon] = line
|
||||
.trim()
|
||||
.split(/[\s,]+/)
|
||||
.map(Number);
|
||||
if (isNaN(lat) || isNaN(lon)) {
|
||||
throw new Error("Invalid coordinates");
|
||||
}
|
||||
return [lat, lon];
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
validate: (value: unknown) => {
|
||||
if (!Array.isArray(value)) return "Неверный формат";
|
||||
if (value.length === 0)
|
||||
return "Введите хотя бы одну пару координат";
|
||||
if (
|
||||
!value.every(
|
||||
(point: unknown) => Array.isArray(point) && point.length === 2
|
||||
)
|
||||
) {
|
||||
return "Каждая точка должна быть массивом из двух координат";
|
||||
return "Каждая строка должна содержать две координаты";
|
||||
}
|
||||
if (
|
||||
!value.every((point: unknown[]) =>
|
||||
@ -159,14 +171,17 @@ export const RouteCreate = () => {
|
||||
},
|
||||
})}
|
||||
error={!!(errors as any)?.path}
|
||||
helperText={(errors as any)?.path?.message} // 'Формат: [[lat1,lon1], [lat2,lon2], ...]'
|
||||
helperText={(errors as any)?.path?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Координаты маршрута *"}
|
||||
name="path"
|
||||
placeholder="[[1.1, 2.2], [2.1, 4.5]]"
|
||||
placeholder="55.7558 37.6173
|
||||
55.7539 37.6208"
|
||||
multiline
|
||||
rows={4}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
@ -186,6 +201,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("governor_appeal", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.governor_appeal}
|
||||
helperText={(errors as any)?.governor_appeal?.message}
|
||||
@ -200,6 +216,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("scale_min", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.scale_min}
|
||||
helperText={(errors as any)?.scale_min?.message}
|
||||
@ -214,6 +231,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("scale_max", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.scale_max}
|
||||
helperText={(errors as any)?.scale_max?.message}
|
||||
@ -228,6 +246,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("rotate", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.rotate}
|
||||
helperText={(errors as any)?.rotate?.message}
|
||||
@ -242,6 +261,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("center_latitude", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.center_latitude}
|
||||
helperText={(errors as any)?.center_latitude?.message}
|
||||
@ -256,6 +276,7 @@ export const RouteCreate = () => {
|
||||
<TextField
|
||||
{...register("center_longitude", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.center_longitude}
|
||||
helperText={(errors as any)?.center_longitude?.message}
|
||||
|
@ -1,83 +1,132 @@
|
||||
import {Autocomplete, Box, TextField, FormControlLabel, Checkbox, 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 {LinkedItems} from '../../components/LinkedItems'
|
||||
import {StationItem, VehicleItem, stationFields, vehicleFields} from './types'
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
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 { LinkedItems } from "../../components/LinkedItems";
|
||||
import {
|
||||
StationItem,
|
||||
VehicleItem,
|
||||
stationFields,
|
||||
vehicleFields,
|
||||
} from "./types";
|
||||
|
||||
export const RouteEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
control,
|
||||
formState: {errors},
|
||||
} = useForm({})
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const {id: routeId} = useParams<{id: string}>()
|
||||
const { id: routeId } = useParams<{ id: string }>();
|
||||
|
||||
const {autocompleteProps: carrierAutocompleteProps} = useAutocomplete({
|
||||
resource: 'carrier',
|
||||
const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
|
||||
resource: "carrier",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'short_name',
|
||||
operator: 'contains',
|
||||
field: "short_name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="carrier_id"
|
||||
rules={{required: 'Это поле является обязательным'}}
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...carrierAutocompleteProps}
|
||||
value={carrierAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
value={
|
||||
carrierAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.short_name : ''
|
||||
return item ? item.short_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.short_name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.short_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} label="Выберите перевозчика" margin="normal" variant="outlined" error={!!errors.carrier_id} helperText={(errors as any)?.carrier_id?.message} required />}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите перевозчика"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.carrier_id}
|
||||
helperText={(errors as any)?.carrier_id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('route_number', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("route_number", {
|
||||
required: "Это поле является обязательным",
|
||||
setValueAs: (value) => String(value),
|
||||
})}
|
||||
error={!!(errors as any)?.route_number}
|
||||
helperText={(errors as any)?.route_number?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Номер маршрута'}
|
||||
label={"Номер маршрута"}
|
||||
name="route_number"
|
||||
/>
|
||||
<Controller
|
||||
name="route_direction" // boolean
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({field}: {field: any}) => <FormControlLabel label="Прямой маршрут?" control={<Checkbox {...field} checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />} />}
|
||||
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
|
||||
variant="caption"
|
||||
color="textSecondary"
|
||||
sx={{ mt: 0, mb: 1 }}
|
||||
>
|
||||
(Прямой / Обратный)
|
||||
</Typography>
|
||||
|
||||
@ -86,38 +135,68 @@ export const RouteEdit = () => {
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
rules={{
|
||||
required: 'Это поле является обязательным',
|
||||
required: "Это поле является обязательным",
|
||||
validate: (value: unknown) => {
|
||||
if (!Array.isArray(value)) return 'Неверный формат'
|
||||
if (!value.every((point: unknown) => Array.isArray(point) && point.length === 2)) {
|
||||
return 'Каждая точка должна быть массивом из двух координат'
|
||||
if (!Array.isArray(value)) return "Неверный формат";
|
||||
if (value.length === 0)
|
||||
return "Введите хотя бы одну пару координат";
|
||||
if (
|
||||
!value.every(
|
||||
(point: unknown) => Array.isArray(point) && point.length === 2
|
||||
)
|
||||
) {
|
||||
return "Каждая строка должна содержать две координаты";
|
||||
}
|
||||
if (!value.every((point: unknown[]) => point.every((coord: unknown) => !isNaN(Number(coord)) && typeof coord === 'number'))) {
|
||||
return 'Координаты должны быть числами'
|
||||
if (
|
||||
!value.every((point: unknown[]) =>
|
||||
point.every(
|
||||
(coord: unknown) =>
|
||||
!isNaN(Number(coord)) && typeof coord === "number"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return "Координаты должны быть числами";
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
}}
|
||||
render={({field, fieldState: {error}}) => (
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
value={Array.isArray(field.value) ? JSON.stringify(field.value) : ''}
|
||||
value={
|
||||
Array.isArray(field.value)
|
||||
? field.value.map((point) => point.join(" ")).join("\n")
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
const parsed = JSON.parse(e.target.value)
|
||||
field.onChange(parsed)
|
||||
const lines = e.target.value.trim().split("\n");
|
||||
const parsed = lines.map((line) => {
|
||||
const [lat, lon] = line
|
||||
.trim()
|
||||
.split(/[\s,]+/)
|
||||
.map(Number);
|
||||
if (isNaN(lat) || isNaN(lon)) {
|
||||
throw new Error("Invalid coordinates");
|
||||
}
|
||||
return [lat, lon];
|
||||
});
|
||||
field.onChange(parsed);
|
||||
} catch {
|
||||
field.onChange([])
|
||||
field.onChange([]);
|
||||
}
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={error?.message} // 'Формат: [[lat1,lon1], [lat2,lon2], ...]'
|
||||
helperText={error?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Координаты маршрута'}
|
||||
placeholder="[[1.1, 2.2], [2.1, 4.5]]"
|
||||
label={"Координаты маршрута *"}
|
||||
placeholder="55.7558 37.6173
|
||||
55.7539 37.6208"
|
||||
multiline
|
||||
rows={4}
|
||||
sx={{
|
||||
marginBottom: 2,
|
||||
}}
|
||||
@ -126,111 +205,130 @@ export const RouteEdit = () => {
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('route_sys_number', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("route_sys_number", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.route_sys_number}
|
||||
helperText={(errors as any)?.route_sys_number?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Системный номер маршрута *'}
|
||||
label={"Системный номер маршрута *"}
|
||||
name="route_sys_number"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('governor_appeal', {
|
||||
{...register("governor_appeal", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.governor_appeal}
|
||||
helperText={(errors as any)?.governor_appeal?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Обращение губернатора'}
|
||||
label={"Обращение губернатора"}
|
||||
name="governor_appeal"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('scale_min', {
|
||||
{...register("scale_min", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.scale_min}
|
||||
helperText={(errors as any)?.scale_min?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Масштаб (мин)'}
|
||||
label={"Масштаб (мин)"}
|
||||
name="scale_min"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('scale_max', {
|
||||
{...register("scale_max", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.scale_max}
|
||||
helperText={(errors as any)?.scale_max?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Масштаб (макс)'}
|
||||
label={"Масштаб (макс)"}
|
||||
name="scale_max"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('rotate', {
|
||||
{...register("rotate", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.rotate}
|
||||
helperText={(errors as any)?.rotate?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Поворот'}
|
||||
label={"Поворот"}
|
||||
name="rotate"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('center_latitude', {
|
||||
{...register("center_latitude", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.center_latitude}
|
||||
helperText={(errors as any)?.center_latitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Центр. широта'}
|
||||
label={"Центр. широта"}
|
||||
name="center_latitude"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('center_longitude', {
|
||||
{...register("center_longitude", {
|
||||
// required: 'Это поле является обязательным',
|
||||
setValueAs: (value) => Number(value),
|
||||
})}
|
||||
error={!!(errors as any)?.center_longitude}
|
||||
helperText={(errors as any)?.center_longitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Центр. долгота'}
|
||||
label={"Центр. долгота"}
|
||||
name="center_longitude"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{routeId && (
|
||||
<>
|
||||
<LinkedItems<StationItem> type="edit" parentId={routeId} parentResource="route" childResource="station" fields={stationFields} title="станции" />
|
||||
<LinkedItems<StationItem>
|
||||
type="edit"
|
||||
parentId={routeId}
|
||||
parentResource="route"
|
||||
childResource="station"
|
||||
fields={stationFields}
|
||||
title="станции"
|
||||
/>
|
||||
|
||||
<LinkedItems<VehicleItem> type="edit" parentId={routeId} parentResource="route" childResource="vehicle" fields={vehicleFields} title="транспортные средства" />
|
||||
<LinkedItems<VehicleItem>
|
||||
type="edit"
|
||||
parentId={routeId}
|
||||
parentResource="route"
|
||||
childResource="vehicle"
|
||||
fields={vehicleFields}
|
||||
title="транспортные средства"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Edit>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user