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(() => {
+ + !isLoading && setDuplicateWarningOpen(false)} + maxWidth="sm" + fullWidth + > + Найдены повторяющиеся маршруты + +

+ Обнаружены маршруты с одинаковыми номерами. Это может привести к + некорректным данным в экспорте. +

+
    + {duplicateRouteNumbers.map((num) => ( +
  • + Найдены повторяющиеся маршруты под номером №{num} +
  • + ))} +
+
+ + + + +
); });