diff --git a/src/pages/City/CityCreatePage/index.tsx b/src/pages/City/CityCreatePage/index.tsx index cf02c32..0a73b8c 100644 --- a/src/pages/City/CityCreatePage/index.tsx +++ b/src/pages/City/CityCreatePage/index.tsx @@ -17,6 +17,7 @@ import { countryStore, languageStore, mediaStore, + snapshotStore, isMediaIdEmpty, SelectMediaDialog, UploadMediaDialog, @@ -60,7 +61,13 @@ export const CityCreatePage = observer(() => { const handleCreate = async () => { try { setIsLoading(true); + const ruCityName = createCityData.ru.name.trim(); await cityStore.createCity(); + try { + await snapshotStore.createEmptySnapshot(`${ruCityName}_Пустой_Экспорт`); + } catch (e) { + console.warn("Failed to create empty snapshot for city:", e); + } toast.success("Город успешно создан"); navigate("/city"); } catch (error) { diff --git a/src/pages/Snapshot/SnapshotCreatePage/index.tsx b/src/pages/Snapshot/SnapshotCreatePage/index.tsx index 8b41209..9d51f35 100644 --- a/src/pages/Snapshot/SnapshotCreatePage/index.tsx +++ b/src/pages/Snapshot/SnapshotCreatePage/index.tsx @@ -6,14 +6,24 @@ import { DialogContent, DialogActions, } from "@mui/material"; -import { snapshotStore, authStore, routeStore, selectedCityStore } from "@shared"; +import { snapshotStore, authStore, routeStore, selectedCityStore, cityStore } from "@shared"; import { observer } from "mobx-react-lite"; import { ArrowLeft, Loader2, Save } from "lucide-react"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; import { runInAction } from "mobx"; +function escapeRegex(s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function buildExportNameRegex(cityNames: string[]): RegExp { + if (!cityNames.length) return /.+/; + const pattern = cityNames.map(escapeRegex).join("|"); + return new RegExp(`^(${pattern})_.+$`); +} + export const SnapshotCreatePage = observer(() => { const { createSnapshot, getSnapshotStatus, getStorageInfo, snapshotStatus } = snapshotStore; const navigate = useNavigate(); @@ -24,10 +34,22 @@ export const SnapshotCreatePage = observer(() => { }, []); const [name, setName] = useState(""); + const [nameError, setNameError] = useState(null); const [isLoading, setIsLoading] = useState(false); const [duplicateWarningOpen, setDuplicateWarningOpen] = useState(false); const [duplicateRouteNumbers, setDuplicateRouteNumbers] = useState([]); + const exportNameRegex = useMemo(() => { + const names = cityStore.cities["ru"].data.map((c) => c.name.trim()); + return buildExportNameRegex(names); + }, [cityStore.cities["ru"].data.length]); + + useEffect(() => { + if (!cityStore.cities["ru"].loaded) { + cityStore.getCities("ru"); + } + }, []); + const canReadRoutes = authStore.canRead("routes"); const startExport = async () => { @@ -115,7 +137,19 @@ export const SnapshotCreatePage = observer(() => { label="Название" required value={name} - onChange={(e) => setName(e.target.value)} + error={!!nameError} + helperText={nameError ?? " "} + onChange={(e) => { + const val = e.target.value; + setName(val); + const trimmed = val.trim(); + const hasFullFormat = trimmed.includes("_") && trimmed.split("_").slice(1).join("_").length > 0; + if (hasFullFormat && !exportNameRegex.test(trimmed)) { + setNameError("Название должно начинаться с названия существующего города"); + } else { + setNameError(null); + } + }} />