Compare commits
2 Commits
db64beb3ee
...
a357994025
| Author | SHA1 | Date | |
|---|---|---|---|
| a357994025 | |||
| 7382a85082 |
@@ -1,62 +0,0 @@
|
||||
# Селектор городов
|
||||
|
||||
## Описание функциональности
|
||||
|
||||
Добавлена функциональность выбора города в админ-панели "Белые ночи":
|
||||
|
||||
### Основные возможности:
|
||||
|
||||
1. **Селектор городов в шапке приложения**
|
||||
|
||||
- Расположен рядом с именем пользователя в верхней части приложения
|
||||
- Показывает список всех доступных городов
|
||||
- Имеет иконку MapPin для лучшего UX
|
||||
|
||||
2. **Сохранение в localStorage**
|
||||
|
||||
- Выбранный город автоматически сохраняется в localStorage
|
||||
- При перезагрузке страницы выбранный город восстанавливается
|
||||
|
||||
3. **Автоматическое использование в формах**
|
||||
- При создании новой станции выбранный город автоматически подставляется
|
||||
- При создании нового перевозчика выбранный город автоматически подставляется
|
||||
- Пользователь может изменить город в форме при необходимости
|
||||
|
||||
### Технические детали:
|
||||
|
||||
#### Новые компоненты и сторы:
|
||||
|
||||
- `SelectedCityStore` - стор для управления выбранным городом
|
||||
- `CitySelector` - компонент селектора городов
|
||||
- `useSelectedCity` - хук для удобного доступа к выбранному городу
|
||||
|
||||
#### Интеграция:
|
||||
|
||||
- Селектор добавлен в `Layout` компонент
|
||||
- Интегрирован в `StationCreatePage` и `CarrierCreatePage`
|
||||
- Использует существующий `CityStore` для получения списка городов
|
||||
|
||||
#### Файлы, которые были изменены:
|
||||
|
||||
- `src/widgets/Layout/index.tsx` - добавлен CitySelector
|
||||
- `src/pages/Station/StationCreatePage/index.tsx` - автоматическая подстановка города
|
||||
- `src/pages/Carrier/CarrierCreatePage/index.tsx` - автоматическая подстановка города
|
||||
- `src/shared/store/index.ts` - добавлен экспорт SelectedCityStore
|
||||
- `src/widgets/index.ts` - добавлен экспорт CitySelector
|
||||
- `src/shared/index.tsx` - добавлен экспорт hooks
|
||||
|
||||
#### Новые файлы:
|
||||
|
||||
- `src/shared/store/SelectedCityStore/index.ts`
|
||||
- `src/widgets/CitySelector/index.tsx`
|
||||
- `src/shared/hooks/useSelectedCity.ts`
|
||||
- `src/shared/hooks/index.ts`
|
||||
|
||||
### Использование:
|
||||
|
||||
1. Пользователь выбирает город в селекторе в шапке приложения
|
||||
2. Выбранный город сохраняется в localStorage
|
||||
3. При создании новой станции или перевозчика выбранный город автоматически подставляется в форму
|
||||
4. Пользователь может изменить город в форме если нужно
|
||||
|
||||
Функциональность полностью интегрирована и готова к использованию.
|
||||
@@ -52,7 +52,7 @@ import { FeatureLike } from "ol/Feature";
|
||||
import { createEmpty, extend, getCenter } from "ol/extent";
|
||||
|
||||
// --- CUSTOM SCROLLBAR STYLES ---
|
||||
const scrollbarHideStyles = `
|
||||
const scrollbarStyles = `
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -60,11 +60,34 @@ const scrollbarHideStyles = `
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-visible {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
`;
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
const styleElement = document.createElement("style");
|
||||
styleElement.textContent = scrollbarHideStyles;
|
||||
styleElement.textContent = scrollbarStyles;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
@@ -73,7 +96,13 @@ if (typeof document !== "undefined") {
|
||||
import { languageInstance } from "@shared";
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
import { stationsStore, routeStore, sightsStore, menuStore } from "@shared";
|
||||
import {
|
||||
stationsStore,
|
||||
routeStore,
|
||||
sightsStore,
|
||||
menuStore,
|
||||
selectedCityStore,
|
||||
} from "@shared";
|
||||
|
||||
// Функция для сброса кешей карты
|
||||
export const clearMapCaches = () => {
|
||||
@@ -101,6 +130,7 @@ interface ApiStation {
|
||||
name: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
city_id: number;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
@@ -110,12 +140,12 @@ interface ApiSight {
|
||||
description: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
city_id: number;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export type SortType = "name_asc" | "name_desc" | "date_asc" | "date_desc";
|
||||
|
||||
|
||||
class MapStore {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
@@ -151,16 +181,32 @@ class MapStore {
|
||||
return sorted.sort((a, b) => b.name.localeCompare(a.name));
|
||||
case "date_asc":
|
||||
return sorted.sort((a, b) => {
|
||||
if ('created_at' in a && 'created_at' in b && a.created_at && b.created_at) {
|
||||
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
||||
if (
|
||||
"created_at" in a &&
|
||||
"created_at" in b &&
|
||||
a.created_at &&
|
||||
b.created_at
|
||||
) {
|
||||
return (
|
||||
new Date(a.created_at).getTime() -
|
||||
new Date(b.created_at).getTime()
|
||||
);
|
||||
}
|
||||
// Фоллбэк: сортировка по ID, если дата недоступна
|
||||
return a.id - b.id;
|
||||
});
|
||||
case "date_desc":
|
||||
return sorted.sort((a, b) => {
|
||||
if ('created_at' in a && 'created_at' in b && a.created_at && b.created_at) {
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
||||
if (
|
||||
"created_at" in a &&
|
||||
"created_at" in b &&
|
||||
a.created_at &&
|
||||
b.created_at
|
||||
) {
|
||||
return (
|
||||
new Date(b.created_at).getTime() -
|
||||
new Date(a.created_at).getTime()
|
||||
);
|
||||
}
|
||||
// Фоллбэк: сортировка по ID, если дата недоступна
|
||||
return b.id - a.id;
|
||||
@@ -179,6 +225,26 @@ class MapStore {
|
||||
return this.sortFeatures(this.sights, this.sightSort);
|
||||
}
|
||||
|
||||
// ГЕТТЕРЫ ДЛЯ ФИЛЬТРАЦИИ ПО ГОРОДУ
|
||||
get filteredStations(): ApiStation[] {
|
||||
const selectedCityId = selectedCityStore.selectedCityId;
|
||||
if (!selectedCityId) {
|
||||
return this.sortedStations;
|
||||
}
|
||||
return this.sortedStations.filter(
|
||||
(station) => station.city_id === selectedCityId
|
||||
);
|
||||
}
|
||||
|
||||
get filteredSights(): ApiSight[] {
|
||||
const selectedCityId = selectedCityStore.selectedCityId;
|
||||
if (!selectedCityId) {
|
||||
return this.sortedSights;
|
||||
}
|
||||
return this.sortedSights.filter(
|
||||
(sight) => sight.city_id === selectedCityId
|
||||
);
|
||||
}
|
||||
|
||||
getRoutes = async () => {
|
||||
const response = await languageInstance("ru").get("/route");
|
||||
@@ -243,7 +309,15 @@ class MapStore {
|
||||
address: "",
|
||||
system_name: name,
|
||||
});
|
||||
stationsStore.setCreateCommonData({ latitude, longitude, city_id: 1 });
|
||||
const selectedCityId = selectedCityStore.selectedCityId || 1;
|
||||
const selectedCityName =
|
||||
selectedCityStore.selectedCityName || "Неизвестный город";
|
||||
stationsStore.setCreateCommonData({
|
||||
latitude,
|
||||
longitude,
|
||||
city_id: selectedCityId,
|
||||
city: selectedCityName,
|
||||
});
|
||||
|
||||
await stationsStore.createStation();
|
||||
createdItem =
|
||||
@@ -290,7 +364,11 @@ class MapStore {
|
||||
sightsStore.updateCreateSight("en", { name, address: "" });
|
||||
sightsStore.updateCreateSight("zh", { name, address: "" });
|
||||
|
||||
await sightsStore.createSightAction(1, { latitude, longitude });
|
||||
const selectedCityId = selectedCityStore.selectedCityId || 1;
|
||||
await sightsStore.createSightAction(selectedCityId, {
|
||||
latitude,
|
||||
longitude,
|
||||
});
|
||||
createdItem = sightsStore.sights[sightsStore.sights.length - 1];
|
||||
} else {
|
||||
throw new Error(`Unknown feature type for creation: ${featureType}`);
|
||||
@@ -1181,9 +1259,9 @@ class MapService {
|
||||
}
|
||||
|
||||
public loadFeaturesFromApi(
|
||||
apiStations: typeof mapStore.stations,
|
||||
_apiStations: typeof mapStore.stations,
|
||||
apiRoutes: typeof mapStore.routes,
|
||||
apiSights: typeof mapStore.sights
|
||||
_apiSights: typeof mapStore.sights
|
||||
): void {
|
||||
if (!this.map) return;
|
||||
|
||||
@@ -1191,7 +1269,11 @@ class MapService {
|
||||
const pointFeatures: Feature<Point>[] = [];
|
||||
const lineFeatures: Feature<LineString>[] = [];
|
||||
|
||||
apiStations.forEach((station) => {
|
||||
// Используем фильтрованные данные из mapStore
|
||||
const filteredStations = mapStore.filteredStations;
|
||||
const filteredSights = mapStore.filteredSights;
|
||||
|
||||
filteredStations.forEach((station) => {
|
||||
if (station.longitude == null || station.latitude == null) return;
|
||||
const point = new Point(
|
||||
transform(
|
||||
@@ -1206,7 +1288,7 @@ class MapService {
|
||||
pointFeatures.push(feature);
|
||||
});
|
||||
|
||||
apiSights.forEach((sight) => {
|
||||
filteredSights.forEach((sight) => {
|
||||
if (sight.longitude == null || sight.latitude == null) return;
|
||||
const point = new Point(
|
||||
transform([sight.longitude, sight.latitude], "EPSG:4326", projection)
|
||||
@@ -1329,6 +1411,7 @@ class MapService {
|
||||
name: properties.name,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
city_id: properties.city_id || 1, // Default city_id if not available
|
||||
});
|
||||
} else if (featureType === "sight") {
|
||||
const coords = (geometry as Point).getCoordinates();
|
||||
@@ -1339,6 +1422,7 @@ class MapService {
|
||||
description: properties.description,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
city_id: properties.city_id || 1, // Default city_id if not available
|
||||
});
|
||||
} else if (featureType === "route") {
|
||||
const coords = (geometry as LineString).getCoordinates();
|
||||
@@ -2176,7 +2260,7 @@ const MapControls: React.FC<MapControlsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
import {observer} from "mobx-react-lite";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// --- MAP SIGHTBAR COMPONENT ---
|
||||
interface MapSightbarProps {
|
||||
@@ -2188,7 +2272,8 @@ interface MapSightbarProps {
|
||||
activeSection: string | null;
|
||||
setActiveSection: (section: string | null) => void;
|
||||
}
|
||||
const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
({
|
||||
mapService,
|
||||
mapFeatures,
|
||||
selectedFeature,
|
||||
@@ -2196,28 +2281,78 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
setSelectedIds,
|
||||
activeSection,
|
||||
setActiveSection,
|
||||
}) => {
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [stationSort, setStationSort] = useState<SortType>("name_asc");
|
||||
const [sightSort, setSightSort] = useState<SortType>("name_asc");
|
||||
|
||||
const { isOpen } = menuStore;
|
||||
|
||||
const { selectedCityId } = selectedCityStore;
|
||||
|
||||
const actualFeatures = useMemo(
|
||||
() => mapFeatures.filter((f) => !f.get("isProxy")),
|
||||
[mapFeatures]
|
||||
);
|
||||
|
||||
// Создаем объединенный список всех объектов для поиска
|
||||
const allFeatures = useMemo(() => {
|
||||
const stations = mapStore.filteredStations.map((station) => {
|
||||
const feature = new Feature({
|
||||
geometry: new Point(
|
||||
transform(
|
||||
[station.longitude, station.latitude],
|
||||
"EPSG:4326",
|
||||
"EPSG:3857"
|
||||
)
|
||||
),
|
||||
name: station.name,
|
||||
});
|
||||
feature.setId(`station-${station.id}`);
|
||||
feature.set("featureType", "station");
|
||||
feature.set("created_at", station.created_at);
|
||||
return feature;
|
||||
});
|
||||
|
||||
const sights = mapStore.filteredSights.map((sight) => {
|
||||
const feature = new Feature({
|
||||
geometry: new Point(
|
||||
transform(
|
||||
[sight.longitude, sight.latitude],
|
||||
"EPSG:4326",
|
||||
"EPSG:3857"
|
||||
)
|
||||
),
|
||||
name: sight.name,
|
||||
description: sight.description,
|
||||
});
|
||||
feature.setId(`sight-${sight.id}`);
|
||||
feature.set("featureType", "sight");
|
||||
feature.set("created_at", sight.created_at);
|
||||
return feature;
|
||||
});
|
||||
|
||||
const lines = actualFeatures.filter(
|
||||
(f) => f.get("featureType") === "route"
|
||||
);
|
||||
|
||||
return [...stations, ...sights, ...lines];
|
||||
}, [
|
||||
mapStore.filteredStations,
|
||||
mapStore.filteredSights,
|
||||
actualFeatures,
|
||||
selectedCityId,
|
||||
mapStore,
|
||||
]);
|
||||
|
||||
const filteredFeatures = useMemo(() => {
|
||||
if (!searchQuery.trim()) return actualFeatures;
|
||||
return actualFeatures.filter((f) =>
|
||||
if (!searchQuery.trim()) return allFeatures;
|
||||
return allFeatures.filter((f) =>
|
||||
((f.get("name") as string) || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}, [actualFeatures, searchQuery]);
|
||||
}, [allFeatures, searchQuery]);
|
||||
|
||||
const handleFeatureClick = useCallback(
|
||||
(id: string | number) => {
|
||||
@@ -2279,13 +2414,13 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
case "name_asc":
|
||||
return sorted.sort((a, b) =>
|
||||
((a.get("name") as string) || "").localeCompare(
|
||||
((b.get("name") as string) || "")
|
||||
(b.get("name") as string) || ""
|
||||
)
|
||||
);
|
||||
case "name_desc":
|
||||
return sorted.sort((a, b) =>
|
||||
((b.get("name") as string) || "").localeCompare(
|
||||
((a.get("name") as string) || "")
|
||||
(a.get("name") as string) || ""
|
||||
)
|
||||
);
|
||||
case "date_asc":
|
||||
@@ -2316,7 +2451,9 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
const stations = filteredFeatures.filter(
|
||||
(f) => f.get("featureType") === "station"
|
||||
);
|
||||
const lines = filteredFeatures.filter((f) => f.get("featureType") === "route");
|
||||
const lines = filteredFeatures.filter(
|
||||
(f) => f.get("featureType") === "route"
|
||||
);
|
||||
const sights = filteredFeatures.filter(
|
||||
(f) => f.get("featureType") === "sight"
|
||||
);
|
||||
@@ -2379,7 +2516,7 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
/>
|
||||
<span
|
||||
className={`font-medium whitespace-nowrap overflow-x-auto block
|
||||
scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent`}
|
||||
scrollbar-visible`}
|
||||
title={fName}
|
||||
>
|
||||
{fName}
|
||||
@@ -2391,7 +2528,8 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const featureTypeVal = feature.get("featureType");
|
||||
if (featureTypeVal) handleEditFeature(featureTypeVal, fId);
|
||||
if (featureTypeVal)
|
||||
handleEditFeature(featureTypeVal, fId);
|
||||
}}
|
||||
className="p-1 rounded-full text-gray-400 hover:text-blue-600 hover:bg-blue-100 transition-colors"
|
||||
title="Редактировать детали"
|
||||
@@ -2427,10 +2565,9 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
title: `Остановки (${sortedStations.length})`,
|
||||
icon: <Bus size={20} />,
|
||||
count: sortedStations.length,
|
||||
content: (
|
||||
<>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<label>Сортировка:</label>
|
||||
sortControl: (
|
||||
<div className="flex items-center space-x-2 p-3 bg-white border-b border-gray-200">
|
||||
<label className="text-sm text-gray-700">Сортировка:</label>
|
||||
<select
|
||||
value={stationSort}
|
||||
onChange={(e) => setStationSort(e.target.value as SortType)}
|
||||
@@ -2440,15 +2577,15 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
<option value="name_desc">Имя ↓</option>
|
||||
</select>
|
||||
</div>
|
||||
{renderFeatureList(sortedStations, "station", MapPin)}
|
||||
</>
|
||||
),
|
||||
content: renderFeatureList(sortedStations, "station", MapPin),
|
||||
},
|
||||
{
|
||||
id: "lines",
|
||||
title: `Маршруты (${lines.length})`,
|
||||
icon: <RouteIcon size={20} />,
|
||||
count: lines.length,
|
||||
sortControl: null,
|
||||
content: renderFeatureList(lines, "route", ArrowRightLeft),
|
||||
},
|
||||
{
|
||||
@@ -2456,10 +2593,9 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
title: `Достопримечательности (${sortedSights.length})`,
|
||||
icon: <Landmark size={20} />,
|
||||
count: sortedSights.length,
|
||||
content: (
|
||||
<>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<label>Сортировка:</label>
|
||||
sortControl: (
|
||||
<div className="flex items-center space-x-2 p-3 bg-white border-b border-gray-200">
|
||||
<label className="text-sm text-gray-700">Сортировка:</label>
|
||||
<select
|
||||
value={sightSort}
|
||||
onChange={(e) => setSightSort(e.target.value as SortType)}
|
||||
@@ -2469,9 +2605,8 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
<option value="name_desc">Имя ↓</option>
|
||||
</select>
|
||||
</div>
|
||||
{renderFeatureList(sortedSights, "sight", Landmark)}
|
||||
</>
|
||||
),
|
||||
content: renderFeatureList(sortedSights, "sight", Landmark),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2480,7 +2615,11 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<div className={`${isOpen ? "w-[360px]" : "w-[590px]"} transition-all duration-300 ease-in-out relative bg-gray-50 shadow-lg flex flex-col border-l border-gray-200 h-[90vh]`}>
|
||||
<div
|
||||
className={`${
|
||||
isOpen ? "w-[360px]" : "w-[590px]"
|
||||
} transition-all duration-300 ease-in-out relative bg-gray-50 shadow-lg flex flex-col border-l border-gray-200 h-[90vh]`}
|
||||
>
|
||||
<div className="p-4 bg-gray-700 text-white">
|
||||
<h2 className="text-lg font-semibold">Панель управления</h2>
|
||||
</div>
|
||||
@@ -2493,7 +2632,7 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-y-auto">
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-y-auto scrollbar-visible">
|
||||
{filteredFeatures.length === 0 && searchQuery ? (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
Ничего не найдено.
|
||||
@@ -2536,14 +2675,19 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
▼
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className={`overflow-y-auto scrollbar-hide bg-white ${
|
||||
activeSection === s.id ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<div className="p-3 text-sm text-gray-600">{s.content}</div>
|
||||
{activeSection === s.id && (
|
||||
<>
|
||||
{s.sortControl && (
|
||||
<div className="flex-shrink-0">{s.sortControl}</div>
|
||||
)}
|
||||
<div className="overflow-y-auto scrollbar-visible bg-white flex-1 min-h-0">
|
||||
<div className="p-3 text-sm text-gray-600">
|
||||
{s.content}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
)}
|
||||
@@ -2561,9 +2705,10 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
// --- MAP PAGE COMPONENT ---
|
||||
export const MapPage: React.FC = () => {
|
||||
export const MapPage: React.FC = observer(() => {
|
||||
const mapRef = useRef<HTMLDivElement | null>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement | null>(null);
|
||||
const [mapServiceInstance, setMapServiceInstance] =
|
||||
@@ -2584,6 +2729,8 @@ export const MapPage: React.FC = () => {
|
||||
string | null
|
||||
>(() => getStoredActiveSection() || "layers");
|
||||
|
||||
const { selectedCityId } = selectedCityStore;
|
||||
|
||||
const handleFeaturesChange = useCallback(
|
||||
(feats: Feature<Geometry>[]) => setMapFeatures([...feats]),
|
||||
[]
|
||||
@@ -2750,6 +2897,22 @@ export const MapPage: React.FC = () => {
|
||||
saveActiveSection(activeSectionFromParent);
|
||||
}, [activeSectionFromParent]);
|
||||
|
||||
// Перезагружаем данные при изменении города
|
||||
useEffect(() => {
|
||||
if (mapServiceInstance && !isDataLoading) {
|
||||
// Очищаем текущие объекты на карте
|
||||
mapServiceInstance.pointSource.clear();
|
||||
mapServiceInstance.lineSource.clear();
|
||||
|
||||
// Загружаем новые данные с фильтрацией по городу
|
||||
mapServiceInstance.loadFeaturesFromApi(
|
||||
mapStore.stations,
|
||||
mapStore.routes,
|
||||
mapStore.sights
|
||||
);
|
||||
}
|
||||
}, [selectedCityId, mapServiceInstance, isDataLoading]);
|
||||
|
||||
const showLoader = isMapLoading || isDataLoading;
|
||||
const showContent = mapServiceInstance && !showLoader && !error;
|
||||
const isAnythingSelected =
|
||||
@@ -2866,4 +3029,4 @@ export const MapPage: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -67,7 +67,8 @@ export const StationCreatePage = observer(() => {
|
||||
// ОБНОВЛЕННАЯ ФУНКЦИЯ: Проверка и вызов окна или создания
|
||||
const handleCreate = async () => {
|
||||
const isCityMissing = !createStationData.common.city_id;
|
||||
const isNameMissing = !createStationData[language].name;
|
||||
// Проверяем названия на всех языках
|
||||
const isNameMissing = !createStationData.ru.name || !createStationData.en.name || !createStationData.zh.name;
|
||||
|
||||
if (isCityMissing || isNameMissing) {
|
||||
setIsSaveWarningOpen(true);
|
||||
|
||||
@@ -67,7 +67,8 @@ export const StationEditPage = observer(() => {
|
||||
// ОБНОВЛЕННАЯ ФУНКЦИЯ: Проверка и вызов окна или редактирования
|
||||
const handleEdit = async () => {
|
||||
const isCityMissing = !editStationData.common.city_id;
|
||||
const isNameMissing = !editStationData[language].name;
|
||||
// Проверяем названия на всех языках
|
||||
const isNameMissing = !editStationData.ru.name || !editStationData.en.name || !editStationData.zh.name;
|
||||
|
||||
if (isCityMissing || isNameMissing) {
|
||||
setIsSaveWarningOpen(true);
|
||||
|
||||
@@ -5,9 +5,10 @@ export const SaveWithoutCityAgree = ({ blocker }: { blocker: any }) => {
|
||||
<div className="fixed top-0 left-0 w-screen h-screen flex justify-center items-center z-10000 bg-black/30">
|
||||
<div className="bg-white p-4 w-140 rounded-lg flex flex-col gap-4 items-center">
|
||||
<p className="text-black w-140 text-center">
|
||||
Вы не указали город и/или не заполнили названия на всех языках.
|
||||
Вы не указали город и/или не заполнили названия на всех языках
|
||||
(русский, английский, китайский).
|
||||
<br />
|
||||
Сохранить достопримечательность без этой информации?
|
||||
Сохранить без этой информации?
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button variant="contained" onClick={() => blocker.proceed()}>
|
||||
|
||||
@@ -119,7 +119,8 @@ export const CreateInformationTab = observer(
|
||||
|
||||
const handleSave = async () => {
|
||||
const isCityMissing = !sight.city_id;
|
||||
const isNameMissing = !sight[language].name;
|
||||
// Проверяем названия на всех языках
|
||||
const isNameMissing = !sight.ru.name || !sight.en.name || !sight.zh.name;
|
||||
|
||||
if (isCityMissing || isNameMissing) {
|
||||
setIsSaveWarningOpen(true);
|
||||
|
||||
@@ -128,7 +128,8 @@ export const InformationTab = observer(
|
||||
// ОБНОВЛЕННАЯ ФУНКЦИЯ: Проверка и вызов окна или сохранения
|
||||
const handleSave = async () => {
|
||||
const isCityMissing = !sight.common.city_id;
|
||||
const isNameMissing = !sight[language].name;
|
||||
// Проверяем названия на всех языках
|
||||
const isNameMissing = !sight.ru.name || !sight.en.name || !sight.zh.name;
|
||||
|
||||
if (isCityMissing || isNameMissing) {
|
||||
setIsSaveWarningOpen(true);
|
||||
|
||||
Reference in New Issue
Block a user