feat: Update admin panel

This commit is contained in:
2025-10-22 02:55:04 +03:00
parent 1b8fc3d215
commit 9e47ab667f
8 changed files with 287 additions and 84 deletions

View File

@@ -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,