Latest version #12

Merged
Kerblif merged 46 commits from preview into master 2025-05-29 10:12:00 +00:00
8 changed files with 565 additions and 416 deletions
Showing only changes of commit db5e9d9fc4 - Show all commits

View File

@ -62,7 +62,6 @@ export const ArticleEditModal = observer(() => {
// Load existing media files when editing an article // Load existing media files when editing an article
const loadExistingMedia = async () => { const loadExistingMedia = async () => {
console.log("Called loadExistingMedia");
if (selectedArticleId) { if (selectedArticleId) {
try { try {
const response = await axiosInstance.get( const response = await axiosInstance.get(

View File

@ -50,8 +50,12 @@ const style = {
}; };
export const StationEditModal = observer(() => { export const StationEditModal = observer(() => {
const { stationModalOpen, setStationModalOpenAction, selectedStationId, selectedRouteId } = const {
stationStore; stationModalOpen,
setStationModalOpenAction,
selectedStationId,
selectedRouteId,
} = stationStore;
const { language } = languageStore; const { language } = languageStore;
useEffect(() => { useEffect(() => {
@ -64,10 +68,9 @@ export const StationEditModal = observer(() => {
const { data: stationQuery, isLoading: isStationLoading } = useCustom({ const { data: stationQuery, isLoading: isStationLoading } = useCustom({
url: `${apiUrl}/route/${selectedRouteId ?? 1}/station`, url: `${apiUrl}/route/${selectedRouteId ?? 1}/station`,
method: 'get' method: "get",
}); });
const { const {
register, register,
control, control,
@ -84,7 +87,6 @@ export const StationEditModal = observer(() => {
id: "", id: "",
redirect: false, redirect: false,
onMutationSuccess: (data) => { onMutationSuccess: (data) => {
console.log(data);
setStationModalOpenAction(false); setStationModalOpenAction(false);
reset(); reset();
window.location.reload(); window.location.reload();
@ -99,14 +101,14 @@ export const StationEditModal = observer(() => {
useEffect(() => { useEffect(() => {
if (stationModalOpen) { if (stationModalOpen) {
const station = stationQuery?.data?.find((station: StationItem) => station.id === selectedStationId); const station = stationQuery?.data?.find(
(station: StationItem) => station.id === selectedStationId
);
if (!station) return; if (!station) return;
for (const key in station) { for (const key in station) {
setValue(key, station[key]); setValue(key, station[key]);
console.log(key, station[key]);
} }
setValue("station_id", station.id); setValue("station_id", station.id);
console.log(stationQuery);
} }
}, [stationModalOpen, stationQuery]); }, [stationModalOpen, stationQuery]);

View File

@ -13,7 +13,15 @@ export function MediaView({media} : Readonly<{media?: MediaData}>) {
const token = localStorage.getItem(TOKEN_KEY); const token = localStorage.getItem(TOKEN_KEY);
return ( return (
<Box <Box
sx={{maxHeight: "300px", width: "100%", display: "flex", flexGrow: 1, justifyContent: "center"}} sx={{
maxHeight: "300px",
width: "100%",
height: "100%",
maxWidth: "300px",
display: "flex",
flexGrow: 1,
justifyContent: "center",
}}
> >
{media?.media_type === 1 && ( {media?.media_type === 1 && (
<img <img
@ -93,5 +101,5 @@ export function MediaView({media} : Readonly<{media?: MediaData}>) {
/> />
)} )}
</Box> </Box>
) );
} }

View File

@ -1,26 +1,43 @@
import { useCustom, useApiUrl } from "@refinedev/core"; import { useCustom, useApiUrl } from "@refinedev/core";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react"; import {
import { RouteData, SightData, SightPatchData, StationData, StationPatchData } from "./types"; createContext,
ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import {
RouteData,
SightData,
SightPatchData,
StationData,
StationPatchData,
} from "./types";
import { axiosInstance } from "../../providers/data"; import { axiosInstance } from "../../providers/data";
const MapDataContext = createContext<{ const MapDataContext = createContext<{
originalRouteData?: RouteData, originalRouteData?: RouteData;
originalStationData?: StationData[], originalStationData?: StationData[];
originalSightData?: SightData[], originalSightData?: SightData[];
routeData?: RouteData, routeData?: RouteData;
stationData?: StationData[], stationData?: StationData[];
sightData?: SightData[], sightData?: SightData[];
isRouteLoading: boolean, isRouteLoading: boolean;
isStationLoading: boolean, isStationLoading: boolean;
isSightLoading: boolean, isSightLoading: boolean;
setScaleRange: (min: number, max: number) => void, setScaleRange: (min: number, max: number) => void;
setMapRotation: (rotation: number) => void, setMapRotation: (rotation: number) => void;
setMapCenter: (x: number, y: number) => void, setMapCenter: (x: number, y: number) => void;
setStationOffset: (stationId: number, x: number, y: number) => void, setStationOffset: (stationId: number, x: number, y: number) => void;
setSightCoordinates: (sightId: number, latitude: number, longitude: number) => void, setSightCoordinates: (
saveChanges: () => void, sightId: number,
latitude: number,
longitude: number
) => void;
saveChanges: () => void;
}>({ }>({
originalRouteData: undefined, originalRouteData: undefined,
originalStationData: undefined, originalStationData: undefined,
@ -40,12 +57,15 @@ const MapDataContext = createContext<{
saveChanges: () => {}, saveChanges: () => {},
}); });
export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>) { export function MapDataProvider({
children,
}: Readonly<{ children: ReactNode }>) {
const { id: routeId } = useParams<{ id: string }>(); const { id: routeId } = useParams<{ id: string }>();
const apiUrl = useApiUrl(); const apiUrl = useApiUrl();
const [originalRouteData, setOriginalRouteData] = useState<RouteData>(); const [originalRouteData, setOriginalRouteData] = useState<RouteData>();
const [originalStationData, setOriginalStationData] = useState<StationData[]>(); const [originalStationData, setOriginalStationData] =
useState<StationData[]>();
const [originalSightData, setOriginalSightData] = useState<SightData[]>(); const [originalSightData, setOriginalSightData] = useState<SightData[]>();
const [routeData, setRouteData] = useState<RouteData>(); const [routeData, setRouteData] = useState<RouteData>();
@ -56,41 +76,42 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
const [stationChanges, setStationChanges] = useState<StationPatchData[]>([]); const [stationChanges, setStationChanges] = useState<StationPatchData[]>([]);
const [sightChanges, setSightChanges] = useState<SightPatchData[]>([]); const [sightChanges, setSightChanges] = useState<SightPatchData[]>([]);
const { data: routeQuery, isLoading: isRouteLoading } = useCustom({ const { data: routeQuery, isLoading: isRouteLoading } = useCustom({
url: `${apiUrl}/route/${routeId}`, url: `${apiUrl}/route/${routeId}`,
method: 'get', method: "get",
}); });
const { data: stationQuery, isLoading: isStationLoading } = useCustom({ const { data: stationQuery, isLoading: isStationLoading } = useCustom({
url: `${apiUrl}/route/${routeId}/station`, url: `${apiUrl}/route/${routeId}/station`,
method: 'get' method: "get",
}); });
const { data: sightQuery, isLoading: isSightLoading } = useCustom({ const { data: sightQuery, isLoading: isSightLoading } = useCustom({
url: `${apiUrl}/route/${routeId}/sight`, url: `${apiUrl}/route/${routeId}/sight`,
method: 'get', method: "get",
}); });
useEffect(() => { useEffect(() => {
// if not undefined, set original data // if not undefined, set original data
if (routeQuery?.data) setOriginalRouteData(routeQuery.data as RouteData); if (routeQuery?.data) setOriginalRouteData(routeQuery.data as RouteData);
if(stationQuery?.data) setOriginalStationData(stationQuery.data as StationData[]); if (stationQuery?.data)
setOriginalStationData(stationQuery.data as StationData[]);
if (sightQuery?.data) setOriginalSightData(sightQuery.data as SightData[]); if (sightQuery?.data) setOriginalSightData(sightQuery.data as SightData[]);
console.log("queries", routeQuery, stationQuery, sightQuery); console.log("queries", routeQuery, stationQuery, sightQuery);
}, [routeQuery, stationQuery, sightQuery]); }, [routeQuery, stationQuery, sightQuery]);
useEffect(() => { useEffect(() => {
// combine changes with original data // combine changes with original data
if(originalRouteData) setRouteData({...originalRouteData, ...routeChanges}); if (originalRouteData)
setRouteData({ ...originalRouteData, ...routeChanges });
if (originalStationData) setStationData(originalStationData); if (originalStationData) setStationData(originalStationData);
if (originalSightData) setSightData(originalSightData); if (originalSightData) setSightData(originalSightData);
}, [ }, [
originalRouteData, originalStationData, originalSightData, originalRouteData,
routeChanges, stationChanges, sightChanges originalStationData,
originalSightData,
routeChanges,
stationChanges,
sightChanges,
]); ]);
useEffect(() => {
console.log("data", routeData, stationData, sightData);
}, [routeData, stationData, sightData]);
function setScaleRange(min: number, max: number) { function setScaleRange(min: number, max: number) {
setRouteChanges((prev) => { setRouteChanges((prev) => {
return { ...prev, scale_min: min, scale_max: max }; return { ...prev, scale_min: min, scale_max: max };
@ -105,7 +126,7 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
function setMapCenter(x: number, y: number) { function setMapCenter(x: number, y: number) {
setRouteChanges((prev) => { setRouteChanges((prev) => {
return {...prev, center_latitude: x, center_longitude: y} return { ...prev, center_latitude: x, center_longitude: y };
}); });
} }
@ -117,16 +138,20 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
async function saveStationChanges() { async function saveStationChanges() {
for (const station of stationChanges) { for (const station of stationChanges) {
const response = await axiosInstance.patch(`/route/${routeId}/station`, station); const response = await axiosInstance.patch(
console.log("response", response); `/route/${routeId}/station`,
station
);
} }
} }
async function saveSightChanges() { async function saveSightChanges() {
console.log("sightChanges", sightChanges); console.log("sightChanges", sightChanges);
for (const sight of sightChanges) { for (const sight of sightChanges) {
const response = await axiosInstance.patch(`/route/${routeId}/sight`, sight); const response = await axiosInstance.patch(
console.log("response", response); `/route/${routeId}/sight`,
sight
);
} }
} }
@ -144,20 +169,30 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
return station; return station;
}); });
} else { } else {
const foundStation = stationData?.find((station) => station.id === stationId); const foundStation = stationData?.find(
(station) => station.id === stationId
);
if (foundStation) { if (foundStation) {
return [...prev, { return [
...prev,
{
station_id: stationId, station_id: stationId,
offset_x: x, offset_y: y, offset_x: x,
transfers: foundStation.transfers offset_y: y,
}]; transfers: foundStation.transfers,
},
];
} }
return prev; return prev;
} }
}); });
} }
function setSightCoordinates(sightId: number, latitude: number, longitude: number) { function setSightCoordinates(
sightId: number,
latitude: number,
longitude: number
) {
setSightChanges((prev) => { setSightChanges((prev) => {
let found = prev.find((sight) => sight.sight_id === sightId); let found = prev.find((sight) => sight.sight_id === sightId);
if (found) { if (found) {
@ -173,11 +208,14 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
} else { } else {
const foundSight = sightData?.find((sight) => sight.id === sightId); const foundSight = sightData?.find((sight) => sight.id === sightId);
if (foundSight) { if (foundSight) {
return [...prev, { return [
...prev,
{
sight_id: sightId, sight_id: sightId,
latitude, latitude,
longitude longitude,
}]; },
];
} }
return prev; return prev;
} }
@ -188,7 +226,8 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
console.log("sightChanges", sightChanges); console.log("sightChanges", sightChanges);
}, [sightChanges]); }, [sightChanges]);
const value = useMemo(() => ({ const value = useMemo(
() => ({
originalRouteData: originalRouteData, originalRouteData: originalRouteData,
originalStationData: originalStationData, originalStationData: originalStationData,
originalSightData: originalSightData, originalSightData: originalSightData,
@ -203,20 +242,30 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
setMapCenter, setMapCenter,
saveChanges, saveChanges,
setStationOffset, setStationOffset,
setSightCoordinates setSightCoordinates,
}), [originalRouteData, originalStationData, originalSightData, routeData, stationData, sightData, isRouteLoading, isStationLoading, isSightLoading]); }),
[
originalRouteData,
originalStationData,
originalSightData,
routeData,
stationData,
sightData,
isRouteLoading,
isStationLoading,
isSightLoading,
]
);
return ( return (
<MapDataContext.Provider value={value}> <MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
{children}
</MapDataContext.Provider>
); );
} }
export const useMapData = () => { export const useMapData = () => {
const context = useContext(MapDataContext); const context = useContext(MapDataContext);
if (!context) { if (!context) {
throw new Error('useMapData must be used within a MapDataProvider'); throw new Error("useMapData must be used within a MapDataProvider");
} }
return context; return context;
}; };

View File

@ -277,14 +277,6 @@ export const RouteCreate = () => {
)} )}
/> />
<Typography
variant="caption"
color="textSecondary"
sx={{ mt: 0, mb: 1 }}
>
{routeDirection ? "Прямой" : "Обратный"}
</Typography>
<TextField <TextField
{...register("scale_min", { {...register("scale_min", {
// required: 'Это поле является обязательным', // required: 'Это поле является обязательным',

View File

@ -10,6 +10,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 { useEffect, useState } from "react";
import { Controller } from "react-hook-form"; import { Controller } from "react-hook-form";
const TRANSFER_FIELDS = [ const TRANSFER_FIELDS = [
@ -29,14 +30,64 @@ export const StationCreate = () => {
saveButtonProps, saveButtonProps,
refineCore: { formLoading }, refineCore: { formLoading },
register, register,
setValue,
control, control,
getValues,
watch,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
refineCoreProps: { refineCoreProps: {
resource: "station/", resource: "station",
}, },
}); });
const [coordinatesPreview, setCoordinatesPreview] = useState({
latitude: "",
longitude: "",
});
const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const [lat, lon] = e.target.value.split(",").map((s) => s.trim());
setCoordinatesPreview({
latitude: lat,
longitude: lon,
});
setValue("latitude", lat);
setValue("longitude", lon);
};
const latitudeContent = watch("latitude");
const longitudeContent = watch("longitude");
useEffect(() => {
setCoordinatesPreview({
latitude: latitudeContent || "",
longitude: longitudeContent || "",
});
}, [latitudeContent, longitudeContent]);
useEffect(() => {
const latitude = getValues("latitude");
const longitude = getValues("longitude");
if (latitude && longitude) {
setCoordinatesPreview({
latitude: latitude,
longitude: longitude,
});
}
}, [getValues]);
const directions = [
{
label: "Прямой",
value: true,
},
{
label: "Обратный",
value: false,
},
];
const [routeDirection, setRouteDirection] = useState(false);
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({ const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
resource: "city", resource: "city",
onSearch: (value) => [ onSearch: (value) => [
@ -108,50 +159,68 @@ export const StationCreate = () => {
label={"Описание"} label={"Описание"}
name="description" name="description"
/> />
<Controller <input
name="direction" // boolean type="hidden"
control={control} {...register("direction", {
defaultValue={false} value: routeDirection,
render={({ field }: { field: any }) => ( })}
<FormControlLabel
label="Прямой маршрут?"
control={
<Checkbox
{...field}
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/> />
<Autocomplete
options={directions}
defaultValue={directions.find((el) => el.value == false)}
onChange={(_, element) => {
if (element) {
setValue("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
/> />
)} )}
/> />
<TextField <TextField
{...register("latitude", { value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
required: "Это поле является обязательным", onChange={handleCoordinatesChange}
valueAsNumber: true,
})}
error={!!(errors as any)?.latitude} error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message} helperText={(errors as any)?.latitude?.message}
margin="normal" margin="normal"
fullWidth fullWidth
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
type="number" type="text"
label={"Широта *"} label={"Координаты *"}
name="latitude"
/> />
<TextField <input
{...register("longitude", { type="hidden"
required: "Это поле является обязательным", {...register("latitude", {
valueAsNumber: true, value: coordinatesPreview.latitude,
setValueAs: (value) => {
if (value === "") {
return 0;
}
return Number(value);
},
})}
/>
<input
type="hidden"
{...register("longitude", {
value: coordinatesPreview.longitude,
setValueAs: (value) => {
if (value === "") {
return 0;
}
return Number(value);
},
})} })}
error={!!(errors as any)?.longitude}
helperText={(errors as any)?.longitude?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="number"
label={"Долгота *"}
name="longitude"
/> />
<Controller <Controller
@ -196,9 +265,15 @@ export const StationCreate = () => {
)} )}
/> />
<Box sx={{ visibility: "hidden" }}>
<TextField <TextField
{...register("offset_x", { {...register("offset_x", {
// required: 'Это поле является обязательным', // required: 'Это поле является обязательным',
setValueAs: (value) => {
if (value === "") {
return 0;
}
},
})} })}
error={!!(errors as any)?.offset_x} error={!!(errors as any)?.offset_x}
helperText={(errors as any)?.offset_x?.message} helperText={(errors as any)?.offset_x?.message}
@ -212,6 +287,11 @@ export const StationCreate = () => {
<TextField <TextField
{...register("offset_y", { {...register("offset_y", {
setValueAs: (value) => {
if (value === "") {
return 0;
}
},
// required: 'Это поле является обязательным', // required: 'Это поле является обязательным',
})} })}
error={!!(errors as any)?.offset_y} error={!!(errors as any)?.offset_y}
@ -223,9 +303,11 @@ export const StationCreate = () => {
label={"Смещение (Y)"} label={"Смещение (Y)"}
name="offset_y" name="offset_y"
/> />
</Box>
</Box>
{/* Группа полей пересадок */} {/* Группа полей пересадок */}
<Paper sx={{ p: 2, mt: 2 }}> <Paper hidden sx={{ p: 2, mt: 2 }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Пересадки Пересадки
</Typography> </Typography>
@ -247,7 +329,6 @@ export const StationCreate = () => {
))} ))}
</Grid> </Grid>
</Paper> </Paper>
</Box>
</Create> </Create>
); );
}; };

View File

@ -96,6 +96,26 @@ export const StationEdit = observer(() => {
}, },
}); });
const directions = [
{
label: "Прямой",
value: true,
},
{
label: "Обратный",
value: false,
},
];
const directionContent = watch("direction");
const [routeDirection, setRouteDirection] = useState(false);
useEffect(() => {
if (directionContent) {
setRouteDirection(directionContent);
}
}, [directionContent]);
useEffect(() => { useEffect(() => {
if (stationData[language as keyof typeof stationData]?.name) { if (stationData[language as keyof typeof stationData]?.name) {
setValue("name", stationData[language as keyof typeof stationData]?.name); setValue("name", stationData[language as keyof typeof stationData]?.name);
@ -172,17 +192,6 @@ export const StationEdit = observer(() => {
}, },
}); });
useEffect(() => {
const latitude = getValues("latitude");
const longitude = getValues("longitude");
if (latitude && longitude) {
setCoordinatesPreview({
latitude: latitude,
longitude: longitude,
});
}
}, [getValues]);
return ( return (
<Edit saveButtonProps={saveButtonProps}> <Edit saveButtonProps={saveButtonProps}>
<Box <Box
@ -217,20 +226,29 @@ export const StationEdit = observer(() => {
label={"Системное название *"} label={"Системное название *"}
name="system_name" name="system_name"
/> />
<Controller
name="direction" // boolean <input
control={control} type="hidden"
defaultValue={false} {...register("direction", { value: routeDirection })}
render={({ field }: { field: any }) => (
<FormControlLabel
label="Прямой маршрут?"
control={
<Checkbox
{...field}
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/> />
<Autocomplete
options={directions}
value={directions.find((el) => el.value == routeDirection)}
onChange={(_, element) => {
if (element) {
setValue("direction", element.value);
setRouteDirection(element.value);
} }
}}
renderInput={(params) => (
<TextField
{...params}
label="Прямой/обратный маршрут"
margin="normal"
variant="outlined"
error={!!errors.direction}
helperText={(errors as any)?.direction?.message}
required
/> />
)} )}
/> />

View File

@ -61,6 +61,7 @@ export const VehicleList = observer(() => {
type: "string", type: "string",
minWidth: 200, minWidth: 200,
display: "flex", display: "flex",
flex: 1,
align: "left", align: "left",
headerAlign: "left", headerAlign: "left",
renderCell: (params) => { renderCell: (params) => {
@ -70,14 +71,14 @@ export const VehicleList = observer(() => {
); );
}, },
}, },
{ // {
field: "city", // field: "city",
headerName: "Город", // headerName: "Город",
type: "string", // type: "string",
align: "left", // align: "left",
headerAlign: "left", // headerAlign: "left",
flex: 1, // flex: 1,
}, // },
{ {
field: "actions", field: "actions",
headerName: "Действия", headerName: "Действия",
@ -110,7 +111,6 @@ export const VehicleList = observer(() => {
<List> <List>
<CustomDataGrid <CustomDataGrid
{...dataGridProps} {...dataGridProps}
languageEnabled
columns={columns} columns={columns}
localeText={localeText} localeText={localeText}
getRowId={(row: any) => row.id} getRowId={(row: any) => row.id}