feat: Update admin panel
This commit is contained in:
@@ -5,6 +5,7 @@ export interface NavigationItem {
|
|||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
for_admin?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
nestedItems?: NavigationItem[];
|
nestedItems?: NavigationItem[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|||||||
import type { NavigationItem } from "../model";
|
import type { NavigationItem } from "../model";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
|
import { authStore } from "@shared";
|
||||||
|
|
||||||
interface NavigationItemProps {
|
interface NavigationItemProps {
|
||||||
item: NavigationItem;
|
item: NavigationItem;
|
||||||
@@ -30,9 +31,21 @@ export const NavigationItemComponent: React.FC<NavigationItemProps> = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||||
|
const { payload } = authStore;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const isAdmin = payload?.is_admin || false;
|
||||||
|
|
||||||
const isActive = item.path ? location.pathname.startsWith(item.path) : false;
|
const isActive = item.path ? location.pathname.startsWith(item.path) : false;
|
||||||
|
|
||||||
|
const filteredNestedItems = item.nestedItems?.filter((nestedItem) => {
|
||||||
|
if (nestedItem.for_admin) {
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (item.id === "all" && !open) {
|
if (item.id === "all" && !open) {
|
||||||
onDrawerOpen?.();
|
onDrawerOpen?.();
|
||||||
@@ -108,15 +121,16 @@ export const NavigationItemComponent: React.FC<NavigationItemProps> = ({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{item.nestedItems &&
|
{filteredNestedItems &&
|
||||||
|
filteredNestedItems.length > 0 &&
|
||||||
open &&
|
open &&
|
||||||
(isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />)}
|
(isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />)}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{item.nestedItems && (
|
{filteredNestedItems && filteredNestedItems.length > 0 && (
|
||||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||||
<List component="div" disablePadding>
|
<List component="div" disablePadding>
|
||||||
{item.nestedItems.map((nestedItem) => (
|
{filteredNestedItems.map((nestedItem) => (
|
||||||
<NavigationItemComponent
|
<NavigationItemComponent
|
||||||
key={nestedItem.id}
|
key={nestedItem.id}
|
||||||
item={nestedItem}
|
item={nestedItem}
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import { NAVIGATION_ITEMS } from "@shared";
|
import { authStore, NAVIGATION_ITEMS } from "@shared";
|
||||||
import { NavigationItem, NavigationItemComponent } from "@entities";
|
import { NavigationItem, NavigationItemComponent } from "@entities";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
interface NavigationListProps {
|
interface NavigationListProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onDrawerOpen?: () => void;
|
onDrawerOpen?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavigationList = ({ open, onDrawerOpen }: NavigationListProps) => {
|
export const NavigationList = observer(
|
||||||
const primaryItems = NAVIGATION_ITEMS.primary;
|
({ open, onDrawerOpen }: NavigationListProps) => {
|
||||||
const secondaryItems = NAVIGATION_ITEMS.secondary;
|
const { payload } = authStore;
|
||||||
|
// @ts-ignore
|
||||||
|
const isAdmin = Boolean(payload?.is_admin) || false;
|
||||||
|
|
||||||
|
const primaryItems = NAVIGATION_ITEMS.primary.filter((item) => {
|
||||||
|
if (item.for_admin) {
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.nestedItems && item.nestedItems.length > 0) {
|
||||||
|
return item.nestedItems.some((nestedItem) => {
|
||||||
|
if (nestedItem.for_admin) {
|
||||||
|
return isAdmin;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -26,7 +46,7 @@ export const NavigationList = ({ open, onDrawerOpen }: NavigationListProps) => {
|
|||||||
</List>
|
</List>
|
||||||
<Divider />
|
<Divider />
|
||||||
<List>
|
<List>
|
||||||
{secondaryItems.map((item) => (
|
{NAVIGATION_ITEMS.secondary.map((item) => (
|
||||||
<NavigationItemComponent
|
<NavigationItemComponent
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item as NavigationItem}
|
item={item as NavigationItem}
|
||||||
@@ -38,4 +58,5 @@ export const NavigationList = ({ open, onDrawerOpen }: NavigationListProps) => {
|
|||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ export const LoginPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigate("/map");
|
navigate("/map");
|
||||||
|
try {
|
||||||
await getUsers();
|
await getUsers();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
toast.success("Вход в систему выполнен успешно");
|
toast.success("Вход в систему выполнен успешно");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(
|
setError(
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ import {
|
|||||||
Draw,
|
Draw,
|
||||||
Modify,
|
Modify,
|
||||||
Select,
|
Select,
|
||||||
defaults as defaultInteractions,
|
DragPan,
|
||||||
|
MouseWheelZoom,
|
||||||
|
KeyboardPan,
|
||||||
|
KeyboardZoom,
|
||||||
|
PinchZoom,
|
||||||
|
PinchRotate,
|
||||||
} from "ol/interaction";
|
} from "ol/interaction";
|
||||||
import { DrawEvent } from "ol/interaction/Draw";
|
import { DrawEvent } from "ol/interaction/Draw";
|
||||||
import { SelectEvent } from "ol/interaction/Select";
|
import { SelectEvent } from "ol/interaction/Select";
|
||||||
@@ -102,6 +107,7 @@ import {
|
|||||||
sightsStore,
|
sightsStore,
|
||||||
menuStore,
|
menuStore,
|
||||||
selectedCityStore,
|
selectedCityStore,
|
||||||
|
carrierStore,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
|
|
||||||
// Функция для сброса кешей карты
|
// Функция для сброса кешей карты
|
||||||
@@ -123,6 +129,7 @@ interface ApiRoute {
|
|||||||
path: [number, number][];
|
path: [number, number][];
|
||||||
center_latitude: number;
|
center_latitude: number;
|
||||||
center_longitude: number;
|
center_longitude: number;
|
||||||
|
carrier_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiStation {
|
interface ApiStation {
|
||||||
@@ -270,6 +277,23 @@ class MapStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get filteredRoutes(): ApiRoute[] {
|
||||||
|
const selectedCityId = selectedCityStore.selectedCityId;
|
||||||
|
if (!selectedCityId) {
|
||||||
|
return this.routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем carriers для текущего языка
|
||||||
|
const carriers = carrierStore.carriers.ru.data;
|
||||||
|
|
||||||
|
// Фильтруем маршруты по городу через carriers
|
||||||
|
return this.routes.filter((route: ApiRoute) => {
|
||||||
|
// Находим carrier для маршрута
|
||||||
|
const carrier = carriers.find((c: any) => c.id === route.carrier_id);
|
||||||
|
return carrier && carrier.city_id === selectedCityId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get filteredSights(): ApiSight[] {
|
get filteredSights(): ApiSight[] {
|
||||||
const selectedCityId = selectedCityStore.selectedCityId;
|
const selectedCityId = selectedCityStore.selectedCityId;
|
||||||
if (!selectedCityId) {
|
if (!selectedCityId) {
|
||||||
@@ -287,7 +311,14 @@ class MapStore {
|
|||||||
languageInstance("ru").get(`/route/${id}`)
|
languageInstance("ru").get(`/route/${id}`)
|
||||||
);
|
);
|
||||||
const routeResponses = await Promise.all(routePromises);
|
const routeResponses = await Promise.all(routePromises);
|
||||||
this.routes = routeResponses.map((res) => res.data);
|
this.routes = routeResponses.map((res) => ({
|
||||||
|
id: res.data.id,
|
||||||
|
route_number: res.data.route_number,
|
||||||
|
path: res.data.path,
|
||||||
|
center_latitude: res.data.center_latitude,
|
||||||
|
center_longitude: res.data.center_longitude,
|
||||||
|
carrier_id: res.data.carrier_id,
|
||||||
|
}));
|
||||||
|
|
||||||
this.routes = this.routes.sort((a, b) =>
|
this.routes = this.routes.sort((a, b) =>
|
||||||
a.route_number.localeCompare(b.route_number)
|
a.route_number.localeCompare(b.route_number)
|
||||||
@@ -372,13 +403,28 @@ class MapStore {
|
|||||||
"EPSG:3857"
|
"EPSG:3857"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Автоматически назначаем перевозчика из выбранного города
|
||||||
|
let carrier_id = 0;
|
||||||
|
let carrier = "";
|
||||||
|
|
||||||
|
if (selectedCityStore.selectedCityId) {
|
||||||
|
const carriersInCity = carrierStore.carriers.ru.data.filter(
|
||||||
|
(c: any) => c.city_id === selectedCityStore.selectedCityId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (carriersInCity.length > 0) {
|
||||||
|
carrier_id = carriersInCity[0].id;
|
||||||
|
carrier = carriersInCity[0].full_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const routeData = {
|
const routeData = {
|
||||||
route_number,
|
route_number,
|
||||||
path,
|
path,
|
||||||
center_latitude,
|
center_latitude,
|
||||||
center_longitude,
|
center_longitude,
|
||||||
carrier: "",
|
carrier,
|
||||||
carrier_id: 0,
|
carrier_id,
|
||||||
governor_appeal: 0,
|
governor_appeal: 0,
|
||||||
rotate: 0,
|
rotate: 0,
|
||||||
route_direction: false,
|
route_direction: false,
|
||||||
@@ -388,6 +434,12 @@ class MapStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await routeStore.createRoute(routeData);
|
await routeStore.createRoute(routeData);
|
||||||
|
|
||||||
|
if (!carrier_id) {
|
||||||
|
toast.error(
|
||||||
|
"В выбранном городе нет доступных перевозчиков, маршрут отображается в общем списке"
|
||||||
|
);
|
||||||
|
}
|
||||||
createdItem = routeStore.routes.data[routeStore.routes.data.length - 1];
|
createdItem = routeStore.routes.data[routeStore.routes.data.length - 1];
|
||||||
} else if (featureType === "sight") {
|
} else if (featureType === "sight") {
|
||||||
const name = properties.name || "Достопримечательность 1";
|
const name = properties.name || "Достопримечательность 1";
|
||||||
@@ -935,7 +987,33 @@ class MapService {
|
|||||||
center: transform(initialCenter, "EPSG:4326", "EPSG:3857"),
|
center: transform(initialCenter, "EPSG:4326", "EPSG:3857"),
|
||||||
zoom: initialZoom,
|
zoom: initialZoom,
|
||||||
}),
|
}),
|
||||||
interactions: defaultInteractions({ doubleClickZoom: false }),
|
interactions: [
|
||||||
|
new MouseWheelZoom(),
|
||||||
|
new KeyboardPan(),
|
||||||
|
new KeyboardZoom(),
|
||||||
|
new PinchZoom(),
|
||||||
|
new PinchRotate(),
|
||||||
|
// Отключаем DoubleClickZoom как было изначально
|
||||||
|
// new DoubleClickZoom(),
|
||||||
|
new DragPan({
|
||||||
|
condition: (event) => {
|
||||||
|
// Разрешаем перетаскивание только при нажатии средней кнопки мыши (колёсико)
|
||||||
|
const originalEvent = event.originalEvent;
|
||||||
|
if (!originalEvent) return false;
|
||||||
|
|
||||||
|
// Проверяем, что это событие мыши и нажата средняя кнопка
|
||||||
|
if (
|
||||||
|
originalEvent.type === "pointerdown" ||
|
||||||
|
originalEvent.type === "pointermove"
|
||||||
|
) {
|
||||||
|
const pointerEvent = originalEvent as PointerEvent;
|
||||||
|
return pointerEvent.buttons === 4; // 4 = средняя кнопка мыши
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
controls: [],
|
controls: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1249,8 +1327,52 @@ class MapService {
|
|||||||
this.map.on("pointermove", this.boundHandlePointerMove as any);
|
this.map.on("pointermove", this.boundHandlePointerMove as any);
|
||||||
const targetEl = this.map.getTargetElement();
|
const targetEl = this.map.getTargetElement();
|
||||||
if (targetEl instanceof HTMLElement) {
|
if (targetEl instanceof HTMLElement) {
|
||||||
|
// Устанавливаем курсор pointer по умолчанию для всей карты
|
||||||
|
targetEl.style.cursor = "pointer";
|
||||||
targetEl.addEventListener("contextmenu", this.boundHandleContextMenu);
|
targetEl.addEventListener("contextmenu", this.boundHandleContextMenu);
|
||||||
targetEl.addEventListener("pointerleave", this.boundHandlePointerLeave);
|
targetEl.addEventListener("pointerleave", this.boundHandlePointerLeave);
|
||||||
|
|
||||||
|
// Добавляем обработчики для изменения курсора при нажатии средней кнопки мыши
|
||||||
|
targetEl.addEventListener("pointerdown", (e) => {
|
||||||
|
if (e.buttons === 4) {
|
||||||
|
// Средняя кнопка мыши
|
||||||
|
e.preventDefault(); // Предотвращаем скролл страницы
|
||||||
|
targetEl.style.cursor = "grabbing";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
targetEl.addEventListener("pointerup", (e) => {
|
||||||
|
if (e.button === 1) {
|
||||||
|
// Средняя кнопка мыши отпущена
|
||||||
|
e.preventDefault(); // Предотвращаем скролл страницы
|
||||||
|
targetEl.style.cursor = "pointer";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Также добавляем обработчик для mousedown/mouseup для совместимости
|
||||||
|
targetEl.addEventListener("mousedown", (e) => {
|
||||||
|
if (e.button === 1) {
|
||||||
|
// Средняя кнопка мыши
|
||||||
|
e.preventDefault(); // Предотвращаем скролл страницы
|
||||||
|
targetEl.style.cursor = "grabbing";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
targetEl.addEventListener("mouseup", (e) => {
|
||||||
|
if (e.button === 1) {
|
||||||
|
// Средняя кнопка мыши отпущена
|
||||||
|
e.preventDefault(); // Предотвращаем скролл страницы
|
||||||
|
targetEl.style.cursor = "pointer";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Дополнительная защита от нежелательного поведения средней кнопки мыши
|
||||||
|
targetEl.addEventListener("auxclick", (e) => {
|
||||||
|
if (e.button === 1) {
|
||||||
|
// Средняя кнопка мыши
|
||||||
|
e.preventDefault(); // Предотвращаем открытие ссылки в новой вкладке
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
document.addEventListener("keydown", this.boundHandleKeyDown);
|
document.addEventListener("keydown", this.boundHandleKeyDown);
|
||||||
this.activateEditMode();
|
this.activateEditMode();
|
||||||
@@ -1270,7 +1392,7 @@ class MapService {
|
|||||||
|
|
||||||
public loadFeaturesFromApi(
|
public loadFeaturesFromApi(
|
||||||
_apiStations: typeof mapStore.stations,
|
_apiStations: typeof mapStore.stations,
|
||||||
apiRoutes: typeof mapStore.routes,
|
_apiRoutes: typeof mapStore.routes,
|
||||||
_apiSights: typeof mapStore.sights
|
_apiSights: typeof mapStore.sights
|
||||||
): void {
|
): void {
|
||||||
if (!this.map) return;
|
if (!this.map) return;
|
||||||
@@ -1282,6 +1404,7 @@ class MapService {
|
|||||||
// Используем фильтрованные данные из mapStore
|
// Используем фильтрованные данные из mapStore
|
||||||
const filteredStations = mapStore.filteredStations;
|
const filteredStations = mapStore.filteredStations;
|
||||||
const filteredSights = mapStore.filteredSights;
|
const filteredSights = mapStore.filteredSights;
|
||||||
|
const filteredRoutes = mapStore.filteredRoutes;
|
||||||
|
|
||||||
filteredStations.forEach((station) => {
|
filteredStations.forEach((station) => {
|
||||||
if (station.longitude == null || station.latitude == null) return;
|
if (station.longitude == null || station.latitude == null) return;
|
||||||
@@ -1313,17 +1436,16 @@ class MapService {
|
|||||||
pointFeatures.push(feature);
|
pointFeatures.push(feature);
|
||||||
});
|
});
|
||||||
|
|
||||||
apiRoutes.forEach((route) => {
|
filteredRoutes.forEach((route) => {
|
||||||
if (!route.path || route.path.length === 0) return;
|
if (!route.path || route.path.length === 0) return;
|
||||||
const coordinates = route.path
|
const coordinates = route.path
|
||||||
.filter((c) => c && c[0] != null && c[1] != null)
|
.filter((c) => c && c[0] != null && c[1] != null)
|
||||||
.map((c: [number, number]) =>
|
.map((c: [number, number]) =>
|
||||||
transform([c[1], c[0]], "EPSG:4326", projection)
|
transform([c[1], c[0]], "EPSG:4326", projection)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (coordinates.length === 0) return;
|
if (coordinates.length === 0) return;
|
||||||
|
|
||||||
const routeId = `route-${route.id}`;
|
const routeId = `route-${route.id}`;
|
||||||
|
|
||||||
const line = new LineString(coordinates);
|
const line = new LineString(coordinates);
|
||||||
const lineFeature = new Feature({
|
const lineFeature = new Feature({
|
||||||
geometry: line,
|
geometry: line,
|
||||||
@@ -1332,8 +1454,6 @@ class MapService {
|
|||||||
lineFeature.setId(routeId);
|
lineFeature.setId(routeId);
|
||||||
lineFeature.set("featureType", "route");
|
lineFeature.set("featureType", "route");
|
||||||
lineFeatures.push(lineFeature);
|
lineFeatures.push(lineFeature);
|
||||||
|
|
||||||
// Не создаем прокси-точки для маршрутов - они должны оставаться только линиями
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pointSource.addFeatures(pointFeatures);
|
this.pointSource.addFeatures(pointFeatures);
|
||||||
@@ -1359,6 +1479,14 @@ class MapService {
|
|||||||
this.routeLayer.changed();
|
this.routeLayer.changed();
|
||||||
}
|
}
|
||||||
if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);
|
if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);
|
||||||
|
|
||||||
|
// Сбрасываем курсор при покидании области карты
|
||||||
|
if (this.map) {
|
||||||
|
const targetEl = this.map.getTargetElement();
|
||||||
|
if (targetEl instanceof HTMLElement) {
|
||||||
|
targetEl.style.cursor = "pointer";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleKeyDown(event: KeyboardEvent): void {
|
private handleKeyDown(event: KeyboardEvent): void {
|
||||||
@@ -1565,7 +1693,8 @@ class MapService {
|
|||||||
layerFilter,
|
layerFilter,
|
||||||
hitTolerance: 5,
|
hitTolerance: 5,
|
||||||
});
|
});
|
||||||
this.map.getTargetElement().style.cursor = hit ? "pointer" : "";
|
// Устанавливаем курсор pointer для всей карты, чтобы показать возможность перетаскивания колёсиком
|
||||||
|
this.map.getTargetElement().style.cursor = hit ? "pointer" : "pointer";
|
||||||
|
|
||||||
const featureAtPixel: Feature<Geometry> | undefined =
|
const featureAtPixel: Feature<Geometry> | undefined =
|
||||||
this.map.forEachFeatureAtPixel(
|
this.map.forEachFeatureAtPixel(
|
||||||
@@ -2137,14 +2266,21 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
|||||||
return feature;
|
return feature;
|
||||||
});
|
});
|
||||||
|
|
||||||
const lines = actualFeatures.filter(
|
const lines = mapStore.filteredRoutes.map((route) => {
|
||||||
(f) => f.get("featureType") === "route"
|
const feature = new Feature({
|
||||||
);
|
geometry: new LineString(route.path),
|
||||||
|
name: route.route_number,
|
||||||
|
});
|
||||||
|
feature.setId(`route-${route.id}`);
|
||||||
|
feature.set("featureType", "route");
|
||||||
|
return feature;
|
||||||
|
});
|
||||||
|
|
||||||
return [...stations, ...sights, ...lines];
|
return [...stations, ...sights, ...lines];
|
||||||
}, [
|
}, [
|
||||||
mapStore.filteredStations,
|
mapStore.filteredStations,
|
||||||
mapStore.filteredSights,
|
mapStore.filteredSights,
|
||||||
|
mapStore.filteredRoutes,
|
||||||
actualFeatures,
|
actualFeatures,
|
||||||
selectedCityId,
|
selectedCityId,
|
||||||
mapStore,
|
mapStore,
|
||||||
@@ -2613,6 +2749,7 @@ export const MapPage: React.FC = observer(() => {
|
|||||||
mapStore.getRoutes(),
|
mapStore.getRoutes(),
|
||||||
mapStore.getStations(),
|
mapStore.getStations(),
|
||||||
mapStore.getSights(),
|
mapStore.getSights(),
|
||||||
|
carrierStore.getCarriers("ru"),
|
||||||
]);
|
]);
|
||||||
mapService.loadFeaturesFromApi(
|
mapService.loadFeaturesFromApi(
|
||||||
mapStore.stations,
|
mapStore.stations,
|
||||||
|
|||||||
@@ -16,13 +16,18 @@ import {
|
|||||||
import { MediaViewer } from "@widgets";
|
import { MediaViewer } from "@widgets";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Loader2, Save, Plus } from "lucide-react";
|
import { ArrowLeft, Loader2, Save, Plus } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { carrierStore } from "../../../shared/store/CarrierStore";
|
import { carrierStore } from "../../../shared/store/CarrierStore";
|
||||||
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
||||||
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
||||||
import { languageStore, SelectArticleModal, SelectMediaDialog } from "@shared";
|
import {
|
||||||
|
languageStore,
|
||||||
|
SelectArticleModal,
|
||||||
|
SelectMediaDialog,
|
||||||
|
selectedCityStore,
|
||||||
|
} from "@shared";
|
||||||
|
|
||||||
export const RouteCreatePage = observer(() => {
|
export const RouteCreatePage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -50,6 +55,21 @@ export const RouteCreatePage = observer(() => {
|
|||||||
articlesStore.getArticleList();
|
articlesStore.getArticleList();
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
|
// Фильтруем перевозчиков только из выбранного города
|
||||||
|
const filteredCarriers = useMemo(() => {
|
||||||
|
const carriers =
|
||||||
|
carrierStore.carriers[language as keyof typeof carrierStore.carriers]
|
||||||
|
.data || [];
|
||||||
|
|
||||||
|
if (!selectedCityStore.selectedCityId) {
|
||||||
|
return carriers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return carriers.filter(
|
||||||
|
(carrier: any) => carrier.city_id === selectedCityStore.selectedCityId
|
||||||
|
);
|
||||||
|
}, [carrierStore.carriers, language, selectedCityStore.selectedCityId]);
|
||||||
|
|
||||||
const validateCoordinates = (value: string) => {
|
const validateCoordinates = (value: string) => {
|
||||||
try {
|
try {
|
||||||
const lines = value.trim().split("\n");
|
const lines = value.trim().split("\n");
|
||||||
@@ -194,16 +214,10 @@ export const RouteCreatePage = observer(() => {
|
|||||||
value={carrier}
|
value={carrier}
|
||||||
label="Выберите перевозчика"
|
label="Выберите перевозчика"
|
||||||
onChange={(e) => setCarrier(e.target.value as string)}
|
onChange={(e) => setCarrier(e.target.value as string)}
|
||||||
disabled={
|
disabled={filteredCarriers.length === 0}
|
||||||
carrierStore.carriers[
|
|
||||||
language as keyof typeof carrierStore.carriers
|
|
||||||
].data?.length === 0
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<MenuItem value="">Не выбрано</MenuItem>
|
<MenuItem value="">Не выбрано</MenuItem>
|
||||||
{carrierStore.carriers[
|
{filteredCarriers.map((carrier: any) => (
|
||||||
language as keyof typeof carrierStore.carriers
|
|
||||||
].data?.map((carrier) => (
|
|
||||||
<MenuItem key={carrier.id} value={carrier.id}>
|
<MenuItem key={carrier.id} value={carrier.id}>
|
||||||
{carrier.full_name}
|
{carrier.full_name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface NavigationItem {
|
|||||||
label: string;
|
label: string;
|
||||||
icon?: LucideIcon | React.ReactNode;
|
icon?: LucideIcon | React.ReactNode;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
for_admin?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
nestedItems?: NavigationItem[];
|
nestedItems?: NavigationItem[];
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@@ -40,6 +41,7 @@ export const NAVIGATION_ITEMS: {
|
|||||||
label: "Снапшоты",
|
label: "Снапшоты",
|
||||||
icon: GitBranch,
|
icon: GitBranch,
|
||||||
path: "/snapshot",
|
path: "/snapshot",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "map",
|
id: "map",
|
||||||
@@ -52,6 +54,7 @@ export const NAVIGATION_ITEMS: {
|
|||||||
label: "Устройства",
|
label: "Устройства",
|
||||||
icon: Cpu,
|
icon: Cpu,
|
||||||
path: "/devices",
|
path: "/devices",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// id: "vehicles",
|
// id: "vehicles",
|
||||||
@@ -64,6 +67,7 @@ export const NAVIGATION_ITEMS: {
|
|||||||
label: "Пользователи",
|
label: "Пользователи",
|
||||||
icon: Users,
|
icon: Users,
|
||||||
path: "/user",
|
path: "/user",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "all",
|
id: "all",
|
||||||
@@ -106,12 +110,14 @@ export const NAVIGATION_ITEMS: {
|
|||||||
label: "Страны",
|
label: "Страны",
|
||||||
icon: Earth,
|
icon: Earth,
|
||||||
path: "/country",
|
path: "/country",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "cities",
|
id: "cities",
|
||||||
label: "Города",
|
label: "Города",
|
||||||
icon: Building2,
|
icon: Building2,
|
||||||
path: "/city",
|
path: "/city",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "carriers",
|
id: "carriers",
|
||||||
@@ -119,6 +125,7 @@ export const NAVIGATION_ITEMS: {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
icon: CarrierSvg,
|
icon: CarrierSvg,
|
||||||
path: "/carrier",
|
path: "/carrier",
|
||||||
|
for_admin: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ export const LeftWidgetTab = observer(
|
|||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
{sight.common.watermark_lu && (
|
||||||
<img
|
<img
|
||||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||||
sight.common.watermark_lu
|
sight.common.watermark_lu
|
||||||
@@ -328,7 +329,9 @@ export const LeftWidgetTab = observer(
|
|||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sight.common.watermark_rd && (
|
||||||
<img
|
<img
|
||||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||||
sight.common.watermark_rd
|
sight.common.watermark_rd
|
||||||
@@ -341,6 +344,7 @@ export const LeftWidgetTab = observer(
|
|||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ImagePlus size={48} color="white" />
|
<ImagePlus size={48} color="white" />
|
||||||
|
|||||||
Reference in New Issue
Block a user