diff --git a/src/client/src/components/widgets/AppealWidget.jsx b/src/client/src/components/widgets/AppealWidget.jsx
index d060c08..497e9fa 100644
--- a/src/client/src/components/widgets/AppealWidget.jsx
+++ b/src/client/src/components/widgets/AppealWidget.jsx
@@ -1,31 +1,47 @@
-import { useRef, useEffect } from 'react'
-import '../../styles/AppealWidget.css'
-import { TouchableLayout } from '../TouchableLayout'
+import { useRef, useEffect } from "react";
+import "../../styles/AppealWidget.css";
+import { TouchableLayout } from "../TouchableLayout";
-function AppealWidget({widgetImgPath, widgetLabel, widgetText, style, isOpen}) {
- const stopProp = (e) => { e.stopPropagation(); e.preventDefault(); };
- const layoutRef = useRef(null);
+function AppealWidget({
+ widgetImgPath,
+ widgetLabel,
+ widgetText,
+ style,
+ isOpen,
+}) {
+ const stopProp = (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ };
+ const layoutRef = useRef(null);
- useEffect(() => {
- if (isOpen && layoutRef.current) {
- const scrollable = layoutRef.current.querySelector('.scrollable');
- if (scrollable) scrollable.scrollTop = 0;
- }
- }, [isOpen]);
+ useEffect(() => {
+ if (isOpen && layoutRef.current) {
+ const scrollable = layoutRef.current.querySelector(".scrollable");
+ if (scrollable) scrollable.scrollTop = 0;
+ }
+ }, [isOpen]);
- return (
-
- {widgetImgPath &&

}
-
{widgetLabel}
-
- {widgetText}
-
-
- );
+ return (
+
+ {widgetImgPath && (
+

+ )}
+
{widgetLabel}
+
+ {widgetText}
+
+
+ );
}
-export default AppealWidget
+export default AppealWidget;
diff --git a/src/client/src/styles/AppealWidget.css b/src/client/src/styles/AppealWidget.css
index e750481..3ed9884 100644
--- a/src/client/src/styles/AppealWidget.css
+++ b/src/client/src/styles/AppealWidget.css
@@ -5,7 +5,7 @@
flex-direction: column;
align-items: center;
width: 420px;
- max-height: calc(100vh - 150px - 100px);
+ max-height: calc(100vh - 150px - 98px);
border-radius: 10px;
background:
linear-gradient(
@@ -13,49 +13,61 @@
rgba(255, 255, 255, 0) 8.71%,
rgba(255, 255, 255, 0.16) 69.69%
),
- var(--carrier-left, #006F3A);
+ var(--carrier-left, #006f3a);
box-sizing: border-box;
overflow: hidden;
touch-action: none;
}
.dynamic-widget-image {
- padding-top: 4px;
- margin-left: 4px;
- margin-right: 4px;
- width: 412px;
- border-radius: 6px 6px 0 0;
+ padding-top: 2px;
+ margin-left: 2px;
+ margin-right: 2px;
+ width: 416px;
+ border-radius: 10px 10px 0 0;
object-fit: cover;
}
.dynamic-widget-label {
- width: 380px;
- margin-top: 20px;
- margin-bottom: 5px;
+ width: 100%;
+ padding: 10px 20px;
+ box-sizing: border-box;
font-size: 20px;
font-weight: 500;
+ color: #fff;
+ border-bottom: 1px solid var(--Glass-stroke, rgba(255, 255, 255, 0.8));
+ background:
+ linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0.2) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ rgba(var(--carrier-right-menu-rgb, 179, 165, 152), 0.4);
}
-.dynamic-widget-text-scroll {
- margin-top: 0;
- margin-bottom: 25px;
+.dynamic-widget-text-scroll.scrollable-container {
+ flex: 1;
+ min-height: 0;
+ align-self: stretch;
+ margin: 15px 20px;
+ overflow: hidden;
+}
+
+.dynamic-widget-text-scroll .scrollable-viewport {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.dynamic-widget-text-scroll .scrollable {
flex: 1;
min-height: 0;
}
-.dynamic-widget-text-scroll.scrollable-container {
- margin: 0 20px;
-}
-
-.dynamic-widget-text-scroll .scrollable-viewport {
- padding-bottom: 20px;
- box-sizing: border-box;
-}
.dynamic-widget-text {
font-size: 14px;
font-weight: 400;
line-height: 190%;
- padding-bottom: 10px;
padding-right: 5px;
}
diff --git a/src/pages/Snapshot/SnapshotCreatePage/index.tsx b/src/pages/Snapshot/SnapshotCreatePage/index.tsx
index d3312e1..711c0df 100644
--- a/src/pages/Snapshot/SnapshotCreatePage/index.tsx
+++ b/src/pages/Snapshot/SnapshotCreatePage/index.tsx
@@ -1,5 +1,12 @@
-import { Button, TextField } from "@mui/material";
-import { snapshotStore } from "@shared";
+import {
+ Button,
+ TextField,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+} from "@mui/material";
+import { snapshotStore, authStore, routeStore } from "@shared";
import { observer } from "mobx-react-lite";
import { ArrowLeft, Loader2, Save } from "lucide-react";
import { useState } from "react";
@@ -12,6 +19,76 @@ export const SnapshotCreatePage = observer(() => {
const navigate = useNavigate();
const [name, setName] = useState("");
const [isLoading, setIsLoading] = useState(false);
+ const [duplicateWarningOpen, setDuplicateWarningOpen] = useState(false);
+ const [duplicateRouteNumbers, setDuplicateRouteNumbers] = useState([]);
+
+ const canReadRoutes = authStore.canRead("routes");
+
+ const startExport = async () => {
+ try {
+ setIsLoading(true);
+ const id = await createSnapshot(name);
+
+ await getSnapshotStatus(id);
+
+ while (snapshotStore.snapshotStatus?.Status != "done") {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ await getSnapshotStatus(id);
+ }
+
+ if (snapshotStore.snapshotStatus?.Status === "done") {
+ toast.success("Экспорт медиа успешно создан");
+
+ runInAction(() => {
+ snapshotStore.snapshotStatus = null;
+ });
+
+ await getStorageInfo();
+ navigate(-1);
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Ошибка при создании экспорта медиа");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSave = async () => {
+ if (!canReadRoutes) {
+ await startExport();
+ return;
+ }
+
+ try {
+ runInAction(() => {
+ routeStore.routes.loaded = false;
+ });
+ await routeStore.getRoutes();
+
+ const routes = routeStore.routes.data;
+ const numberCount = new Map();
+ for (const route of routes) {
+ const num = (route.route_number ?? "").trim();
+ if (num) {
+ numberCount.set(num, (numberCount.get(num) ?? 0) + 1);
+ }
+ }
+
+ const duplicates = Array.from(numberCount.entries())
+ .filter(([, count]) => count > 1)
+ .map(([num]) => num);
+
+ if (duplicates.length > 0) {
+ setDuplicateRouteNumbers(duplicates);
+ setDuplicateWarningOpen(true);
+ } else {
+ await startExport();
+ }
+ } catch {
+ await startExport();
+ }
+ };
return (
@@ -40,35 +117,7 @@ export const SnapshotCreatePage = observer(() => {
color="primary"
className="w-min flex gap-2 items-center"
startIcon={}
- onClick={async () => {
- try {
- setIsLoading(true);
- const id = await createSnapshot(name);
-
- await getSnapshotStatus(id);
-
- while (snapshotStore.snapshotStatus?.Status != "done") {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- await getSnapshotStatus(id);
- }
-
- if (snapshotStore.snapshotStatus?.Status === "done") {
- toast.success("Экспорт медиа успешно создан");
-
- runInAction(() => {
- snapshotStore.snapshotStatus = null;
- });
-
- await getStorageInfo();
- navigate(-1);
- }
- } catch (error) {
- console.error(error);
- toast.error("Ошибка при создании экспорта медиа");
- } finally {
- setIsLoading(false);
- }
- }}
+ onClick={handleSave}
disabled={isLoading || !name.trim()}
>
{isLoading ? (
@@ -87,6 +136,47 @@ export const SnapshotCreatePage = observer(() => {
+
+
);
});