feat: Route list page
This commit is contained in:
@ -2,7 +2,7 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { articlesStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Trash2, FileText } from "lucide-react";
|
||||
import { Trash2, Eye } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
@ -31,8 +31,8 @@ export const ArticleListPage = observer(() => {
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/media/${params.row.id}`)}>
|
||||
<FileText size={20} className="text-green-500" />
|
||||
<button onClick={() => navigate(`/article/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -48,7 +48,7 @@ export const ArticleListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = articleList.map((article) => ({
|
||||
const rows = articleList[language].data.map((article) => ({
|
||||
id: article.id,
|
||||
heading: article.heading,
|
||||
body: article.body,
|
||||
|
@ -63,7 +63,7 @@ export const CarrierCreatePage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/carrier")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -61,7 +61,7 @@ export const CarrierEditPage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/carrier")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -62,7 +62,7 @@ export const CarrierListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = carriers.map((carrier) => ({
|
||||
const rows = carriers.data?.map((carrier) => ({
|
||||
id: carrier.id,
|
||||
full_name: carrier.full_name,
|
||||
short_name: carrier.short_name,
|
||||
|
@ -72,7 +72,7 @@ export const CityCreatePage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/city")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -89,7 +89,7 @@ export const CityEditPage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/city")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -33,7 +33,7 @@ export const CountryCreatePage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/country")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -43,7 +43,7 @@ export const CountryEditPage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/country")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
File diff suppressed because it is too large
Load Diff
129
src/pages/MapPage/mapStore.ts
Normal file
129
src/pages/MapPage/mapStore.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { languageInstance } from "@shared";
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
interface ApiRoute {
|
||||
id: number;
|
||||
route_number: string;
|
||||
path: [number, number][];
|
||||
}
|
||||
|
||||
interface ApiStation {
|
||||
id: number;
|
||||
name: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
interface ApiSight {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
class MapStore {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
routes: ApiRoute[] = [];
|
||||
stations: ApiStation[] = [];
|
||||
sights: ApiSight[] = [];
|
||||
|
||||
getRoutes = async () => {
|
||||
const routes = await languageInstance("ru").get("/route");
|
||||
|
||||
const mappedRoutes = routes.data.map((route: any) => ({
|
||||
id: route.id,
|
||||
route_number: route.route_number,
|
||||
path: route.path,
|
||||
}));
|
||||
|
||||
this.routes = mappedRoutes;
|
||||
};
|
||||
|
||||
getStations = async () => {
|
||||
const stations = await languageInstance("ru").get("/station");
|
||||
const mappedStations = stations.data.map((station: any) => ({
|
||||
id: station.id,
|
||||
name: station.name,
|
||||
latitude: station.latitude,
|
||||
longitude: station.longitude,
|
||||
}));
|
||||
|
||||
this.stations = mappedStations;
|
||||
};
|
||||
|
||||
getSights = async () => {
|
||||
const sights = await languageInstance("ru").get("/sight");
|
||||
const mappedSights = sights.data.map((sight: any) => ({
|
||||
id: sight.id,
|
||||
name: sight.name,
|
||||
description: sight.description,
|
||||
latitude: sight.latitude,
|
||||
longitude: sight.longitude,
|
||||
}));
|
||||
|
||||
this.sights = mappedSights;
|
||||
};
|
||||
|
||||
deleteRecourse = async (recourse: string, id: number) => {
|
||||
await languageInstance("ru").delete(`/${recourse}/${id}`);
|
||||
if (recourse === "route") {
|
||||
this.routes = this.routes.filter((route) => route.id !== id);
|
||||
} else if (recourse === "station") {
|
||||
this.stations = this.stations.filter((station) => station.id !== id);
|
||||
} else if (recourse === "sight") {
|
||||
this.sights = this.sights.filter((sight) => sight.id !== id);
|
||||
}
|
||||
};
|
||||
|
||||
handleSave = async (json: string) => {
|
||||
const sights: any[] = [];
|
||||
const routes: any[] = [];
|
||||
const stations: any[] = [];
|
||||
|
||||
const parsedJSON = JSON.parse(json);
|
||||
|
||||
console.log(parsedJSON);
|
||||
parsedJSON.features.forEach((feature: any) => {
|
||||
const { geometry, properties, id } = feature;
|
||||
const idCanBeSplited = id.split("-");
|
||||
|
||||
if (!(idCanBeSplited.length > 1)) {
|
||||
if (properties.featureType === "station") {
|
||||
stations.push({
|
||||
name: properties.name || "",
|
||||
latitude: geometry.coordinates[1],
|
||||
longitude: geometry.coordinates[0],
|
||||
});
|
||||
} else if (properties.featureType === "route") {
|
||||
routes.push({
|
||||
route_number: properties.name || "",
|
||||
path: geometry.coordinates,
|
||||
});
|
||||
} else if (properties.featureType === "sight") {
|
||||
sights.push({
|
||||
name: properties.name || "",
|
||||
description: properties.description || "",
|
||||
latitude: geometry.coordinates[1],
|
||||
longitude: geometry.coordinates[0],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const station of stations) {
|
||||
await languageInstance("ru").post("/station", station);
|
||||
}
|
||||
for (const route of routes) {
|
||||
await languageInstance("ru").post("/route", route);
|
||||
}
|
||||
for (const sight of sights) {
|
||||
await languageInstance("ru").post("/sight", sight);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new MapStore();
|
@ -164,7 +164,7 @@ export const MediaEditPage = observer(() => {
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<ArrowLeft size={20} />}
|
||||
onClick={() => navigate("/media")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
Назад
|
||||
</Button>
|
||||
|
@ -6,20 +6,94 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Typography,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { carrierStore } from "../../../shared/store/CarrierStore";
|
||||
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
||||
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
||||
|
||||
export const RouteCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [carrier, setCarrier] = useState<string>("");
|
||||
const [routeNumber, setRouteNumber] = useState("");
|
||||
const [direction, setDirection] = useState("");
|
||||
const [routeCoords, setRouteCoords] = useState("");
|
||||
const [govRouteNumber, setGovRouteNumber] = useState("");
|
||||
const [governorAppeal, setGovernorAppeal] = useState<string>("");
|
||||
const [direction, setDirection] = useState("backward");
|
||||
const [scaleMin, setScaleMin] = useState("");
|
||||
const [scaleMax, setScaleMax] = useState("");
|
||||
const [turn, setTurn] = useState("");
|
||||
const [centerLat, setCenterLat] = useState("");
|
||||
const [centerLng, setCenterLng] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
carrierStore.getCarriers();
|
||||
articlesStore.getArticleList();
|
||||
}, []);
|
||||
|
||||
const handleCreateRoute = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// Преобразуем значения в нужные типы
|
||||
const carrier_id = Number(carrier);
|
||||
const governor_appeal = Number(governorAppeal);
|
||||
const scale_min = scaleMin ? Number(scaleMin) : undefined;
|
||||
const scale_max = scaleMax ? Number(scaleMax) : undefined;
|
||||
const rotate = turn ? Number(turn) : undefined;
|
||||
const center_latitude = centerLat ? Number(centerLat) : undefined;
|
||||
const center_longitude = centerLng ? Number(centerLng) : undefined;
|
||||
const route_direction = direction === "forward";
|
||||
// Координаты маршрута как массив массивов чисел
|
||||
const path = routeCoords
|
||||
.split("\n")
|
||||
.map((line) =>
|
||||
line
|
||||
.split(" ")
|
||||
.map((coord) => Number(coord.trim()))
|
||||
.filter((n) => !isNaN(n))
|
||||
)
|
||||
.filter((arr) => arr.length === 2);
|
||||
|
||||
// Собираем объект маршрута
|
||||
const newRoute: Partial<Route> = {
|
||||
carrier:
|
||||
carrierStore.carriers.data.find((c: any) => c.id === carrier_id)
|
||||
?.full_name || "",
|
||||
carrier_id,
|
||||
route_number: routeNumber,
|
||||
route_sys_number: govRouteNumber,
|
||||
governor_appeal,
|
||||
route_direction,
|
||||
scale_min,
|
||||
scale_max,
|
||||
rotate,
|
||||
center_latitude,
|
||||
center_longitude,
|
||||
path,
|
||||
};
|
||||
|
||||
// Отправка на сервер (пример, если есть routeStore.createRoute)
|
||||
let createdRoute: Route | null = null;
|
||||
|
||||
await routeStore.createRoute(newRoute);
|
||||
toast.success("Маршрут успешно создан");
|
||||
navigate(-1);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Произошла ошибка при создании маршрута");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex justify-between items-center">
|
||||
@ -28,11 +102,31 @@ export const RouteCreatePage = observer(() => {
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
Маршруты / Создать
|
||||
</button>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Создание маршрута</h1>
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<Typography variant="h5" fontWeight={700}>
|
||||
Создать маршрут
|
||||
</Typography>
|
||||
<Box className="flex flex-col gap-6 w-full">
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Выберите перевозчика</InputLabel>
|
||||
<Select
|
||||
value={carrier}
|
||||
label="Выберите перевозчика"
|
||||
onChange={(e) => setCarrier(e.target.value as string)}
|
||||
disabled={carrierStore.carriers.data.length === 0}
|
||||
>
|
||||
<MenuItem value="">Не выбрано</MenuItem>
|
||||
{carrierStore.carriers.data.map(
|
||||
(c: (typeof carrierStore.carriers.data)[number]) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.full_name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Номер маршрута"
|
||||
@ -40,38 +134,88 @@ export const RouteCreatePage = observer(() => {
|
||||
value={routeNumber}
|
||||
onChange={(e) => setRouteNumber(e.target.value)}
|
||||
/>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Направление</InputLabel>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Координаты маршрута"
|
||||
multiline
|
||||
minRows={3}
|
||||
value={routeCoords}
|
||||
onChange={(e) => setRouteCoords(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Номер маршрута в Говорящем Городе"
|
||||
required
|
||||
value={govRouteNumber}
|
||||
onChange={(e) => setGovRouteNumber(e.target.value)}
|
||||
/>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Обращение губернатора</InputLabel>
|
||||
<Select
|
||||
value={direction}
|
||||
label="Направление"
|
||||
onChange={(e) => setDirection(e.target.value)}
|
||||
required
|
||||
value={governorAppeal}
|
||||
label="Обращение губернатора"
|
||||
onChange={(e) => setGovernorAppeal(e.target.value as string)}
|
||||
disabled={articlesStore.articleList.ru.data.length === 0}
|
||||
>
|
||||
<MenuItem value="forward">Прямое</MenuItem>
|
||||
<MenuItem value="backward">Обратное</MenuItem>
|
||||
<MenuItem value="">Не выбрано</MenuItem>
|
||||
{articlesStore.articleList.ru.data.map(
|
||||
(a: (typeof articlesStore.articleList.ru.data)[number]) => (
|
||||
<MenuItem key={a.id} value={a.id}>
|
||||
{a.heading}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Прямой/обратный маршрут</InputLabel>
|
||||
<Select
|
||||
value={direction}
|
||||
label="Прямой/обратный маршрут"
|
||||
onChange={(e) => setDirection(e.target.value)}
|
||||
>
|
||||
<MenuItem value="forward">Прямой</MenuItem>
|
||||
<MenuItem value="backward">Обратный</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Масштаб (мин)"
|
||||
value={scaleMin}
|
||||
onChange={(e) => setScaleMin(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Масштаб (макс)"
|
||||
value={scaleMax}
|
||||
onChange={(e) => setScaleMax(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Поворот"
|
||||
value={turn}
|
||||
onChange={(e) => setTurn(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Центр. широта"
|
||||
value={centerLat}
|
||||
onChange={(e) => setCenterLat(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Центр. долгота"
|
||||
value={centerLng}
|
||||
onChange={(e) => setCenterLng(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// await createRoute(routeNumber, direction === "forward");
|
||||
setIsLoading(false);
|
||||
toast.success("Маршрут успешно создан");
|
||||
navigate(-1);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Произошла ошибка");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
onClick={handleCreateRoute}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
|
@ -72,7 +72,7 @@ export const RouteListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = routes.map((route) => ({
|
||||
const rows = routes.data.map((route) => ({
|
||||
id: route.id,
|
||||
carrier: route.carrier,
|
||||
route_number: route.route_number,
|
||||
@ -81,8 +81,6 @@ export const RouteListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Маршруты</h1>
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./RouteListPage";
|
||||
export * from "./RouteListPage";
|
||||
export * from "./RouteCreatePage";
|
||||
|
@ -38,7 +38,7 @@ export const SightListPage = observer(() => {
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/sight/${params.row.id}`)}>
|
||||
<button onClick={() => navigate(`/sight/${params.row.id}/edit`)}>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
{/* <button onClick={() => navigate(`/sight/${params.row.id}`)}>
|
||||
|
@ -3,18 +3,13 @@ import { languageStore, snapshotStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DatabaseBackup, Eye, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CreateButton,
|
||||
DeleteModal,
|
||||
LanguageSwitcher,
|
||||
SnapshotRestore,
|
||||
} from "@widgets";
|
||||
|
||||
import { CreateButton, DeleteModal, SnapshotRestore } from "@widgets";
|
||||
|
||||
export const SnapshotListPage = observer(() => {
|
||||
const { snapshots, getSnapshots, deleteSnapshot, restoreSnapshot } =
|
||||
snapshotStore;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<string | null>(null); // Lifted state
|
||||
const { language } = languageStore;
|
||||
@ -76,8 +71,6 @@ export const SnapshotListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl ">Снапшоты</h1>
|
||||
|
179
src/pages/Station/StationEditPage/index.tsx
Normal file
179
src/pages/Station/StationEditPage/index.tsx
Normal file
@ -0,0 +1,179 @@
|
||||
import {
|
||||
Button,
|
||||
Paper,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save, ImagePlus } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { stationsStore, languageStore, cityStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
||||
import { SelectMediaDialog } from "@shared";
|
||||
|
||||
export const StationEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { language } = languageStore;
|
||||
const { id } = useParams();
|
||||
const {
|
||||
editStationData,
|
||||
getEditStation,
|
||||
setEditCommonData,
|
||||
editStation,
|
||||
setLanguageEditStationData,
|
||||
} = stationsStore;
|
||||
const { cities, getCities } = cityStore;
|
||||
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await editStation(Number(id));
|
||||
toast.success("Станция успешно обновлена");
|
||||
} catch (error) {
|
||||
console.error("Error updating station:", error);
|
||||
toast.error("Ошибка при обновлении станции");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAndSetStationData = async () => {
|
||||
if (!id) return;
|
||||
|
||||
const stationId = Number(id);
|
||||
await getEditStation(stationId);
|
||||
await getCities(language);
|
||||
};
|
||||
|
||||
fetchAndSetStationData();
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название"
|
||||
value={editStationData[language].name || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setLanguageEditStationData(language, {
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="direction-label">Прямой/обратный маршрут</InputLabel>
|
||||
<Select
|
||||
labelId="direction-label"
|
||||
value={editStationData.common.direction ? "Прямой" : "Обратный"}
|
||||
label="Прямой/обратный маршрут"
|
||||
onChange={(e) =>
|
||||
setEditCommonData({
|
||||
direction: e.target.value === "Прямой",
|
||||
})
|
||||
}
|
||||
>
|
||||
<MenuItem value="Прямой">Прямой</MenuItem>
|
||||
<MenuItem value="Обратный">Обратный</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Описание"
|
||||
value={editStationData[language].description || ""}
|
||||
onChange={(e) =>
|
||||
setLanguageEditStationData(language, {
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Адрес"
|
||||
value={editStationData[language].address || ""}
|
||||
onChange={(e) =>
|
||||
setLanguageEditStationData(language, {
|
||||
address: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Координаты"
|
||||
value={`${editStationData.common.latitude} ${editStationData.common.longitude}`}
|
||||
onChange={(e) => {
|
||||
const [latitude, longitude] = e.target.value.split(" ").map(Number);
|
||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
||||
setEditCommonData({
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Город</InputLabel>
|
||||
<Select
|
||||
value={editStationData.common.city_id || ""}
|
||||
label="Город"
|
||||
onChange={(e) => {
|
||||
const selectedCity = cities[language].find(
|
||||
(city) => city.id === e.target.value
|
||||
);
|
||||
setEditCommonData({
|
||||
city_id: e.target.value as number,
|
||||
city: selectedCity?.name || "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{cities[language].map((city) => (
|
||||
<MenuItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={isLoading || !editStationData[language]?.name}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Обновить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -2,19 +2,19 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { languageStore, stationsStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const StationListPage = observer(() => {
|
||||
const { stations, getStations, deleteStation } = stationsStore;
|
||||
const { stationLists, getStationList, deleteStation } = stationsStore;
|
||||
const navigate = useNavigate();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null); // Lifted state
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getStations();
|
||||
getStationList();
|
||||
}, [language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
@ -57,6 +57,9 @@ export const StationListPage = observer(() => {
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/station/${params.row.id}/edit`)}>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
<button onClick={() => navigate(`/station/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
@ -74,7 +77,7 @@ export const StationListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = stations.map((station) => ({
|
||||
const rows = stationLists[language].data.map((station: any) => ({
|
||||
id: station.id,
|
||||
name: station.name,
|
||||
system_name: station.system_name,
|
||||
|
77
src/pages/Station/StationPreviewPage/index.tsx
Normal file
77
src/pages/Station/StationPreviewPage/index.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { Paper } from "@mui/material";
|
||||
import { languageStore, stationsStore } from "@shared";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
export const StationPreviewPage = observer(() => {
|
||||
const { id } = useParams();
|
||||
const { stationPreview, getStationPreview } = stationsStore;
|
||||
const navigate = useNavigate();
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (id) {
|
||||
await getStationPreview(Number(id));
|
||||
}
|
||||
})();
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Название</h1>
|
||||
<p>{stationPreview[id!]?.[language]?.data.name}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Системное название</h1>
|
||||
<p>{stationPreview[id!]?.[language]?.data.system_name}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Направление</h1>
|
||||
<p
|
||||
className={`${
|
||||
stationPreview[id!]?.[language]?.data.direction
|
||||
? "text-green-500"
|
||||
: "text-red-500"
|
||||
}`}
|
||||
>
|
||||
{stationPreview[id!]?.[language]?.data.direction
|
||||
? "Прямой"
|
||||
: "Обратный"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{stationPreview[id!]?.[language]?.data.address && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Адрес</h1>
|
||||
<p>{stationPreview[id!]?.[language]?.data.address}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{stationPreview[id!]?.[language]?.data.description && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Описание</h1>
|
||||
<p>{stationPreview[id!]?.[language]?.data.description}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -1 +1,4 @@
|
||||
export * from "./StationListPage";
|
||||
export * from "./StationCreatePage";
|
||||
export * from "./StationPreviewPage";
|
||||
export * from "./StationEditPage";
|
||||
|
@ -36,7 +36,7 @@ export const UserCreatePage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/user")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -52,7 +52,7 @@ export const UserEditPage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/user")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -79,7 +79,7 @@ export const UserListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = users.map((user) => ({
|
||||
const rows = users.data?.map((user) => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
is_admin: user.is_admin,
|
||||
|
@ -32,7 +32,7 @@ export const VehicleCreatePage = observer(() => {
|
||||
await vehicleStore.createVehicle(
|
||||
Number(tailNumber),
|
||||
type,
|
||||
carrierStore.carriers.find((c) => c.id === carrierId)?.full_name!,
|
||||
carrierStore.carriers.data.find((c) => c.id === carrierId)?.full_name!,
|
||||
carrierId!
|
||||
);
|
||||
toast.success("Транспорт успешно создан");
|
||||
@ -48,7 +48,7 @@ export const VehicleCreatePage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/vehicle")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
@ -88,7 +88,7 @@ export const VehicleCreatePage = observer(() => {
|
||||
required
|
||||
onChange={(e) => setCarrierId(e.target.value as number)}
|
||||
>
|
||||
{carrierStore.carriers.map((carrier) => (
|
||||
{carrierStore.carriers.data.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
|
@ -56,7 +56,7 @@ export const VehicleEditPage = observer(() => {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/vehicle")}
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
|
@ -25,18 +25,15 @@ export const VehicleListPage = observer(() => {
|
||||
field: "tail_number",
|
||||
headerName: "Бортовой номер",
|
||||
flex: 1,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
},
|
||||
{
|
||||
field: "type",
|
||||
headerName: "Тип",
|
||||
flex: 1,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<div className="flex h-full gap-7 items-center">
|
||||
{VEHICLE_TYPES.find((type) => type.value === params.row.type)
|
||||
?.label || params.row.type}
|
||||
</div>
|
||||
@ -47,21 +44,17 @@ export const VehicleListPage = observer(() => {
|
||||
field: "carrier",
|
||||
headerName: "Перевозчик",
|
||||
flex: 1,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
},
|
||||
{
|
||||
field: "city",
|
||||
headerName: "Город",
|
||||
flex: 1,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
},
|
||||
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
flex: 1,
|
||||
width: 200,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
|
||||
@ -88,13 +81,14 @@ export const VehicleListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = vehicles.map((vehicle) => ({
|
||||
const rows = vehicles.data?.map((vehicle) => ({
|
||||
id: vehicle.vehicle.id,
|
||||
tail_number: vehicle.vehicle.tail_number,
|
||||
type: vehicle.vehicle.type,
|
||||
carrier: vehicle.vehicle.carrier,
|
||||
city: carriers.find((carrier) => carrier.id === vehicle.vehicle.carrier_id)
|
||||
?.city,
|
||||
city: carriers.data?.find(
|
||||
(carrier) => carrier.id === vehicle.vehicle.carrier_id
|
||||
)?.city,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user