feat: Redesign route direction with media_order input

This commit is contained in:
Илья Куприец 2025-05-20 16:59:57 +03:00
parent 7c363f1730
commit 8a443882b5
4 changed files with 244 additions and 184 deletions

View File

@ -106,17 +106,19 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>(
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
{!props.dontRecurse && {!props.dontRecurse && (
<> <>
<ArticleEditModal /> <ArticleEditModal />
<StationEditModal /> <StationEditModal />
</> </>
} )}
</> </>
); );
} };
export const LinkedItemsContents = <T extends { id: number; [key: string]: any }>({ export const LinkedItemsContents = <
T extends { id: number; [key: string]: any }
>({
parentId, parentId,
parentResource, parentResource,
childResource, childResource,
@ -128,7 +130,7 @@ export const LinkedItemsContents = <T extends { id: number; [key: string]: any }
onUpdate, onUpdate,
disableCreation = false, disableCreation = false,
updatedLinkedItems, updatedLinkedItems,
refresh refresh,
}: LinkedItemsProps<T>) => { }: LinkedItemsProps<T>) => {
const { language } = languageStore; const { language } = languageStore;
const { setArticleModalOpenAction, setArticleIdAction } = articleStore; const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
@ -358,11 +360,17 @@ export const LinkedItemsContents = <T extends { id: number; [key: string]: any }
: "default", : "default",
}} }}
onClick={() => { onClick={() => {
if (childResource === "article" && type==="edit") { if (
childResource === "article" &&
type === "edit"
) {
setArticleModalOpenAction(true); setArticleModalOpenAction(true);
setArticleIdAction(item.id); setArticleIdAction(item.id);
} }
if (childResource === "station" && type==="edit") { if (
childResource === "station" &&
type === "edit"
) {
setStationModalOpenAction(true); setStationModalOpenAction(true);
setStationIdAction(item.id); setStationIdAction(item.id);
setRouteIdAction(Number(parentId)); setRouteIdAction(Number(parentId));
@ -434,25 +442,15 @@ export const LinkedItemsContents = <T extends { id: number; [key: string]: any }
<Autocomplete <Autocomplete
fullWidth fullWidth
value={ value={
availableItems?.find( availableItems?.find((item) => item.id === selectedItemId) || null
(item) => item.id === selectedItemId
) || null
}
onChange={(_, newValue) =>
setSelectedItemId(newValue?.id || null)
} }
onChange={(_, newValue) => setSelectedItemId(newValue?.id || null)}
options={availableItems} options={availableItems}
getOptionLabel={(item) => String(item[fields[0].data])} getOptionLabel={(item) => String(item[fields[0].data])}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField {...params} label={`Выберите ${title}`} fullWidth />
{...params}
label={`Выберите ${title}`}
fullWidth
/>
)} )}
isOptionEqualToValue={(option, value) => isOptionEqualToValue={(option, value) => option.id === value?.id}
option.id === value?.id
}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const searchWords = inputValue const searchWords = inputValue
.toLowerCase() .toLowerCase()
@ -495,17 +493,32 @@ export const LinkedItemsContents = <T extends { id: number; [key: string]: any }
{childResource === "media" && ( {childResource === "media" && (
<FormControl fullWidth> <FormControl fullWidth>
<TextField <TextField
type="number" type="text"
label="Порядок отображения медиа" label="Порядок отображения медиа"
value={mediaOrder} value={mediaOrder}
onChange={(e) => { onChange={(e) => {
const newValue = Number(e.target.value); const rawValue = e.target.value;
const numericValue = Number(rawValue);
const maxValue = linkedItems.length + 1; const maxValue = linkedItems.length + 1;
const value = Math.max(1, Math.min(newValue, maxValue));
setMediaOrder(value); if (isNaN(numericValue)) {
return;
} else {
let newValue = numericValue;
if (newValue < 10 && newValue > 0) {
setMediaOrder(numericValue);
}
if (newValue > maxValue) {
newValue = maxValue;
}
setMediaOrder(newValue);
}
}} }}
fullWidth fullWidth
slotProps={{inputLabel: {shrink: true}}} InputLabelProps={{ shrink: true }}
/> />
</FormControl> </FormControl>
)} )}
@ -513,7 +526,9 @@ export const LinkedItemsContents = <T extends { id: number; [key: string]: any }
<Button <Button
variant="contained" variant="contained"
onClick={linkItem} onClick={linkItem}
disabled={!selectedItemId} disabled={
!selectedItemId || (childResource == "media" && mediaOrder == 0)
}
sx={{ alignSelf: "flex-start" }} sx={{ alignSelf: "flex-start" }}
> >
Добавить Добавить

View File

@ -8,6 +8,7 @@ import {
} from "@mui/material"; } 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 { useState } from "react";
import { Controller } from "react-hook-form"; import { Controller } from "react-hook-form";
export const RouteCreate = () => { export const RouteCreate = () => {
@ -16,6 +17,7 @@ export const RouteCreate = () => {
refineCore: { formLoading }, refineCore: { formLoading },
register, register,
control, control,
setValue,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
refineCoreProps: { refineCoreProps: {
@ -23,6 +25,16 @@ export const RouteCreate = () => {
}, },
}); });
const directions = [
{
label: "Прямой",
value: true,
},
{
label: "Обратный",
value: false,
},
];
const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({ const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
resource: "carrier", resource: "carrier",
onSearch: (value) => [ onSearch: (value) => [
@ -34,7 +46,8 @@ export const RouteCreate = () => {
], ],
}); });
const { autocompleteProps: governorAppealAutocompleteProps } = useAutocomplete({ const { autocompleteProps: governorAppealAutocompleteProps } =
useAutocomplete({
resource: "article", resource: "article",
onSearch: (value) => [ onSearch: (value) => [
@ -48,9 +61,11 @@ export const RouteCreate = () => {
operator: "contains", operator: "contains",
value, value,
}, },
] ],
}); });
const [routeDirection, setRouteDirection] = useState(false);
return ( return (
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}> <Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
<Box <Box
@ -117,31 +132,6 @@ export const RouteCreate = () => {
name="route_number" 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 <TextField
{...register("path", { {...register("path", {
required: "Это поле является обязательным", required: "Это поле является обязательным",
@ -211,7 +201,7 @@ export const RouteCreate = () => {
fullWidth fullWidth
slotProps={{ inputLabel: { shrink: true } }} slotProps={{ inputLabel: { shrink: true } }}
type="number" type="number"
label={"Системный номер маршрута *"} label={"Номер маршрута в Говорящем Городе *"}
name="route_sys_number" name="route_sys_number"
/> />
@ -258,6 +248,43 @@ export const RouteCreate = () => {
)} )}
/> />
<input
type="hidden"
{...register("route_direction", {
value: routeDirection,
})}
/>
<Autocomplete
options={directions}
defaultValue={directions.find((el) => el.value == false)}
onChange={(_, element) => {
if (element) {
setValue("route_direction", element.value);
setRouteDirection(element.value);
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Прямой/обратный маршрут"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
<Typography
variant="caption"
color="textSecondary"
sx={{ mt: 0, mb: 1 }}
>
{routeDirection ? "Прямой" : "Обратный"}
</Typography>
<TextField <TextField
{...register("scale_min", { {...register("scale_min", {
// required: 'Это поле является обязательным', // required: 'Это поле является обязательным',

View File

@ -18,7 +18,7 @@ import {
stationFields, stationFields,
vehicleFields, vehicleFields,
} from "./types"; } from "./types";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { META_LANGUAGE, languageStore } from "@stores"; import { META_LANGUAGE, languageStore } from "@stores";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { LanguageSelector } from "@ui"; import { LanguageSelector } from "@ui";
@ -32,11 +32,25 @@ export const RouteEdit = observer(() => {
formState: { errors }, formState: { errors },
refineCore: { queryResult }, refineCore: { queryResult },
setValue, setValue,
getValues,
watch, watch,
} = useForm({ } = useForm({
refineCoreProps: META_LANGUAGE(language) refineCoreProps: META_LANGUAGE(language),
}); });
const routeDirectionFromServer = watch("route_direction");
const [routeDirection, setRouteDirection] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const directions = [
{
label: "Прямой",
value: true,
},
{
label: "Обратный",
value: false,
},
];
const { id: routeId } = useParams<{ id: string }>(); const { id: routeId } = useParams<{ id: string }>();
@ -59,10 +73,11 @@ export const RouteEdit = observer(() => {
value, value,
}, },
], ],
...META_LANGUAGE(language) ...META_LANGUAGE(language),
}); });
const { autocompleteProps: governorAppealAutocompleteProps } = useAutocomplete({ const { autocompleteProps: governorAppealAutocompleteProps } =
useAutocomplete({
resource: "article", resource: "article",
onSearch: (value) => [ onSearch: (value) => [
@ -77,14 +92,18 @@ export const RouteEdit = observer(() => {
value, value,
}, },
], ],
...META_LANGUAGE(language) ...META_LANGUAGE(language),
}); });
useEffect(() => {
if (routeDirectionFromServer) {
setRouteDirection(routeDirectionFromServer);
}
}, [routeDirectionFromServer]);
return ( return (
<Edit saveButtonProps={saveButtonProps}> <Edit saveButtonProps={saveButtonProps}>
<Box <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
sx={{display: "flex", flexDirection: "column", gap:1}}
>
<Box <Box
component="form" component="form"
sx={{ display: "flex", flexDirection: "column" }} sx={{ display: "flex", flexDirection: "column" }}
@ -149,32 +168,31 @@ export const RouteEdit = observer(() => {
label={"Номер маршрута"} label={"Номер маршрута"}
name="route_number" name="route_number"
/> />
<Controller
name="route_direction" // boolean <input type="hidden" {...register("route_direction")} />
control={control}
defaultValue={false} <Autocomplete
render={({ field }: { field: any }) => ( options={directions}
<FormControlLabel value={directions.find((el) => el.value == routeDirection)}
label="Прямой маршрут?" onChange={(_, element) => {
control={ if (element) {
<Checkbox setValue("route_direction", element.value);
{...field} setRouteDirection(element.value);
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
} }
}}
renderInput={(params) => (
<TextField
{...params}
label="Прямой/обратный маршрут"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/> />
)} )}
/> />
<Typography
variant="caption"
color="textSecondary"
sx={{ mt: 0, mb: 1 }}
>
(Прямой / Обратный)
</Typography>
<TextField <TextField
{...register("path", { {...register("path", {
required: "Это поле является обязательным", required: "Это поле является обязательным",
@ -198,7 +216,8 @@ export const RouteEdit = observer(() => {
return "Введите хотя бы одну пару координат"; return "Введите хотя бы одну пару координат";
if ( if (
!value.every( !value.every(
(point: unknown) => Array.isArray(point) && point.length === 2 (point: unknown) =>
Array.isArray(point) && point.length === 2
) )
) { ) {
return "Каждая строка должна содержать две координаты"; return "Каждая строка должна содержать две координаты";
@ -243,7 +262,7 @@ export const RouteEdit = observer(() => {
fullWidth fullWidth
slotProps={{ inputLabel: { shrink: true } }} slotProps={{ inputLabel: { shrink: true } }}
type="number" type="number"
label={"Системный номер маршрута *"} label={"Номер маршрута в Говорящем Городе *"}
name="route_sys_number" name="route_sys_number"
/> />
@ -364,7 +383,6 @@ export const RouteEdit = observer(() => {
label={"Центр. долгота"} label={"Центр. долгота"}
name="center_longitude" name="center_longitude"
/> />
</Box> </Box>
{routeId && ( {routeId && (
@ -390,7 +408,7 @@ export const RouteEdit = observer(() => {
</> </>
)} )}
<Box sx={{ display: 'flex', justifyContent: 'flex-start' }}> <Box sx={{ display: "flex", justifyContent: "flex-start" }}>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"

View File

@ -9,7 +9,7 @@ import {
} from "@refinedev/mui"; } from "@refinedev/mui";
import { Button, Typography } from "@mui/material"; import { Button, Typography } from "@mui/material";
import React from "react"; import React from "react";
import MapIcon from '@mui/icons-material/Map'; import MapIcon from "@mui/icons-material/Map";
import { localeText } from "../../locales/ru/localeText"; import { localeText } from "../../locales/ru/localeText";
import { useLink } from "@refinedev/core"; import { useLink } from "@refinedev/core";
@ -21,7 +21,7 @@ export const RouteList = observer(() => {
const { language } = languageStore; const { language } = languageStore;
const { dataGridProps } = useDataGrid({ const { dataGridProps } = useDataGrid({
resource: "route/", resource: "route/",
meta: META_LANGUAGE(language) meta: META_LANGUAGE(language),
}); });
const columns = React.useMemo<GridColDef[]>( const columns = React.useMemo<GridColDef[]>(
@ -64,7 +64,7 @@ export const RouteList = observer(() => {
}, },
{ {
field: "route_sys_number", field: "route_sys_number",
headerName: "Системный номер маршрута", headerName: "Номер маршрута в Говорящем Городе",
type: "string", type: "string",
minWidth: 120, minWidth: 120,
display: "flex", display: "flex",