feat: add city name in snaphost name
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [duplicateWarningOpen, setDuplicateWarningOpen] = useState(false);
|
||||
const [duplicateRouteNumbers, setDuplicateRouteNumbers] = useState<string[]>([]);
|
||||
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
@@ -124,7 +158,7 @@ export const SnapshotCreatePage = observer(() => {
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleSave}
|
||||
disabled={isLoading || !name.trim()}
|
||||
disabled={isLoading || !exportNameRegex.test(name.trim())}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, languageStore, snapshotStore, SearchInput } from "@shared";
|
||||
import { authStore, languageStore, snapshotStore, cityStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DatabaseBackup, Trash2 } from "lucide-react";
|
||||
@@ -9,6 +9,16 @@ import { Alert, Box, Button, CircularProgress, Dialog, DialogActions, DialogCont
|
||||
|
||||
const LOW_STORAGE_THRESHOLD_GB = 10;
|
||||
|
||||
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})_.+$`);
|
||||
}
|
||||
|
||||
const SEGMENT_COLORS = [
|
||||
"#FF3B30",
|
||||
"#FF9500",
|
||||
@@ -45,6 +55,12 @@ export const SnapshotListPage = observer(() => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isEmptySnapshotModalOpen, setIsEmptySnapshotModalOpen] = useState(false);
|
||||
const [emptySnapshotName, setEmptySnapshotName] = useState("");
|
||||
const [emptySnapshotNameError, setEmptySnapshotNameError] = useState<string | null>(null);
|
||||
|
||||
const exportNameRegex = useMemo(() => {
|
||||
const names = cityStore.cities["ru"].data.map((c) => c.name.trim());
|
||||
return buildExportNameRegex(names);
|
||||
}, [cityStore.cities["ru"].data.length]);
|
||||
const [isCreatingEmpty, setIsCreatingEmpty] = useState(false);
|
||||
const [paginationModel, setPaginationModel] = useState({
|
||||
page: 0,
|
||||
@@ -201,6 +217,7 @@ export const SnapshotListPage = observer(() => {
|
||||
disabled={isLowStorage}
|
||||
onClick={() => {
|
||||
setEmptySnapshotName("");
|
||||
setEmptySnapshotNameError(null);
|
||||
setIsEmptySnapshotModalOpen(true);
|
||||
}}
|
||||
>
|
||||
@@ -353,7 +370,19 @@ export const SnapshotListPage = observer(() => {
|
||||
fullWidth
|
||||
label="Название"
|
||||
value={emptySnapshotName}
|
||||
onChange={(e) => setEmptySnapshotName(e.target.value)}
|
||||
error={!!emptySnapshotNameError}
|
||||
helperText={emptySnapshotNameError ?? " "}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setEmptySnapshotName(val);
|
||||
const trimmed = val.trim();
|
||||
const hasFullFormat = trimmed.includes("_") && trimmed.split("_").slice(1).join("_").length > 0;
|
||||
if (hasFullFormat && !exportNameRegex.test(trimmed)) {
|
||||
setEmptySnapshotNameError("Название должно начинаться с названия существующего города");
|
||||
} else {
|
||||
setEmptySnapshotNameError(null);
|
||||
}
|
||||
}}
|
||||
margin="normal"
|
||||
/>
|
||||
</DialogContent>
|
||||
@@ -363,7 +392,7 @@ export const SnapshotListPage = observer(() => {
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!emptySnapshotName.trim() || isCreatingEmpty}
|
||||
disabled={!exportNameRegex.test(emptySnapshotName.trim()) || isCreatingEmpty}
|
||||
onClick={async () => {
|
||||
setIsCreatingEmpty(true);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user