323 lines
10 KiB
TypeScript
323 lines
10 KiB
TypeScript
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({});
|
||
|
||
const { id: routeId } = useParams<{ id: string }>();
|
||
|
||
const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
|
||
resource: "carrier",
|
||
onSearch: (value) => [
|
||
{
|
||
field: "short_name",
|
||
operator: "contains",
|
||
value,
|
||
},
|
||
],
|
||
});
|
||
|
||
return (
|
||
<Edit saveButtonProps={saveButtonProps}>
|
||
<Box
|
||
component="form"
|
||
sx={{ display: "flex", flexDirection: "column" }}
|
||
autoComplete="off"
|
||
>
|
||
<Controller
|
||
control={control}
|
||
name="carrier_id"
|
||
rules={{ required: "Это поле является обязательным" }}
|
||
defaultValue={null}
|
||
render={({ field }) => (
|
||
<Autocomplete
|
||
{...carrierAutocompleteProps}
|
||
value={
|
||
carrierAutocompleteProps.options.find(
|
||
(option) => option.id === field.value
|
||
) || null
|
||
}
|
||
onChange={(_, value) => {
|
||
field.onChange(value?.id || "");
|
||
}}
|
||
getOptionLabel={(item) => {
|
||
return item ? item.short_name : "";
|
||
}}
|
||
isOptionEqualToValue={(option, value) => {
|
||
return option.id === value?.id;
|
||
}}
|
||
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
|
||
/>
|
||
)}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="text"
|
||
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)}
|
||
/>
|
||
}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Typography
|
||
variant="caption"
|
||
color="textSecondary"
|
||
sx={{ mt: 0, mb: 1 }}
|
||
>
|
||
(Прямой / Обратный)
|
||
</Typography>
|
||
|
||
<TextField
|
||
{...register("path", {
|
||
required: "Это поле является обязательным",
|
||
setValueAs: (value: string) => {
|
||
try {
|
||
// Разбиваем строку на строки и парсим каждую строку как пару координат
|
||
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 "Каждая строка должна содержать две координаты";
|
||
}
|
||
if (
|
||
!value.every((point: unknown[]) =>
|
||
point.every(
|
||
(coord: unknown) =>
|
||
!isNaN(Number(coord)) && typeof coord === "number"
|
||
)
|
||
)
|
||
) {
|
||
return "Координаты должны быть числами";
|
||
}
|
||
return true;
|
||
},
|
||
})}
|
||
error={!!(errors as any)?.path}
|
||
helperText={(errors as any)?.path?.message}
|
||
margin="normal"
|
||
fullWidth
|
||
InputLabelProps={{ shrink: true }}
|
||
type="text"
|
||
label={"Координаты маршрута *"}
|
||
name="path"
|
||
placeholder="55.7558 37.6173
|
||
55.7539 37.6208"
|
||
multiline
|
||
rows={4}
|
||
sx={{
|
||
marginBottom: 2,
|
||
}}
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="number"
|
||
label={"Системный номер маршрута *"}
|
||
name="route_sys_number"
|
||
/>
|
||
|
||
<TextField
|
||
{...register("governor_appeal", {
|
||
// required: 'Это поле является обязательным',
|
||
})}
|
||
error={!!(errors as any)?.governor_appeal}
|
||
helperText={(errors as any)?.governor_appeal?.message}
|
||
margin="normal"
|
||
fullWidth
|
||
InputLabelProps={{ shrink: true }}
|
||
type="number"
|
||
label={"Обращение губернатора"}
|
||
name="governor_appeal"
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="number"
|
||
label={"Масштаб (мин)"}
|
||
name="scale_min"
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="number"
|
||
label={"Масштаб (макс)"}
|
||
name="scale_max"
|
||
/>
|
||
|
||
<TextField
|
||
{...register("rotate", {
|
||
// required: 'Это поле является обязательным',
|
||
setValueAs: (value) => Number(value),
|
||
})}
|
||
error={!!(errors as any)?.rotate}
|
||
helperText={(errors as any)?.rotate?.message}
|
||
margin="normal"
|
||
fullWidth
|
||
InputLabelProps={{ shrink: true }}
|
||
type="number"
|
||
label={"Поворот"}
|
||
name="rotate"
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="number"
|
||
label={"Центр. широта"}
|
||
name="center_latitude"
|
||
/>
|
||
|
||
<TextField
|
||
{...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 }}
|
||
type="number"
|
||
label={"Центр. долгота"}
|
||
name="center_longitude"
|
||
/>
|
||
</Box>
|
||
|
||
{routeId && (
|
||
<>
|
||
<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="транспортные средства"
|
||
/>
|
||
</>
|
||
)}
|
||
</Edit>
|
||
);
|
||
};
|