feat: Add checkbox for sightbar
entity + fix build errors
This commit is contained in:
@ -6,7 +6,6 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Box,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
@ -15,8 +14,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { carrierStore, cityStore, mediaStore } from "@shared";
|
import { carrierStore, cityStore, mediaStore } from "@shared";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
import { MediaViewer } from "@widgets";
|
||||||
import { HexColorPicker } from "react-colorful";
|
|
||||||
|
|
||||||
export const CarrierCreatePage = observer(() => {
|
export const CarrierCreatePage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Paper } from "@mui/material";
|
import { Paper } from "@mui/material";
|
||||||
import { carrierStore, languageStore, mediaStore } from "@shared";
|
import { carrierStore, mediaStore } from "@shared";
|
||||||
import { MediaViewer } from "@widgets";
|
import { MediaViewer } from "@widgets";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
@ -32,7 +32,7 @@ export const CityEditPage = observer(() => {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { editCityData, editCity, getCity, setEditCityData } = cityStore;
|
const { editCityData, editCity, getCity, setEditCityData } = cityStore;
|
||||||
const { getCountries } = countryStore;
|
const { getCountries } = countryStore;
|
||||||
const { getMedia, getOneMedia, oneMedia } = mediaStore;
|
const { getMedia, getOneMedia } = mediaStore;
|
||||||
|
|
||||||
const handleEdit = async () => {
|
const handleEdit = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -40,7 +40,7 @@ export const CreateSightPage = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
await getCities();
|
await getCities("ru");
|
||||||
await getArticles(languageStore.language);
|
await getArticles(languageStore.language);
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
|
@ -13,7 +13,6 @@ import VectorLayer from "ol/layer/Vector";
|
|||||||
import VectorSource, { VectorSourceEvent } from "ol/source/Vector";
|
import VectorSource, { VectorSourceEvent } from "ol/source/Vector";
|
||||||
import { Draw, Modify, Select } from "ol/interaction";
|
import { Draw, Modify, Select } from "ol/interaction";
|
||||||
import { DrawEvent } from "ol/interaction/Draw";
|
import { DrawEvent } from "ol/interaction/Draw";
|
||||||
import { ModifyEvent } from "ol/interaction/Modify";
|
|
||||||
import { SelectEvent } from "ol/interaction/Select";
|
import { SelectEvent } from "ol/interaction/Select";
|
||||||
import {
|
import {
|
||||||
Style,
|
Style,
|
||||||
@ -34,7 +33,6 @@ import {
|
|||||||
Landmark,
|
Landmark,
|
||||||
Pencil,
|
Pencil,
|
||||||
Save,
|
Save,
|
||||||
Plus,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@ -382,7 +380,8 @@ class MapService {
|
|||||||
|
|
||||||
this.modifyInteraction = new Modify({
|
this.modifyInteraction = new Modify({
|
||||||
source: this.vectorSource,
|
source: this.vectorSource,
|
||||||
style: (feature) => {
|
// @ts-ignore
|
||||||
|
style: (feature: FeatureLike) => {
|
||||||
const originalFeature = feature.get("features")[0];
|
const originalFeature = feature.get("features")[0];
|
||||||
if (
|
if (
|
||||||
originalFeature &&
|
originalFeature &&
|
||||||
@ -475,7 +474,7 @@ class MapService {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.modifyInteraction.on("modifyend", (event: ModifyEvent) => {
|
this.modifyInteraction.on("modifyend", () => {
|
||||||
if (this.beforeModifyState) {
|
if (this.beforeModifyState) {
|
||||||
this.addStateToHistory("modify-before", this.beforeModifyState);
|
this.addStateToHistory("modify-before", this.beforeModifyState);
|
||||||
this.beforeModifyState = null;
|
this.beforeModifyState = null;
|
||||||
@ -976,7 +975,54 @@ class MapService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ИСПРАВЛЕННЫЙ МЕТОД ---
|
// --- НОВОЕ ---
|
||||||
|
// Метод для множественного удаления объектов по их ID
|
||||||
|
public deleteMultipleFeatures(featureIds: (string | number)[]): void {
|
||||||
|
if (!featureIds || featureIds.length === 0) return;
|
||||||
|
|
||||||
|
// Вывод в консоль по требованию
|
||||||
|
console.log("Запрос на множественное удаление. ID объектов:", featureIds);
|
||||||
|
|
||||||
|
const currentState = this.getCurrentStateAsGeoJSON();
|
||||||
|
if (currentState) {
|
||||||
|
this.addStateToHistory("multiple delete", currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedFeaturesCollection = this.selectInteraction?.getFeatures();
|
||||||
|
let deletedCount = 0;
|
||||||
|
|
||||||
|
featureIds.forEach((id) => {
|
||||||
|
const feature = this.vectorSource.getFeatureById(id);
|
||||||
|
if (feature) {
|
||||||
|
// Удаление из "бэкенда"/стора для каждого объекта
|
||||||
|
const recourse = String(id).split("-")[0];
|
||||||
|
const numericId = String(id).split("-")[1];
|
||||||
|
if (recourse && numericId) {
|
||||||
|
mapStore.deleteRecourse(recourse, Number(numericId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если удаляемый объект выбран для редактирования, убираем его из выделения
|
||||||
|
if (selectedFeaturesCollection?.getArray().includes(feature)) {
|
||||||
|
selectedFeaturesCollection.remove(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем объект с карты
|
||||||
|
this.vectorSource.removeFeature(feature);
|
||||||
|
deletedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
// Если основное выделение стало пустым, оповещаем React
|
||||||
|
if (selectedFeaturesCollection?.getLength() === 0) {
|
||||||
|
this.onFeatureSelect(null);
|
||||||
|
}
|
||||||
|
toast.success(`Удалено ${deletedCount} объект(ов).`);
|
||||||
|
} else {
|
||||||
|
toast.warn("Не найдено объектов для удаления.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getAllFeaturesAsGeoJSON(): string | null {
|
public getAllFeaturesAsGeoJSON(): string | null {
|
||||||
if (!this.vectorSource || !this.map) return null;
|
if (!this.vectorSource || !this.map) return null;
|
||||||
const feats = this.vectorSource.getFeatures();
|
const feats = this.vectorSource.getFeatures();
|
||||||
@ -984,11 +1030,9 @@ class MapService {
|
|||||||
|
|
||||||
const geoJSONFmt = new GeoJSON();
|
const geoJSONFmt = new GeoJSON();
|
||||||
|
|
||||||
// Просто передаем опции трансформации в метод writeFeatures.
|
|
||||||
// Он сам всё сделает правильно, не трогая оригинальные объекты.
|
|
||||||
return geoJSONFmt.writeFeatures(feats, {
|
return geoJSONFmt.writeFeatures(feats, {
|
||||||
dataProjection: "EPSG:4326", // В какую проекцию конвертировать (стандарт для GeoJSON)
|
dataProjection: "EPSG:4326",
|
||||||
featureProjection: this.map.getView().getProjection(), // В какой проекции находятся объекты на карте
|
featureProjection: this.map.getView().getProjection(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,6 +1155,11 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
const [activeSection, setActiveSection] = useState<string | null>("layers");
|
const [activeSection, setActiveSection] = useState<string | null>("layers");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
// --- НОВОЕ ---
|
||||||
|
// Состояние для хранения ID объектов, выбранных для удаления
|
||||||
|
const [selectedForDeletion, setSelectedForDeletion] = useState<
|
||||||
|
Set<string | number>
|
||||||
|
>(new Set());
|
||||||
|
|
||||||
const toggleSection = (id: string) =>
|
const toggleSection = (id: string) =>
|
||||||
setActiveSection(activeSection === id ? null : id);
|
setActiveSection(activeSection === id ? null : id);
|
||||||
@ -1135,6 +1184,40 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
[mapService]
|
[mapService]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- НОВОЕ ---
|
||||||
|
// Обработчик изменения состояния чекбокса
|
||||||
|
const handleCheckboxChange = useCallback(
|
||||||
|
(id: string | number | undefined) => {
|
||||||
|
if (id === undefined) return;
|
||||||
|
setSelectedForDeletion((prev) => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
if (newSet.has(id)) {
|
||||||
|
newSet.delete(id);
|
||||||
|
} else {
|
||||||
|
newSet.add(id);
|
||||||
|
}
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- НОВОЕ ---
|
||||||
|
// Обработчик для запуска множественного удаления
|
||||||
|
const handleBulkDelete = useCallback(() => {
|
||||||
|
if (!mapService || selectedForDeletion.size === 0) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
window.confirm(
|
||||||
|
`Вы уверены, что хотите удалить ${selectedForDeletion.size} объект(ов)? Это действие нельзя отменить.`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const idsToDelete = Array.from(selectedForDeletion);
|
||||||
|
mapService.deleteMultipleFeatures(idsToDelete);
|
||||||
|
setSelectedForDeletion(new Set()); // Очищаем выбор после удаления
|
||||||
|
}
|
||||||
|
}, [mapService, selectedForDeletion]);
|
||||||
|
|
||||||
const handleEditFeature = useCallback(
|
const handleEditFeature = useCallback(
|
||||||
(featureType: string | undefined, fullId: string | number | undefined) => {
|
(featureType: string | undefined, fullId: string | number | undefined) => {
|
||||||
if (!featureType || !fullId) return;
|
if (!featureType || !fullId) return;
|
||||||
@ -1191,18 +1274,35 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
const sId = s.getId();
|
const sId = s.getId();
|
||||||
const sName = (s.get("name") as string) || "Без названия";
|
const sName = (s.get("name") as string) || "Без названия";
|
||||||
const isSelected = selectedFeature?.getId() === sId;
|
const isSelected = selectedFeature?.getId() === sId;
|
||||||
|
// --- ИЗМЕНЕНИЕ ---
|
||||||
|
const isCheckedForDeletion =
|
||||||
|
sId !== undefined && selectedForDeletion.has(sId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={String(sId)}
|
key={String(sId)}
|
||||||
className={`flex items-start justify-between p-2 rounded-md cursor-pointer group transition-colors duration-150 ${
|
className={`flex items-start p-2 rounded-md group transition-colors duration-150 ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-orange-100 border border-orange-300 hover:bg-orange-200"
|
? "bg-orange-100 border border-orange-300"
|
||||||
: "hover:bg-blue-50"
|
: "hover:bg-blue-50"
|
||||||
}`}
|
}`}
|
||||||
|
>
|
||||||
|
{/* --- НОВОЕ: Чекбокс для множественного выбора --- */}
|
||||||
|
<div className="flex-shrink-0 pr-2 pt-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
|
||||||
|
checked={!!isCheckedForDeletion}
|
||||||
|
onChange={() => handleCheckboxChange(sId)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label={`Выбрать ${sName} для удаления`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0 cursor-pointer"
|
||||||
onClick={() => handleFeatureClick(sId)}
|
onClick={() => handleFeatureClick(sId)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0">
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<MapPin
|
<MapPin
|
||||||
size={16}
|
size={16}
|
||||||
@ -1266,6 +1366,8 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
const lId = l.getId();
|
const lId = l.getId();
|
||||||
const lName = (l.get("name") as string) || "Без названия";
|
const lName = (l.get("name") as string) || "Без названия";
|
||||||
const isSelected = selectedFeature?.getId() === lId;
|
const isSelected = selectedFeature?.getId() === lId;
|
||||||
|
const isCheckedForDeletion =
|
||||||
|
lId !== undefined && selectedForDeletion.has(lId);
|
||||||
const lGeom = l.getGeometry();
|
const lGeom = l.getGeometry();
|
||||||
let lineLengthText: string | null = null;
|
let lineLengthText: string | null = null;
|
||||||
if (lGeom instanceof LineString) {
|
if (lGeom instanceof LineString) {
|
||||||
@ -1276,14 +1378,26 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={String(lId)}
|
key={String(lId)}
|
||||||
className={`flex items-start justify-between p-2 rounded-md cursor-pointer group transition-colors duration-150 ${
|
className={`flex items-start p-2 rounded-md group transition-colors duration-150 ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-orange-100 border border-orange-300 hover:bg-orange-200"
|
? "bg-orange-100 border border-orange-300"
|
||||||
: "hover:bg-blue-50"
|
: "hover:bg-blue-50"
|
||||||
}`}
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 pr-2 pt-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
|
||||||
|
checked={!!isCheckedForDeletion}
|
||||||
|
onChange={() => handleCheckboxChange(lId)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label={`Выбрать ${lName} для удаления`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0 cursor-pointer"
|
||||||
onClick={() => handleFeatureClick(lId)}
|
onClick={() => handleFeatureClick(lId)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0">
|
|
||||||
<div className="flex items-center mb-0.5">
|
<div className="flex items-center mb-0.5">
|
||||||
<ArrowRightLeft
|
<ArrowRightLeft
|
||||||
size={16}
|
size={16}
|
||||||
@ -1352,17 +1466,31 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
const sId = s.getId();
|
const sId = s.getId();
|
||||||
const sName = (s.get("name") as string) || "Без названия";
|
const sName = (s.get("name") as string) || "Без названия";
|
||||||
const isSelected = selectedFeature?.getId() === sId;
|
const isSelected = selectedFeature?.getId() === sId;
|
||||||
|
const isCheckedForDeletion =
|
||||||
|
sId !== undefined && selectedForDeletion.has(sId);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={String(sId)}
|
key={String(sId)}
|
||||||
className={`flex items-center justify-between p-2 rounded-md cursor-pointer group transition-colors duration-150 ${
|
className={`flex items-start p-2 rounded-md group transition-colors duration-150 ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-orange-100 border border-orange-300 hover:bg-orange-200"
|
? "bg-orange-100 border border-orange-300"
|
||||||
: "hover:bg-blue-50"
|
: "hover:bg-blue-50"
|
||||||
}`}
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 pr-2 pt-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
|
||||||
|
checked={!!isCheckedForDeletion}
|
||||||
|
onChange={() => handleCheckboxChange(sId)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label={`Выбрать ${sName} для удаления`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex items-center text-gray-800 text-sm flex-grow mr-2 min-w-0 cursor-pointer"
|
||||||
onClick={() => handleFeatureClick(sId)}
|
onClick={() => handleFeatureClick(sId)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center text-gray-800 text-sm flex-grow mr-2 min-w-0">
|
|
||||||
<Landmark
|
<Landmark
|
||||||
size={16}
|
size={16}
|
||||||
className={`mr-1.5 flex-shrink-0 ${
|
className={`mr-1.5 flex-shrink-0 ${
|
||||||
@ -1427,12 +1555,13 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// --- ИЗМЕНЕНИЕ: Реструктуризация для футера с кнопками ---
|
||||||
<div className="w-72 relative md:w-80 bg-gray-50 shadow-lg flex flex-col border-l border-gray-200 h-[90vh]">
|
<div className="w-72 relative md:w-80 bg-gray-50 shadow-lg flex flex-col border-l border-gray-200 h-[90vh]">
|
||||||
<div className="p-4 bg-gray-700 text-white">
|
<div className="p-4 bg-gray-700 text-white">
|
||||||
<h2 className="text-lg font-semibold">Панель управления</h2>
|
<h2 className="text-lg font-semibold">Панель управления</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col min-h-0">
|
||||||
<div className="flex-1 overflow-y-auto max-h-[70%]">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{sections.map((s) => (
|
{sections.map((s) => (
|
||||||
<div
|
<div
|
||||||
key={s.id}
|
key={s.id}
|
||||||
@ -1480,9 +1609,20 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* --- НОВОЕ: Футер сайдбара с кнопками действий --- */}
|
||||||
|
<div className="p-3 border-t border-gray-200 bg-gray-50/95 space-y-2">
|
||||||
|
{selectedForDeletion.size > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={handleBulkDelete}
|
||||||
|
className="w-full flex items-center justify-center px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} className="mr-2" />
|
||||||
|
Удалить выбранное ({selectedForDeletion.size})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="m-3 w-[90%] h-[40px] flex items-center justify-center px-4 py-2 bg-blue-500 disabled:bg-blue-300 text-white rounded-md hover:bg-blue-600 transition-colors"
|
className="w-full h-[40px] flex items-center justify-center px-4 py-2 bg-blue-500 disabled:bg-blue-300 text-white rounded-md hover:bg-blue-600 transition-colors"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<Save size={16} className="mr-2" />
|
<Save size={16} className="mr-2" />
|
||||||
@ -1493,6 +1633,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1502,9 +1643,8 @@ export const MapPage: React.FC = () => {
|
|||||||
const tooltipRef = useRef<HTMLDivElement | null>(null);
|
const tooltipRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [mapServiceInstance, setMapServiceInstance] =
|
const [mapServiceInstance, setMapServiceInstance] =
|
||||||
useState<MapService | null>(null);
|
useState<MapService | null>(null);
|
||||||
// --- ИЗМЕНЕНИЕ: Разделение состояния загрузки ---
|
const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
|
||||||
const [isMapLoading, setIsMapLoading] = useState<boolean>(true); // Для рендеринга карты
|
const [isDataLoading, setIsDataLoading] = useState<boolean>(true);
|
||||||
const [isDataLoading, setIsDataLoading] = useState<boolean>(true); // Для загрузки данных с API
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [currentMapMode, setCurrentMapMode] = useState<string>("edit");
|
const [currentMapMode, setCurrentMapMode] = useState<string>("edit");
|
||||||
const [mapFeatures, setMapFeatures] = useState<Feature<Geometry>[]>([]);
|
const [mapFeatures, setMapFeatures] = useState<Feature<Geometry>[]>([]);
|
||||||
@ -1525,12 +1665,10 @@ export const MapPage: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let service: MapService | null = null;
|
let service: MapService | null = null;
|
||||||
if (mapRef.current && tooltipRef.current && !mapServiceInstance) {
|
if (mapRef.current && tooltipRef.current && !mapServiceInstance) {
|
||||||
// Изначально оба процесса загрузки активны
|
|
||||||
setIsMapLoading(true);
|
setIsMapLoading(true);
|
||||||
setIsDataLoading(true);
|
setIsDataLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// --- ИЗМЕНЕНИЕ: Логика загрузки данных вынесена и управляет своим состоянием ---
|
|
||||||
const loadInitialData = async (mapService: MapService) => {
|
const loadInitialData = async (mapService: MapService) => {
|
||||||
console.log("Starting data load...");
|
console.log("Starting data load...");
|
||||||
try {
|
try {
|
||||||
@ -1548,7 +1686,6 @@ export const MapPage: React.FC = () => {
|
|||||||
console.error("Failed to load initial map data:", e);
|
console.error("Failed to load initial map data:", e);
|
||||||
setError("Не удалось загрузить данные для карты.");
|
setError("Не удалось загрузить данные для карты.");
|
||||||
} finally {
|
} finally {
|
||||||
// Завершаем состояние загрузки данных независимо от результата
|
|
||||||
setIsDataLoading(false);
|
setIsDataLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1556,7 +1693,7 @@ export const MapPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
service = new MapService(
|
service = new MapService(
|
||||||
{ ...mapConfig, target: mapRef.current },
|
{ ...mapConfig, target: mapRef.current },
|
||||||
setIsMapLoading, // MapService теперь управляет только состоянием загрузки карты
|
setIsMapLoading,
|
||||||
setError,
|
setError,
|
||||||
setCurrentMapMode,
|
setCurrentMapMode,
|
||||||
handleFeaturesChange,
|
handleFeaturesChange,
|
||||||
@ -1565,7 +1702,6 @@ export const MapPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
setMapServiceInstance(service);
|
setMapServiceInstance(service);
|
||||||
|
|
||||||
// Запускаем загрузку данных
|
|
||||||
loadInitialData(service);
|
loadInitialData(service);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("MapPage useEffect error:", e);
|
console.error("MapPage useEffect error:", e);
|
||||||
@ -1574,7 +1710,6 @@ export const MapPage: React.FC = () => {
|
|||||||
e.message || "Неизвестная ошибка"
|
e.message || "Неизвестная ошибка"
|
||||||
}. Пожалуйста, проверьте консоль.`
|
}. Пожалуйста, проверьте консоль.`
|
||||||
);
|
);
|
||||||
// В случае критической ошибки инициализации, завершаем все загрузки
|
|
||||||
setIsMapLoading(false);
|
setIsMapLoading(false);
|
||||||
setIsDataLoading(false);
|
setIsDataLoading(false);
|
||||||
}
|
}
|
||||||
@ -1607,7 +1742,6 @@ export const MapPage: React.FC = () => {
|
|||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
{/* --- ИЗМЕНЕНИЕ: Обновленный лоадер --- */}
|
|
||||||
{showLoader && (
|
{showLoader && (
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-500 bg-opacity-50 z-[1001]">
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-500 bg-opacity-50 z-[1001]">
|
||||||
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mb-3"></div>
|
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mb-3"></div>
|
||||||
@ -1629,7 +1763,6 @@ export const MapPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* --- ИЗМЕНЕНИЕ: Условие для отображения контента --- */}
|
|
||||||
{showContent && (
|
{showContent && (
|
||||||
<MapControls
|
<MapControls
|
||||||
mapService={mapServiceInstance}
|
mapService={mapServiceInstance}
|
||||||
|
@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Eye, Trash2 } from "lucide-react";
|
import { Eye, Trash2 } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
import { CreateButton, DeleteModal } from "@widgets";
|
||||||
|
|
||||||
export const MediaListPage = observer(() => {
|
export const MediaListPage = observer(() => {
|
||||||
const { media, getMedia, deleteMedia } = mediaStore;
|
const { media, getMedia, deleteMedia } = mediaStore;
|
||||||
|
@ -80,9 +80,6 @@ export const RouteCreatePage = observer(() => {
|
|||||||
path,
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Отправка на сервер (пример, если есть routeStore.createRoute)
|
|
||||||
let createdRoute: Route | null = null;
|
|
||||||
|
|
||||||
await routeStore.createRoute(newRoute);
|
await routeStore.createRoute(newRoute);
|
||||||
toast.success("Маршрут успешно создан");
|
toast.success("Маршрут успешно создан");
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
|
@ -2,9 +2,9 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
|||||||
import { languageStore, routeStore } from "@shared";
|
import { languageStore, routeStore } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Eye, Map, Pencil, Trash2 } from "lucide-react";
|
import { Map, Pencil, Trash2 } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
import { CreateButton, DeleteModal } from "@widgets";
|
||||||
|
|
||||||
export const RouteListPage = observer(() => {
|
export const RouteListPage = observer(() => {
|
||||||
const { routes, getRoutes, deleteRoute } = routeStore;
|
const { routes, getRoutes, deleteRoute } = routeStore;
|
||||||
|
@ -171,20 +171,14 @@ export const MapDataProvider = observer(
|
|||||||
|
|
||||||
async function saveStationChanges() {
|
async function saveStationChanges() {
|
||||||
for (const station of stationChanges) {
|
for (const station of stationChanges) {
|
||||||
const response = await authInstance.patch(
|
await authInstance.patch(`/route/${routeId}/station`, station);
|
||||||
`/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 authInstance.patch(
|
await authInstance.patch(`/route/${routeId}/sight`, sight);
|
||||||
`/route/${routeId}/sight`,
|
|
||||||
sight
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTransform } from "./TransformContext";
|
import { useTransform } from "./TransformContext";
|
||||||
import { SightData } from "./types";
|
import { SightData } from "./types";
|
||||||
import { Assets, FederatedMouseEvent, Graphics, Texture } from "pixi.js";
|
import { Assets, FederatedMouseEvent, Graphics, Texture } from "pixi.js";
|
||||||
import { COLORS } from "../../contexts/color-mode/theme";
|
|
||||||
import { SIGHT_SIZE, UP_SCALE } from "./Constants";
|
import { SIGHT_SIZE, UP_SCALE } from "./Constants";
|
||||||
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
||||||
import { useMapData } from "./MapDataContext";
|
import { useMapData } from "./MapDataContext";
|
||||||
@ -12,13 +12,13 @@ interface SightProps {
|
|||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sight({
|
export function Sight({ sight, id }: Readonly<SightProps>) {
|
||||||
sight, id
|
|
||||||
}: Readonly<SightProps>) {
|
|
||||||
const { rotation, scale } = useTransform();
|
const { rotation, scale } = useTransform();
|
||||||
const { setSightCoordinates } = useMapData();
|
const { setSightCoordinates } = useMapData();
|
||||||
|
|
||||||
const [position, setPosition] = useState(coordinatesToLocal(sight.latitude, sight.longitude));
|
const [position, setPosition] = useState(
|
||||||
|
coordinatesToLocal(sight.latitude, sight.longitude)
|
||||||
|
);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
|
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
|
||||||
const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
|
const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
|
||||||
@ -27,11 +27,11 @@ export function Sight({
|
|||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
setStartPosition({
|
setStartPosition({
|
||||||
x: position.x,
|
x: position.x,
|
||||||
y: position.y
|
y: position.y,
|
||||||
});
|
});
|
||||||
setStartMousePosition({
|
setStartMousePosition({
|
||||||
x: e.globalX,
|
x: e.globalX,
|
||||||
y: e.globalY
|
y: e.globalY,
|
||||||
});
|
});
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -44,7 +44,7 @@ export function Sight({
|
|||||||
const sin = Math.sin(rotation);
|
const sin = Math.sin(rotation);
|
||||||
const newPosition = {
|
const newPosition = {
|
||||||
x: startPosition.x + dx * cos + dy * sin,
|
x: startPosition.x + dx * cos + dy * sin,
|
||||||
y: startPosition.y - dx * sin + dy * cos
|
y: startPosition.y - dx * sin + dy * cos,
|
||||||
};
|
};
|
||||||
setPosition(newPosition);
|
setPosition(newPosition);
|
||||||
const coordinates = localToCoordinates(newPosition.x, newPosition.y);
|
const coordinates = localToCoordinates(newPosition.x, newPosition.y);
|
||||||
@ -60,10 +60,8 @@ export function Sight({
|
|||||||
const [texture, setTexture] = useState(Texture.EMPTY);
|
const [texture, setTexture] = useState(Texture.EMPTY);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (texture === Texture.EMPTY) {
|
if (texture === Texture.EMPTY) {
|
||||||
Assets
|
Assets.load("/SightIcon.png").then((result) => {
|
||||||
.load('/SightIcon.png')
|
setTexture(result);
|
||||||
.then((result) => {
|
|
||||||
setTexture(result)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [texture]);
|
}, [texture]);
|
||||||
@ -71,46 +69,36 @@ export function Sight({
|
|||||||
function draw(g: Graphics) {
|
function draw(g: Graphics) {
|
||||||
g.clear();
|
g.clear();
|
||||||
g.circle(0, 0, 20);
|
g.circle(0, 0, 20);
|
||||||
g.fill({color: COLORS.primary}); // Fill circle with primary color
|
g.fill({ color: "#000" }); // Fill circle with primary color
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!sight) {
|
if (!sight) {
|
||||||
console.error("sight is null");
|
console.error("sight is null");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coordinates = coordinatesToLocal(sight.latitude, sight.longitude);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pixiContainer rotation={-rotation}
|
<pixiContainer
|
||||||
eventMode='static'
|
rotation={-rotation}
|
||||||
|
eventMode="static"
|
||||||
interactive
|
interactive
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
onGlobalPointerMove={handlePointerMove}
|
onGlobalPointerMove={handlePointerMove}
|
||||||
onPointerUp={handlePointerUp}
|
onPointerUp={handlePointerUp}
|
||||||
onPointerUpOutside={handlePointerUp}
|
onPointerUpOutside={handlePointerUp}
|
||||||
x={position.x * UP_SCALE - SIGHT_SIZE/2} // Offset by half width to center
|
x={position.x * UP_SCALE - SIGHT_SIZE / 2} // Offset by half width to center
|
||||||
y={position.y * UP_SCALE - SIGHT_SIZE/2} // Offset by half height to center
|
y={position.y * UP_SCALE - SIGHT_SIZE / 2} // Offset by half height to center
|
||||||
>
|
>
|
||||||
<pixiSprite
|
<pixiSprite texture={texture} width={SIGHT_SIZE} height={SIGHT_SIZE} />
|
||||||
texture={texture}
|
<pixiGraphics draw={draw} x={SIGHT_SIZE} y={0} />
|
||||||
width={SIGHT_SIZE}
|
|
||||||
height={SIGHT_SIZE}
|
|
||||||
/>
|
|
||||||
<pixiGraphics
|
|
||||||
draw={draw}
|
|
||||||
x={SIGHT_SIZE}
|
|
||||||
y={0}
|
|
||||||
/>
|
|
||||||
<pixiText
|
<pixiText
|
||||||
text={`${id+1}`}
|
text={`${id + 1}`}
|
||||||
x={SIGHT_SIZE+1}
|
x={SIGHT_SIZE + 1}
|
||||||
y={0}
|
y={0}
|
||||||
anchor={0.5}
|
anchor={0.5}
|
||||||
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
fill: "#ffffff",
|
fill: "#ffffff",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -7,12 +7,11 @@ import {
|
|||||||
UP_SCALE,
|
UP_SCALE,
|
||||||
} from "./Constants";
|
} from "./Constants";
|
||||||
import { useTransform } from "./TransformContext";
|
import { useTransform } from "./TransformContext";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { StationData } from "./types";
|
import { StationData } from "./types";
|
||||||
import { useMapData } from "./MapDataContext";
|
import { useMapData } from "./MapDataContext";
|
||||||
import { coordinatesToLocal } from "./utils";
|
import { coordinatesToLocal } from "./utils";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { languageStore } from "@shared";
|
|
||||||
|
|
||||||
interface StationProps {
|
interface StationProps {
|
||||||
station: StationData;
|
station: StationData;
|
||||||
@ -47,7 +46,6 @@ export const Station = observer(
|
|||||||
|
|
||||||
export const StationLabel = observer(
|
export const StationLabel = observer(
|
||||||
({ station, ruLabel }: Readonly<StationProps>) => {
|
({ station, ruLabel }: Readonly<StationProps>) => {
|
||||||
const { language } = languageStore;
|
|
||||||
const { rotation, scale } = useTransform();
|
const { rotation, scale } = useTransform();
|
||||||
const { setStationOffset } = useMapData();
|
const { setStationOffset } = useMapData();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useRef, useEffect, useState } from "react";
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Application, ApplicationRef, extend } from "@pixi/react";
|
import { Application, extend } from "@pixi/react";
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Graphics,
|
Graphics,
|
||||||
@ -13,7 +13,7 @@ import { Stack } from "@mui/material";
|
|||||||
import { MapDataProvider, useMapData } from "./MapDataContext";
|
import { MapDataProvider, useMapData } from "./MapDataContext";
|
||||||
import { TransformProvider, useTransform } from "./TransformContext";
|
import { TransformProvider, useTransform } from "./TransformContext";
|
||||||
import { InfiniteCanvas } from "./InfiniteCanvas";
|
import { InfiniteCanvas } from "./InfiniteCanvas";
|
||||||
import { Sight } from "./Sight";
|
|
||||||
import { UP_SCALE } from "./Constants";
|
import { UP_SCALE } from "./Constants";
|
||||||
import { Station } from "./Station";
|
import { Station } from "./Station";
|
||||||
import { TravelPath } from "./TravelPath";
|
import { TravelPath } from "./TravelPath";
|
||||||
|
@ -2,7 +2,7 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
|||||||
import { languageStore, snapshotStore } from "@shared";
|
import { languageStore, snapshotStore } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DatabaseBackup, Eye, Trash2 } from "lucide-react";
|
import { DatabaseBackup, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
import { CreateButton, DeleteModal, SnapshotRestore } from "@widgets";
|
import { CreateButton, DeleteModal, SnapshotRestore } from "@widgets";
|
||||||
|
|
||||||
|
@ -6,17 +6,15 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Box,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save, ImagePlus } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { stationsStore, languageStore, cityStore } from "@shared";
|
import { stationsStore, languageStore, cityStore } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
import { LanguageSwitcher } from "@widgets";
|
||||||
import { SelectMediaDialog } from "@shared";
|
|
||||||
|
|
||||||
export const StationEditPage = observer(() => {
|
export const StationEditPage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -17,7 +17,7 @@ export const UserEditPage = observer(() => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { editUserData, editUser, getUser, setEditUserData, user } = userStore;
|
const { editUserData, editUser, getUser, setEditUserData } = userStore;
|
||||||
|
|
||||||
const handleEdit = async () => {
|
const handleEdit = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -31,7 +31,7 @@ export const VehicleCreatePage = observer(() => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await vehicleStore.createVehicle(
|
await vehicleStore.createVehicle(
|
||||||
Number(tailNumber),
|
Number(tailNumber),
|
||||||
type,
|
Number(type),
|
||||||
carrierStore.carriers.data.find((c) => c.id === carrierId)?.full_name!,
|
carrierStore.carriers.data.find((c) => c.id === carrierId)?.full_name!,
|
||||||
carrierId!
|
carrierId!
|
||||||
);
|
);
|
||||||
|
@ -108,7 +108,7 @@ export const VehicleEditPage = observer(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{carrierStore.carriers.map((carrier) => (
|
{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>
|
||||||
|
@ -8,11 +8,8 @@ import {
|
|||||||
Earth,
|
Earth,
|
||||||
Landmark,
|
Landmark,
|
||||||
BusFront,
|
BusFront,
|
||||||
Bus,
|
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Car,
|
Car,
|
||||||
Train,
|
|
||||||
Ship,
|
|
||||||
Table,
|
Table,
|
||||||
Split,
|
Split,
|
||||||
Newspaper,
|
Newspaper,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { authInstance, languageStore } from "@shared";
|
import { authInstance } from "@shared";
|
||||||
import { makeAutoObservable, runInAction } from "mobx";
|
import { makeAutoObservable, runInAction } from "mobx";
|
||||||
|
|
||||||
export type Carrier = {
|
export type Carrier = {
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import { authInstance, languageInstance } from "@shared";
|
|
||||||
import { makeAutoObservable, runInAction } from "mobx";
|
|
||||||
|
|
||||||
type City = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
country_code: string;
|
|
||||||
country: string;
|
|
||||||
arms?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CityStore {
|
|
||||||
cities: City[] = [];
|
|
||||||
ruCities: City[] = [];
|
|
||||||
city: City | null = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
makeAutoObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCities = async () => {
|
|
||||||
const response = await authInstance.get("/city");
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.cities = response.data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getRuCities = async () => {
|
|
||||||
const response = await languageInstance("ru").get("/city");
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.ruCities = response.data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteCity = async (id: number) => {
|
|
||||||
await authInstance.delete(`/city/${id}`);
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.cities = this.cities.filter((city) => city.id !== id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getCity = async (id: string) => {
|
|
||||||
const response = await authInstance.get(`/city/${id}`);
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.city = response.data;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
createCity = async (
|
|
||||||
name: string,
|
|
||||||
country: string,
|
|
||||||
countryCode: string,
|
|
||||||
mediaId: string
|
|
||||||
) => {
|
|
||||||
const response = await authInstance.post("/city", {
|
|
||||||
name: name,
|
|
||||||
country: country,
|
|
||||||
country_code: countryCode,
|
|
||||||
arms: mediaId,
|
|
||||||
});
|
|
||||||
runInAction(() => {
|
|
||||||
this.cities.push(response.data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// export const cityStore = new CityStore();
|
|
@ -256,14 +256,17 @@ class StationsStore {
|
|||||||
// Update the cached preview data and station lists after successful patch
|
// Update the cached preview data and station lists after successful patch
|
||||||
if (this.stationPreview[id]) {
|
if (this.stationPreview[id]) {
|
||||||
this.stationPreview[id][language] = {
|
this.stationPreview[id][language] = {
|
||||||
...this.stationPreview[id][language], // Preserve common fields that might not be in the language-specific patch response
|
loaded: true,
|
||||||
|
data: {
|
||||||
|
...this.stationPreview[id][language].data,
|
||||||
id: response.data.id,
|
id: response.data.id,
|
||||||
name: response.data.name,
|
name: response.data.name,
|
||||||
system_name: response.data.system_name,
|
system_name: response.data.system_name,
|
||||||
description: response.data.description,
|
description: response.data.description,
|
||||||
address: response.data.address,
|
address: response.data.address,
|
||||||
...commonDataPayload,
|
...commonDataPayload,
|
||||||
} as Station; // Cast to Station to satisfy type
|
} as Station,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (this.stationLists[language].data) {
|
if (this.stationLists[language].data) {
|
||||||
this.stationLists[language].data = this.stationLists[
|
this.stationLists[language].data = this.stationLists[
|
||||||
@ -327,8 +330,8 @@ class StationsStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.stationPreview[id][language] = {
|
this.stationPreview[id][language] = {
|
||||||
data: response.data,
|
|
||||||
loaded: true,
|
loaded: true,
|
||||||
|
data: response.data as Station,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { authInstance, languageStore, languageInstance } from "@shared";
|
import { languageInstance } from "@shared";
|
||||||
import { makeAutoObservable, runInAction } from "mobx";
|
import { makeAutoObservable, runInAction } from "mobx";
|
||||||
|
|
||||||
export type Vehicle = {
|
export type Vehicle = {
|
||||||
|
@ -121,7 +121,7 @@ export const DevicesTable = observer(() => {
|
|||||||
// Transform the raw devices data into rows suitable for the table
|
// Transform the raw devices data into rows suitable for the table
|
||||||
// This will also filter out devices without a UUID, as those cannot be acted upon.
|
// This will also filter out devices without a UUID, as those cannot be acted upon.
|
||||||
const currentTableRows = transformDevicesToRows(
|
const currentTableRows = transformDevicesToRows(
|
||||||
vehicles as Vehicle[]
|
vehicles.data as Vehicle[]
|
||||||
// devices as ConnectedDevice[]
|
// devices as ConnectedDevice[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export const SightsTable = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
await getSights();
|
await getSights();
|
||||||
await getCities();
|
await getCities(language);
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [language, getSights, getCities]);
|
}, [language, getSights, getCities]);
|
||||||
@ -67,7 +67,7 @@ export const SightsTable = observer(() => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{rows(sights, cities)?.map((row) => (
|
{rows(sights, cities[language])?.map((row) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={row?.id}
|
key={row?.id}
|
||||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user