diff --git a/.env b/.env
index c554530..c8ded82 100644
--- a/.env
+++ b/.env
@@ -1,8 +1,8 @@
-VITE_API_URL='https://wn.st.unprism.ru'
-VITE_REACT_APP ='https://wn.st.unprism.ru/'
-VITE_KRBL_MEDIA='https://wn.st.unprism.ru/media/'
-VITE_NEED_AUTH='true'
-# VITE_API_URL='https://wn.krbl.ru'
-# VITE_REACT_APP ='https://wn.krbl.ru/'
-# VITE_KRBL_MEDIA='https://wn.krbl.ru/media/'
+# VITE_API_URL='https://wn.st.unprism.ru'
+# VITE_REACT_APP ='https://wn.st.unprism.ru/'
+# VITE_KRBL_MEDIA='https://wn.st.unprism.ru/media/'
# VITE_NEED_AUTH='true'
+VITE_API_URL='https://wn.krbl.ru'
+VITE_REACT_APP ='https://wn.krbl.ru/'
+VITE_KRBL_MEDIA='https://wn.krbl.ru/media/'
+VITE_NEED_AUTH='true'
diff --git a/src/client/src/components/ListOfSights/SightFrame.jsx b/src/client/src/components/ListOfSights/SightFrame.jsx
index 073befc..2ae027e 100644
--- a/src/client/src/components/ListOfSights/SightFrame.jsx
+++ b/src/client/src/components/ListOfSights/SightFrame.jsx
@@ -43,9 +43,51 @@ const SightFrame = observer(({ media, sight_id, sight_name }) => {
const [threeViewResetKey, setThreeViewResetKey] = useState(0);
const threeViewControlRef = useRef(null);
const mediaCache = useRef({});
+ const idleTimerRef = useRef(null);
const textWrapperRef = useRef(null);
+ // Автозакрытие fullscreen 3D при бездействии (45 сек)
+ useEffect(() => {
+ if (!isFullscreen3D) {
+ if (idleTimerRef.current) {
+ clearInterval(idleTimerRef.current);
+ idleTimerRef.current = null;
+ }
+ return;
+ }
+
+ let idleSeconds = 0;
+
+ const checkIdle = () => {
+ idleSeconds += 1;
+ if (idleSeconds >= 45) {
+ setIsFullscreen3D(false);
+ }
+ };
+
+ idleTimerRef.current = setInterval(checkIdle, 1000);
+
+ const resetIdle = () => {
+ idleSeconds = 0;
+ };
+
+ const events = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
+ events.forEach((event) => {
+ window.addEventListener(event, resetIdle, { passive: true });
+ });
+
+ return () => {
+ if (idleTimerRef.current) {
+ clearInterval(idleTimerRef.current);
+ idleTimerRef.current = null;
+ }
+ events.forEach((event) => {
+ window.removeEventListener(event, resetIdle);
+ });
+ };
+ }, [isFullscreen3D]);
+
const {
routeSights,
routeSightsEn,
diff --git a/src/client/src/components/side-menu/SideMenu.jsx b/src/client/src/components/side-menu/SideMenu.jsx
index 0d38baf..c92a564 100644
--- a/src/client/src/components/side-menu/SideMenu.jsx
+++ b/src/client/src/components/side-menu/SideMenu.jsx
@@ -13,6 +13,7 @@ import StationsList from "./StationsList";
import LeftWidget from "./LeftWidget";
import { apiStore } from "../../api/ApiStore/store";
import { getMediaUrl } from "../../api/apiConfig";
+import defaultCrest from "../../assets/images/Герб.png";
const SideMenu = observer(({ onMenuToggle }) => {
const {
@@ -369,13 +370,11 @@ const SideMenu = observer(({ onMenuToggle }) => {
"background 0.3s ease, backdrop-filter 0.3s ease, box-shadow 0.3s ease",
}}
>
- {designData?.creastPath && (
-
- )}
+
{carrier?.slogan && (
{carrier.slogan}
)}
diff --git a/src/client/src/stores/ColorStore.ts b/src/client/src/stores/ColorStore.ts
index 75ce2a0..f24723a 100644
--- a/src/client/src/stores/ColorStore.ts
+++ b/src/client/src/stores/ColorStore.ts
@@ -68,4 +68,4 @@ class ColorStore implements ColorStore {
}
export const colorStore = new ColorStore();
-export { ColorStore };
\ No newline at end of file
+export { ColorStore };
diff --git a/src/pages/Carrier/CarrierCreatePage/index.tsx b/src/pages/Carrier/CarrierCreatePage/index.tsx
index ba87efc..40b1c47 100644
--- a/src/pages/Carrier/CarrierCreatePage/index.tsx
+++ b/src/pages/Carrier/CarrierCreatePage/index.tsx
@@ -6,6 +6,8 @@ import {
MenuItem,
FormControl,
InputLabel,
+ ToggleButtonGroup,
+ ToggleButton,
} from "@mui/material";
import { observer } from "mobx-react-lite";
import { ArrowLeft, Loader2, Save } from "lucide-react";
@@ -27,7 +29,59 @@ import {
import { useState, useEffect } from "react";
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
+type ColorFields = { main_color: string; left_color: string; right_color: string; rgb_color: string };
+
+const colorFields = (data: ColorFields) => ({
+ main_color: data.main_color,
+ left_color: data.left_color,
+ right_color: data.right_color,
+ rgb_color: data.rgb_color,
+});
+
+const ColorPickerField = ({
+ label,
+ value,
+ onChange,
+}: {
+ label: string;
+ value: string;
+ onChange: (val: string) => void;
+}) => (
+
+
+ onChange(e.target.value)}
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
+ />
+
+
onChange(e.target.value)}
+ InputProps={{
+ endAdornment: value ? (
+
+ ) : undefined,
+ }}
+ />
+
+);
+
export const CarrierCreatePage = observer(() => {
+ const [colorMode, setColorMode] = useState<"rgb" | "three">("three");
const navigate = useNavigate();
const { createCarrierData, setCreateCarrierData } = carrierStore;
const { language } = languageStore;
@@ -220,6 +274,114 @@ export const CarrierCreatePage = observer(() => {
}
/>
+
+
+
+ Режим цвета:
+ {
+ if (!val) return;
+ setColorMode(val);
+ if (val === "rgb") {
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { main_color: "", left_color: "", right_color: "", rgb_color: createCarrierData.rgb_color }
+ );
+ } else {
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { main_color: createCarrierData.main_color, left_color: createCarrierData.left_color, right_color: createCarrierData.right_color, rgb_color: "" }
+ );
+ }
+ }}
+ >
+ Один цвет
+ Три цвета
+
+
+
* при переключении цвет сбрасывается
+
+
+ {colorMode === "rgb" ? (
+
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { ...colorFields(createCarrierData), rgb_color: val }
+ )
+ }
+ />
+ ) : (
+ <>
+
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { ...colorFields(createCarrierData), main_color: val }
+ )
+ }
+ />
+
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { ...colorFields(createCarrierData), left_color: val }
+ )
+ }
+ />
+
+ setCreateCarrierData(
+ createCarrierData[language].full_name,
+ createCarrierData[language].short_name,
+ createCarrierData.city_id,
+ createCarrierData[language].slogan,
+ selectedMediaId || "",
+ language,
+ { ...colorFields(createCarrierData), right_color: val }
+ )
+ }
+ />
+ >
+ )}
+
+
({
+ main_color: data.main_color,
+ left_color: data.left_color,
+ right_color: data.right_color,
+ rgb_color: data.rgb_color,
+});
+
+const ColorPickerField = ({
+ label,
+ value,
+ onChange,
+}: {
+ label: string;
+ value: string;
+ onChange: (val: string) => void;
+}) => (
+
+
+ onChange(e.target.value)}
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
+ />
+
+
onChange(e.target.value)}
+ InputProps={{
+ endAdornment: value ? (
+
+ ) : undefined,
+ }}
+ />
+
+);
+
export const CarrierEditPage = observer(() => {
const navigate = useNavigate();
const { id } = useParams();
@@ -37,6 +90,7 @@ export const CarrierEditPage = observer(() => {
const { language } = languageStore;
const canReadCities = authStore.canRead("cities");
+ const [colorMode, setColorMode] = useState<"rgb" | "three">("rgb");
const [isLoading, setIsLoading] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(true);
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
@@ -68,13 +122,25 @@ export const CarrierEditPage = observer(() => {
const carrierData = await getCarrier(Number(id));
if (carrierData) {
+ const colors = {
+ main_color: carrierData.ru?.main_color || "",
+ left_color: carrierData.ru?.left_color || "",
+ right_color: carrierData.ru?.right_color || "",
+ rgb_color: carrierData.ru?.rgb_color || "",
+ };
+ if (colors.rgb_color) {
+ setColorMode("rgb");
+ } else {
+ setColorMode("three");
+ }
setEditCarrierData(
carrierData.ru?.full_name || "",
carrierData.ru?.short_name || "",
carrierData.ru?.city_id || 0,
carrierData.ru?.slogan || "",
carrierData.ru?.logo || "",
- "ru"
+ "ru",
+ colors
);
setEditCarrierData(
carrierData.en?.full_name || "",
@@ -273,6 +339,114 @@ export const CarrierEditPage = observer(() => {
}
/>
+
+
+
+ Режим цвета:
+ {
+ if (!val) return;
+ setColorMode(val);
+ if (val === "rgb") {
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { main_color: "", left_color: "", right_color: "", rgb_color: editCarrierData.rgb_color }
+ );
+ } else {
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { main_color: editCarrierData.main_color, left_color: editCarrierData.left_color, right_color: editCarrierData.right_color, rgb_color: "" }
+ );
+ }
+ }}
+ >
+ Один цвет
+ Три цвета
+
+
+
* при переключении цвет сбрасывается
+
+
+ {colorMode === "rgb" ? (
+
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { ...colorFields(editCarrierData), rgb_color: val }
+ )
+ }
+ />
+ ) : (
+ <>
+
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { ...colorFields(editCarrierData), main_color: val }
+ )
+ }
+ />
+
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { ...colorFields(editCarrierData), left_color: val }
+ )
+ }
+ />
+
+ setEditCarrierData(
+ editCarrierData[language].full_name,
+ editCarrierData[language].short_name,
+ editCarrierData.city_id,
+ editCarrierData[language].slogan,
+ editCarrierData.logo,
+ language,
+ { ...colorFields(editCarrierData), right_color: val }
+ )
+ }
+ />
+ >
+ )}
+
+
{
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const { language } = languageStore;
const { createCityData, setCreateCityData, setCreateCityWeatherCode } =
cityStore;
diff --git a/src/pages/City/CityEditPage/index.tsx b/src/pages/City/CityEditPage/index.tsx
index 1090841..f765aba 100644
--- a/src/pages/City/CityEditPage/index.tsx
+++ b/src/pages/City/CityEditPage/index.tsx
@@ -23,12 +23,19 @@ import {
SelectMediaDialog,
UploadMediaDialog,
PreviewMediaDialog,
+ selectedCityStore,
} from "@shared";
import { useEffect, useState } from "react";
import { LanguageSwitcher, ImageUploadCard } from "@widgets";
export const CityEditPage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(true);
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
diff --git a/src/pages/City/CityListPage/index.tsx b/src/pages/City/CityListPage/index.tsx
index b1349dc..afbab5f 100644
--- a/src/pages/City/CityListPage/index.tsx
+++ b/src/pages/City/CityListPage/index.tsx
@@ -1,6 +1,6 @@
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
import { ruRU } from "@mui/x-data-grid/locales";
-import { authStore, languageStore, cityStore, countryStore, SearchInput } from "@shared";
+import { authStore, languageStore, cityStore, countryStore, selectedCityStore, SearchInput } from "@shared";
import { useEffect, useState, useMemo } from "react";
import { observer } from "mobx-react-lite";
import { Pencil, Trash2, Minus } from "lucide-react";
@@ -59,16 +59,21 @@ export const CityListPage = observer(() => {
}, [cities, countryStore.countries, language, isLoading]);
const filteredRows = useMemo(() => {
+ const { selectedCityId } = selectedCityStore;
+ if (!selectedCityId) return [];
+
const query = searchQuery.trim().toLowerCase();
- if (!query) return rows;
- return rows.filter((row) => {
+ const result = rows.filter((row) => row.id === selectedCityId);
+
+ if (!query) return result;
+ return result.filter((row) => {
const cityName = (row.name ?? "").toLowerCase();
const countryName = (
countryStore.countries[language]?.data?.find((c) => c.code === row.country)?.name ?? ""
).toLowerCase();
return cityName.includes(query) || countryName.includes(query);
});
- }, [rows, searchQuery, countryStore.countries, language]);
+ }, [rows, searchQuery, countryStore.countries, language, selectedCityStore.selectedCityId]);
const columns: GridColDef[] = [
{
@@ -139,7 +144,11 @@ export const CityListPage = observer(() => {
Города
{canWriteCities && (
-
+
)}
@@ -195,7 +204,13 @@ export const CityListPage = observer(() => {
slots={{
noRowsOverlay: () => (
- {isLoading ? : "Нет городов"}
+ {isLoading ? (
+
+ ) : !selectedCityStore.selectedCityId ? (
+ "Выберите город"
+ ) : (
+ "Нет городов"
+ )}
),
}}
diff --git a/src/pages/Country/CountryAddPage/index.tsx b/src/pages/Country/CountryAddPage/index.tsx
index d6ef176..b7bcea1 100644
--- a/src/pages/Country/CountryAddPage/index.tsx
+++ b/src/pages/Country/CountryAddPage/index.tsx
@@ -15,11 +15,18 @@ import {
RU_COUNTRIES,
EN_COUNTRIES,
ZH_COUNTRIES,
+ selectedCityStore,
} from "@shared";
-import { useState } from "react";
+import { useState, useEffect } from "react";
export const CountryAddPage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const { createCountryData, setCountryData, createCountry } = countryStore;
diff --git a/src/pages/Country/CountryCreatePage/index.tsx b/src/pages/Country/CountryCreatePage/index.tsx
index 4601465..3de4d13 100644
--- a/src/pages/Country/CountryCreatePage/index.tsx
+++ b/src/pages/Country/CountryCreatePage/index.tsx
@@ -4,12 +4,18 @@ import { ArrowLeft, Save } from "lucide-react";
import { Loader2 } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
-import { countryStore, languageStore } from "@shared";
-import { useState } from "react";
+import { countryStore, languageStore, selectedCityStore } from "@shared";
+import { useState, useEffect } from "react";
import { LanguageSwitcher } from "@widgets";
export const CountryCreatePage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const { language } = languageStore;
const { createCountryData, setCountryData, createCountry } = countryStore;
diff --git a/src/pages/Country/CountryEditPage/index.tsx b/src/pages/Country/CountryEditPage/index.tsx
index fbfda84..b20ab8e 100644
--- a/src/pages/Country/CountryEditPage/index.tsx
+++ b/src/pages/Country/CountryEditPage/index.tsx
@@ -4,12 +4,18 @@ import { ArrowLeft, Save } from "lucide-react";
import { Loader2 } from "lucide-react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
-import { countryStore, languageStore, LoadingSpinner } from "@shared";
+import { countryStore, languageStore, LoadingSpinner, selectedCityStore } from "@shared";
import { useEffect, useState } from "react";
import { LanguageSwitcher } from "@widgets";
export const CountryEditPage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(true);
const { language } = languageStore;
diff --git a/src/pages/Country/CountryListPage/index.tsx b/src/pages/Country/CountryListPage/index.tsx
index efdfd9c..92555f4 100644
--- a/src/pages/Country/CountryListPage/index.tsx
+++ b/src/pages/Country/CountryListPage/index.tsx
@@ -1,6 +1,6 @@
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
import { ruRU } from "@mui/x-data-grid/locales";
-import { authStore, countryStore, languageStore, SearchInput } from "@shared";
+import { authStore, countryStore, languageStore, selectedCityStore, SearchInput } from "@shared";
import { useEffect, useState, useMemo } from "react";
import { observer } from "mobx-react-lite";
import { Trash2, Minus } from "lucide-react";
@@ -74,15 +74,20 @@ export const CountryListPage = observer(() => {
];
const rows = useMemo(() => {
+ const { selectedCity } = selectedCityStore;
+ if (!selectedCity) {
+ return [];
+ }
const query = searchQuery.trim().toLowerCase();
return (countries[language]?.data ?? [])
+ .filter((country) => country.code === selectedCity.country_code)
.filter((country) => !query || (country.name ?? "").toLowerCase().includes(query))
.map((country) => ({
id: country.code,
code: country.code,
name: country.name,
}));
- }, [countries[language]?.data, searchQuery]);
+ }, [countries[language]?.data, searchQuery, selectedCityStore.selectedCity]);
return (
<>
@@ -92,7 +97,11 @@ export const CountryListPage = observer(() => {
Страны
{canWriteCountries && (
-
+
)}
@@ -148,7 +157,13 @@ export const CountryListPage = observer(() => {
slots={{
noRowsOverlay: () => (
- {isLoading ? : "Нет стран"}
+ {isLoading ? (
+
+ ) : !selectedCityStore.selectedCityId ? (
+ "Выберите город"
+ ) : (
+ "Нет стран"
+ )}
),
}}
diff --git a/src/pages/CreateSightPage/index.tsx b/src/pages/CreateSightPage/index.tsx
index f0e34da..7f6729f 100644
--- a/src/pages/CreateSightPage/index.tsx
+++ b/src/pages/CreateSightPage/index.tsx
@@ -5,6 +5,7 @@ import {
cityStore,
createSightStore,
languageStore,
+ selectedCityStore,
} from "@shared";
import {
CreateInformationTab,
@@ -30,6 +31,11 @@ export const CreateSightPage = observer(() => {
const { getArticles } = articlesStore;
const needLeave = createSightStore.needLeaveAgree;
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const handleChange = (_: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
diff --git a/src/pages/EditSightPage/index.tsx b/src/pages/EditSightPage/index.tsx
index 7ca464e..6c94a6c 100644
--- a/src/pages/EditSightPage/index.tsx
+++ b/src/pages/EditSightPage/index.tsx
@@ -9,6 +9,7 @@ import {
cityStore,
editSightStore,
LoadingSpinner,
+ selectedCityStore,
} from "@shared";
import { useBlocker, useParams } from "react-router-dom";
@@ -25,6 +26,11 @@ export const EditSightPage = observer(() => {
const { sight, getSightInfo, needLeaveAgree, getRightArticles } = editSightStore;
const { getArticles } = articlesStore;
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const { id } = useParams();
const { getCities } = cityStore;
diff --git a/src/pages/Route/RouteCreatePage/index.tsx b/src/pages/Route/RouteCreatePage/index.tsx
index 908f4de..769a550 100644
--- a/src/pages/Route/RouteCreatePage/index.tsx
+++ b/src/pages/Route/RouteCreatePage/index.tsx
@@ -39,6 +39,12 @@ import type { Route } from "@shared";
export const RouteCreatePage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [carrier, setCarrier] = useState("");
const [routeNumber, setRouteNumber] = useState("");
const [routeCoords, setRouteCoords] = useState("");
@@ -555,7 +561,7 @@ export const RouteCreatePage = observer(() => {
/>
{
diff --git a/src/pages/Route/RouteEditPage/index.tsx b/src/pages/Route/RouteEditPage/index.tsx
index 4c3fe9c..c70c7ac 100644
--- a/src/pages/Route/RouteEditPage/index.tsx
+++ b/src/pages/Route/RouteEditPage/index.tsx
@@ -36,11 +36,18 @@ import {
UploadMediaDialog,
PreviewMediaDialog,
LoadingSpinner,
+ selectedCityStore,
} from "@shared";
import { LinkedItems } from "../LinekedStations";
export const RouteEditPage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const { id } = useParams();
const { editRouteData, copyRouteAction } = routeStore;
const [isLoading, setIsLoading] = useState(false);
@@ -548,7 +555,7 @@ export const RouteEditPage = observer(() => {
/>
{
diff --git a/src/pages/Route/RouteListPage/index.tsx b/src/pages/Route/RouteListPage/index.tsx
index d2390d4..9635780 100644
--- a/src/pages/Route/RouteListPage/index.tsx
+++ b/src/pages/Route/RouteListPage/index.tsx
@@ -210,6 +210,9 @@ export const RouteListPage = observer(() => {
const rows = useMemo(() => {
const { selectedCityId } = selectedCityStore;
+ if (!selectedCityId) {
+ return [];
+ }
const query = searchQuery.trim().toLowerCase();
let filtered = routes.data;
if (selectedCityId) {
@@ -247,7 +250,11 @@ export const RouteListPage = observer(() => {
Маршруты
{canWriteRoutes && (
-
+
)}
@@ -304,7 +311,13 @@ export const RouteListPage = observer(() => {
slots={{
noRowsOverlay: () => (
- {isLoading ? : "Нет маршрутов"}
+ {isLoading ? (
+
+ ) : !selectedCityStore.selectedCityId ? (
+ "Выберите город"
+ ) : (
+ "Нет маршрутов"
+ )}
),
}}
diff --git a/src/pages/Sight/SightListPage/index.tsx b/src/pages/Sight/SightListPage/index.tsx
index f647ca9..ec54204 100644
--- a/src/pages/Sight/SightListPage/index.tsx
+++ b/src/pages/Sight/SightListPage/index.tsx
@@ -121,8 +121,11 @@ export const SightListPage = observer(() => {
}] : []),
];
+ const { selectedCityId } = selectedCityStore;
const filteredSights = useMemo(() => {
- const { selectedCityId } = selectedCityStore;
+ if (!selectedCityId) {
+ return [];
+ }
const allowedCityIds = canReadCities
? null
: authStore.meCities["ru"].map((c) => c.city_id);
@@ -131,12 +134,12 @@ export const SightListPage = observer(() => {
if (allowedCityIds && !allowedCityIds.includes(sight.city_id)) {
return false;
}
- if (selectedCityId && sight.city_id !== selectedCityId) {
+ if (sight.city_id !== selectedCityId) {
return false;
}
return true;
});
- }, [sights, selectedCityStore.selectedCityId, canReadCities, authStore.meCities]);
+ }, [sights, selectedCityId, canReadCities, authStore.meCities]);
const query = searchQuery.trim().toLowerCase();
const rows = filteredSights
@@ -161,6 +164,7 @@ export const SightListPage = observer(() => {
)}
@@ -216,6 +220,8 @@ export const SightListPage = observer(() => {
{isLoading ? (
+ ) : !selectedCityId ? (
+ "Выберите город"
) : (
"Нет достопримечательностей"
)}
diff --git a/src/pages/Station/StationCreatePage/index.tsx b/src/pages/Station/StationCreatePage/index.tsx
index 8a9732f..2cb2fa6 100644
--- a/src/pages/Station/StationCreatePage/index.tsx
+++ b/src/pages/Station/StationCreatePage/index.tsx
@@ -19,6 +19,7 @@ import {
mediaStore,
isMediaIdEmpty,
useSelectedCity,
+ selectedCityStore,
SelectMediaDialog,
UploadMediaDialog,
PreviewMediaDialog,
@@ -32,6 +33,12 @@ import {
export const StationCreatePage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const { language } = languageStore;
const {
diff --git a/src/pages/Station/StationEditPage/index.tsx b/src/pages/Station/StationEditPage/index.tsx
index bf4e82b..5ef875a 100644
--- a/src/pages/Station/StationEditPage/index.tsx
+++ b/src/pages/Station/StationEditPage/index.tsx
@@ -19,6 +19,7 @@ import {
mediaStore,
isMediaIdEmpty,
LoadingSpinner,
+ selectedCityStore,
SelectMediaDialog,
PreviewMediaDialog,
UploadMediaDialog,
@@ -34,6 +35,12 @@ import { LinkedSights } from "../LinkedSights";
export const StationEditPage = observer(() => {
const navigate = useNavigate();
+
+ useEffect(() => {
+ selectedCityStore.setIsLocked(true);
+ return () => selectedCityStore.setIsLocked(false);
+ }, []);
+
const [isLoading, setIsLoading] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(true);
const { language } = languageStore;
diff --git a/src/pages/Station/StationListPage/index.tsx b/src/pages/Station/StationListPage/index.tsx
index c27768a..445460b 100644
--- a/src/pages/Station/StationListPage/index.tsx
+++ b/src/pages/Station/StationListPage/index.tsx
@@ -174,9 +174,12 @@ export const StationListPage = observer(() => {
const rows = useMemo(() => {
const { selectedCityId } = selectedCityStore;
+ if (!selectedCityId) {
+ return [];
+ }
const query = searchQuery.trim().toLowerCase();
return stationLists[language].data
- .filter((station: any) => !selectedCityId || station.city_id === selectedCityId)
+ .filter((station: any) => station.city_id === selectedCityId)
.filter(
(station: any) =>
!query ||
@@ -202,7 +205,11 @@ export const StationListPage = observer(() => {
Остановки
{canWriteStations && (
-
+
)}
@@ -277,7 +284,13 @@ export const StationListPage = observer(() => {
slots={{
noRowsOverlay: () => (
- {isLoading ? : "Нет остановок"}
+ {isLoading ? (
+
+ ) : !selectedCityStore.selectedCityId ? (
+ "Выберите город"
+ ) : (
+ "Нет остановок"
+ )}
),
}}
diff --git a/src/shared/store/CarrierStore/index.tsx b/src/shared/store/CarrierStore/index.tsx
index 88967cf..db1898c 100644
--- a/src/shared/store/CarrierStore/index.tsx
+++ b/src/shared/store/CarrierStore/index.tsx
@@ -16,9 +16,10 @@ export type Carrier = {
city: string;
city_id: number;
logo: string;
- // main_color: string;
- // left_color: string;
- // right_color: string;
+ main_color: string;
+ left_color: string;
+ right_color: string;
+ rgb_color: string;
};
type CarrierData = {
@@ -112,6 +113,10 @@ class CarrierStore {
createCarrierData = {
city_id: 0,
logo: "",
+ main_color: "",
+ left_color: "",
+ right_color: "",
+ rgb_color: "",
ru: {
full_name: "",
short_name: "",
@@ -135,10 +140,17 @@ class CarrierStore {
cityId: number,
slogan: string,
logoId: string,
- language: Language
+ language: Language,
+ colors?: { main_color?: string; left_color?: string; right_color?: string; rgb_color?: string }
) => {
this.createCarrierData.city_id = cityId;
this.createCarrierData.logo = logoId;
+ if (colors) {
+ if (colors.main_color !== undefined) this.createCarrierData.main_color = colors.main_color;
+ if (colors.left_color !== undefined) this.createCarrierData.left_color = colors.left_color;
+ if (colors.right_color !== undefined) this.createCarrierData.right_color = colors.right_color;
+ if (colors.rgb_color !== undefined) this.createCarrierData.rgb_color = colors.rgb_color;
+ }
this.createCarrierData[language] = {
full_name: fullName,
short_name: shortName,
@@ -198,9 +210,11 @@ class CarrierStore {
city: cityName,
city_id: this.createCarrierData.city_id,
slogan: (this.createCarrierData[language].slogan || "").trim(),
- ...(this.createCarrierData.logo
- ? { logo: this.createCarrierData.logo }
- : {}),
+ ...(this.createCarrierData.logo ? { logo: this.createCarrierData.logo } : {}),
+ ...(this.createCarrierData.rgb_color ? { rgb_color: this.createCarrierData.rgb_color } : {}),
+ ...(this.createCarrierData.main_color ? { main_color: this.createCarrierData.main_color } : {}),
+ ...(this.createCarrierData.left_color ? { left_color: this.createCarrierData.left_color } : {}),
+ ...(this.createCarrierData.right_color ? { right_color: this.createCarrierData.right_color } : {}),
};
const response = await languageInstance(language).post("/carrier", payload);
@@ -243,6 +257,10 @@ class CarrierStore {
this.createCarrierData = {
city_id: 0,
logo: "",
+ main_color: "",
+ left_color: "",
+ right_color: "",
+ rgb_color: "",
ru: {
full_name: "",
short_name: "",
@@ -265,53 +283,46 @@ class CarrierStore {
ru: {
full_name: "",
short_name: "",
-
- // main_color: "",
- // left_color: "",
- // right_color: "",
slogan: "",
},
en: {
full_name: "",
short_name: "",
-
- // main_color: "",
- // left_color: "",
- // right_color: "",
+ slogan: "",
+ },
+ zh: {
+ full_name: "",
+ short_name: "",
slogan: "",
},
city_id: 0,
logo: "",
- zh: {
- full_name: "",
- short_name: "",
-
- // main_color: "",
- // left_color: "",
- // right_color: "",
- slogan: "",
- },
+ main_color: "",
+ left_color: "",
+ right_color: "",
+ rgb_color: "",
};
setEditCarrierData = (
fullName: string,
shortName: string,
cityId: number,
- // main_color: string,
- // left_color: string,
- // right_color: string,
slogan: string,
logoId: string,
- language: Language
+ language: Language,
+ colors?: { main_color?: string; left_color?: string; right_color?: string; rgb_color?: string }
) => {
this.editCarrierData.city_id = cityId;
this.editCarrierData.logo = logoId;
+ if (colors) {
+ if (colors.main_color !== undefined) this.editCarrierData.main_color = colors.main_color;
+ if (colors.left_color !== undefined) this.editCarrierData.left_color = colors.left_color;
+ if (colors.right_color !== undefined) this.editCarrierData.right_color = colors.right_color;
+ if (colors.rgb_color !== undefined) this.editCarrierData.rgb_color = colors.rgb_color;
+ }
this.editCarrierData[language] = {
full_name: fullName,
short_name: shortName,
- // main_color: main_color,
- // left_color: left_color,
- // right_color: right_color,
slogan: slogan,
};
};
@@ -326,9 +337,11 @@ class CarrierStore {
slogan: (this.editCarrierData[lang].slogan || "").trim(),
city: cityName,
city_id: this.editCarrierData.city_id,
- ...(this.editCarrierData.logo
- ? { logo: this.editCarrierData.logo }
- : {}),
+ ...(this.editCarrierData.logo ? { logo: this.editCarrierData.logo } : {}),
+ ...(this.editCarrierData.rgb_color ? { rgb_color: this.editCarrierData.rgb_color } : {}),
+ ...(this.editCarrierData.main_color ? { main_color: this.editCarrierData.main_color } : {}),
+ ...(this.editCarrierData.left_color ? { left_color: this.editCarrierData.left_color } : {}),
+ ...(this.editCarrierData.right_color ? { right_color: this.editCarrierData.right_color } : {}),
});
runInAction(() => {
diff --git a/src/shared/store/SelectedCityStore/index.ts b/src/shared/store/SelectedCityStore/index.ts
index 270cc20..534996a 100644
--- a/src/shared/store/SelectedCityStore/index.ts
+++ b/src/shared/store/SelectedCityStore/index.ts
@@ -3,6 +3,7 @@ import { City } from "../CityStore";
class SelectedCityStore {
selectedCity: City | null = null;
+ isLocked: boolean = false;
constructor() {
makeAutoObservable(this);
@@ -32,6 +33,12 @@ class SelectedCityStore {
});
};
+ setIsLocked = (locked: boolean) => {
+ runInAction(() => {
+ this.isLocked = locked;
+ });
+ };
+
clearSelectedCity = () => {
this.setSelectedCity(null);
};
diff --git a/src/widgets/CitySelector/index.tsx b/src/widgets/CitySelector/index.tsx
index 12f6165..d73654c 100644
--- a/src/widgets/CitySelector/index.tsx
+++ b/src/widgets/CitySelector/index.tsx
@@ -12,7 +12,7 @@ import { authStore, cityStore, selectedCityStore, type City } from "@shared";
import { MapPin } from "lucide-react";
export const CitySelector: React.FC = observer(() => {
- const { selectedCity, setSelectedCity } = selectedCityStore;
+ const { selectedCity, setSelectedCity, isLocked } = selectedCityStore;
const canLoadAllCities = authStore.isAdmin && authStore.canRead("cities");
useEffect(() => {
@@ -58,26 +58,35 @@ export const CitySelector: React.FC = observer(() => {
return (
-
+