update route preview

This commit is contained in:
2025-04-14 01:03:58 +03:00
parent 607012bd47
commit b6449b02c0
178 changed files with 8999 additions and 86 deletions

View File

@ -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}

View File

@ -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>
)
}
);
};