feat: add city name in snaphost name
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
countryStore,
|
countryStore,
|
||||||
languageStore,
|
languageStore,
|
||||||
mediaStore,
|
mediaStore,
|
||||||
|
snapshotStore,
|
||||||
isMediaIdEmpty,
|
isMediaIdEmpty,
|
||||||
SelectMediaDialog,
|
SelectMediaDialog,
|
||||||
UploadMediaDialog,
|
UploadMediaDialog,
|
||||||
@@ -60,7 +61,13 @@ export const CityCreatePage = observer(() => {
|
|||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
const ruCityName = createCityData.ru.name.trim();
|
||||||
await cityStore.createCity();
|
await cityStore.createCity();
|
||||||
|
try {
|
||||||
|
await snapshotStore.createEmptySnapshot(`${ruCityName}_Пустой_Экспорт`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to create empty snapshot for city:", e);
|
||||||
|
}
|
||||||
toast.success("Город успешно создан");
|
toast.success("Город успешно создан");
|
||||||
navigate("/city");
|
navigate("/city");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,14 +6,24 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
} from "@mui/material";
|
} 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 { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
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 { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { runInAction } from "mobx";
|
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(() => {
|
export const SnapshotCreatePage = observer(() => {
|
||||||
const { createSnapshot, getSnapshotStatus, getStorageInfo, snapshotStatus } = snapshotStore;
|
const { createSnapshot, getSnapshotStatus, getStorageInfo, snapshotStatus } = snapshotStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -24,10 +34,22 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
const [nameError, setNameError] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [duplicateWarningOpen, setDuplicateWarningOpen] = useState(false);
|
const [duplicateWarningOpen, setDuplicateWarningOpen] = useState(false);
|
||||||
const [duplicateRouteNumbers, setDuplicateRouteNumbers] = useState<string[]>([]);
|
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 canReadRoutes = authStore.canRead("routes");
|
||||||
|
|
||||||
const startExport = async () => {
|
const startExport = async () => {
|
||||||
@@ -115,7 +137,19 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
label="Название"
|
label="Название"
|
||||||
required
|
required
|
||||||
value={name}
|
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
|
<Button
|
||||||
@@ -124,7 +158,7 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
className="w-min flex gap-2 items-center"
|
className="w-min flex gap-2 items-center"
|
||||||
startIcon={<Save size={20} />}
|
startIcon={<Save size={20} />}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isLoading || !name.trim()}
|
disabled={isLoading || !exportNameRegex.test(name.trim())}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||||
import { ruRU } from "@mui/x-data-grid/locales";
|
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 { useEffect, useState, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { DatabaseBackup, Trash2 } from "lucide-react";
|
import { DatabaseBackup, Trash2 } from "lucide-react";
|
||||||
@@ -9,6 +9,16 @@ import { Alert, Box, Button, CircularProgress, Dialog, DialogActions, DialogCont
|
|||||||
|
|
||||||
const LOW_STORAGE_THRESHOLD_GB = 10;
|
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 = [
|
const SEGMENT_COLORS = [
|
||||||
"#FF3B30",
|
"#FF3B30",
|
||||||
"#FF9500",
|
"#FF9500",
|
||||||
@@ -45,6 +55,12 @@ export const SnapshotListPage = observer(() => {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [isEmptySnapshotModalOpen, setIsEmptySnapshotModalOpen] = useState(false);
|
const [isEmptySnapshotModalOpen, setIsEmptySnapshotModalOpen] = useState(false);
|
||||||
const [emptySnapshotName, setEmptySnapshotName] = useState("");
|
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 [isCreatingEmpty, setIsCreatingEmpty] = useState(false);
|
||||||
const [paginationModel, setPaginationModel] = useState({
|
const [paginationModel, setPaginationModel] = useState({
|
||||||
page: 0,
|
page: 0,
|
||||||
@@ -201,6 +217,7 @@ export const SnapshotListPage = observer(() => {
|
|||||||
disabled={isLowStorage}
|
disabled={isLowStorage}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEmptySnapshotName("");
|
setEmptySnapshotName("");
|
||||||
|
setEmptySnapshotNameError(null);
|
||||||
setIsEmptySnapshotModalOpen(true);
|
setIsEmptySnapshotModalOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -353,7 +370,19 @@ export const SnapshotListPage = observer(() => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
label="Название"
|
label="Название"
|
||||||
value={emptySnapshotName}
|
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"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -363,7 +392,7 @@ export const SnapshotListPage = observer(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!emptySnapshotName.trim() || isCreatingEmpty}
|
disabled={!exportNameRegex.test(emptySnapshotName.trim()) || isCreatingEmpty}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsCreatingEmpty(true);
|
setIsCreatingEmpty(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user