feat: Update admin panel
This commit is contained in:
@@ -16,7 +16,12 @@ import {
|
||||
Draw,
|
||||
Modify,
|
||||
Select,
|
||||
defaults as defaultInteractions,
|
||||
DragPan,
|
||||
MouseWheelZoom,
|
||||
KeyboardPan,
|
||||
KeyboardZoom,
|
||||
PinchZoom,
|
||||
PinchRotate,
|
||||
} from "ol/interaction";
|
||||
import { DrawEvent } from "ol/interaction/Draw";
|
||||
import { SelectEvent } from "ol/interaction/Select";
|
||||
@@ -102,6 +107,7 @@ import {
|
||||
sightsStore,
|
||||
menuStore,
|
||||
selectedCityStore,
|
||||
carrierStore,
|
||||
} from "@shared";
|
||||
|
||||
// Функция для сброса кешей карты
|
||||
@@ -123,6 +129,7 @@ interface ApiRoute {
|
||||
path: [number, number][];
|
||||
center_latitude: number;
|
||||
center_longitude: number;
|
||||
carrier_id: number;
|
||||
}
|
||||
|
||||
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[] {
|
||||
const selectedCityId = selectedCityStore.selectedCityId;
|
||||
if (!selectedCityId) {
|
||||
@@ -287,7 +311,14 @@ class MapStore {
|
||||
languageInstance("ru").get(`/route/${id}`)
|
||||
);
|
||||
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) =>
|
||||
a.route_number.localeCompare(b.route_number)
|
||||
@@ -372,13 +403,28 @@ class MapStore {
|
||||
"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 = {
|
||||
route_number,
|
||||
path,
|
||||
center_latitude,
|
||||
center_longitude,
|
||||
carrier: "",
|
||||
carrier_id: 0,
|
||||
carrier,
|
||||
carrier_id,
|
||||
governor_appeal: 0,
|
||||
rotate: 0,
|
||||
route_direction: false,
|
||||
@@ -388,6 +434,12 @@ class MapStore {
|
||||
};
|
||||
|
||||
await routeStore.createRoute(routeData);
|
||||
|
||||
if (!carrier_id) {
|
||||
toast.error(
|
||||
"В выбранном городе нет доступных перевозчиков, маршрут отображается в общем списке"
|
||||
);
|
||||
}
|
||||
createdItem = routeStore.routes.data[routeStore.routes.data.length - 1];
|
||||
} else if (featureType === "sight") {
|
||||
const name = properties.name || "Достопримечательность 1";
|
||||
@@ -935,7 +987,33 @@ class MapService {
|
||||
center: transform(initialCenter, "EPSG:4326", "EPSG:3857"),
|
||||
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: [],
|
||||
});
|
||||
|
||||
@@ -1249,8 +1327,52 @@ class MapService {
|
||||
this.map.on("pointermove", this.boundHandlePointerMove as any);
|
||||
const targetEl = this.map.getTargetElement();
|
||||
if (targetEl instanceof HTMLElement) {
|
||||
// Устанавливаем курсор pointer по умолчанию для всей карты
|
||||
targetEl.style.cursor = "pointer";
|
||||
targetEl.addEventListener("contextmenu", this.boundHandleContextMenu);
|
||||
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);
|
||||
this.activateEditMode();
|
||||
@@ -1270,7 +1392,7 @@ class MapService {
|
||||
|
||||
public loadFeaturesFromApi(
|
||||
_apiStations: typeof mapStore.stations,
|
||||
apiRoutes: typeof mapStore.routes,
|
||||
_apiRoutes: typeof mapStore.routes,
|
||||
_apiSights: typeof mapStore.sights
|
||||
): void {
|
||||
if (!this.map) return;
|
||||
@@ -1282,6 +1404,7 @@ class MapService {
|
||||
// Используем фильтрованные данные из mapStore
|
||||
const filteredStations = mapStore.filteredStations;
|
||||
const filteredSights = mapStore.filteredSights;
|
||||
const filteredRoutes = mapStore.filteredRoutes;
|
||||
|
||||
filteredStations.forEach((station) => {
|
||||
if (station.longitude == null || station.latitude == null) return;
|
||||
@@ -1313,17 +1436,16 @@ class MapService {
|
||||
pointFeatures.push(feature);
|
||||
});
|
||||
|
||||
apiRoutes.forEach((route) => {
|
||||
filteredRoutes.forEach((route) => {
|
||||
if (!route.path || route.path.length === 0) return;
|
||||
const coordinates = route.path
|
||||
.filter((c) => c && c[0] != null && c[1] != null)
|
||||
.map((c: [number, number]) =>
|
||||
transform([c[1], c[0]], "EPSG:4326", projection)
|
||||
);
|
||||
|
||||
if (coordinates.length === 0) return;
|
||||
|
||||
const routeId = `route-${route.id}`;
|
||||
|
||||
const line = new LineString(coordinates);
|
||||
const lineFeature = new Feature({
|
||||
geometry: line,
|
||||
@@ -1332,8 +1454,6 @@ class MapService {
|
||||
lineFeature.setId(routeId);
|
||||
lineFeature.set("featureType", "route");
|
||||
lineFeatures.push(lineFeature);
|
||||
|
||||
// Не создаем прокси-точки для маршрутов - они должны оставаться только линиями
|
||||
});
|
||||
|
||||
this.pointSource.addFeatures(pointFeatures);
|
||||
@@ -1359,6 +1479,14 @@ class MapService {
|
||||
this.routeLayer.changed();
|
||||
}
|
||||
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 {
|
||||
@@ -1565,7 +1693,8 @@ class MapService {
|
||||
layerFilter,
|
||||
hitTolerance: 5,
|
||||
});
|
||||
this.map.getTargetElement().style.cursor = hit ? "pointer" : "";
|
||||
// Устанавливаем курсор pointer для всей карты, чтобы показать возможность перетаскивания колёсиком
|
||||
this.map.getTargetElement().style.cursor = hit ? "pointer" : "pointer";
|
||||
|
||||
const featureAtPixel: Feature<Geometry> | undefined =
|
||||
this.map.forEachFeatureAtPixel(
|
||||
@@ -2137,14 +2266,21 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
return feature;
|
||||
});
|
||||
|
||||
const lines = actualFeatures.filter(
|
||||
(f) => f.get("featureType") === "route"
|
||||
);
|
||||
const lines = mapStore.filteredRoutes.map((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];
|
||||
}, [
|
||||
mapStore.filteredStations,
|
||||
mapStore.filteredSights,
|
||||
mapStore.filteredRoutes,
|
||||
actualFeatures,
|
||||
selectedCityId,
|
||||
mapStore,
|
||||
@@ -2613,6 +2749,7 @@ export const MapPage: React.FC = observer(() => {
|
||||
mapStore.getRoutes(),
|
||||
mapStore.getStations(),
|
||||
mapStore.getSights(),
|
||||
carrierStore.getCarriers("ru"),
|
||||
]);
|
||||
mapService.loadFeaturesFromApi(
|
||||
mapStore.stations,
|
||||
|
||||
Reference in New Issue
Block a user