feat: update appeal widget and check double routes for create snapshot
This commit is contained in:
@@ -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(); };
|
||||
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');
|
||||
const scrollable = layoutRef.current.querySelector(".scrollable");
|
||||
if (scrollable) scrollable.scrollTop = 0;
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<div style={style} className='dynamic-widget'
|
||||
<div
|
||||
style={style}
|
||||
className="dynamic-widget"
|
||||
onPointerDown={stopProp}
|
||||
onPointerMove={stopProp}
|
||||
onPointerUp={stopProp}
|
||||
>
|
||||
{widgetImgPath && <img className='dynamic-widget-image' src={widgetImgPath} />}
|
||||
<div className='dynamic-widget-label'>{widgetLabel}</div>
|
||||
<TouchableLayout ref={layoutRef} className="dynamic-widget-text-scroll" maxHeight="calc(100vh - 150px - 100px - 300px)">
|
||||
<div className='dynamic-widget-text'>{widgetText}</div>
|
||||
{widgetImgPath && (
|
||||
<img className="dynamic-widget-image" src={widgetImgPath} />
|
||||
)}
|
||||
<div className="dynamic-widget-label">{widgetLabel}</div>
|
||||
<TouchableLayout
|
||||
ref={layoutRef}
|
||||
className="dynamic-widget-text-scroll"
|
||||
>
|
||||
<div className="dynamic-widget-text">{widgetText}</div>
|
||||
</TouchableLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppealWidget
|
||||
export default AppealWidget;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<string[]>([]);
|
||||
|
||||
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<string, number>();
|
||||
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 (
|
||||
<div className="w-full h-[400px] flex justify-center items-center">
|
||||
@@ -40,35 +117,7 @@ export const SnapshotCreatePage = observer(() => {
|
||||
color="primary"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
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(() => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
open={duplicateWarningOpen}
|
||||
onClose={() => !isLoading && setDuplicateWarningOpen(false)}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Найдены повторяющиеся маршруты</DialogTitle>
|
||||
<DialogContent>
|
||||
<p className="mb-3">
|
||||
Обнаружены маршруты с одинаковыми номерами. Это может привести к
|
||||
некорректным данным в экспорте.
|
||||
</p>
|
||||
<ul className="list-disc pl-5">
|
||||
{duplicateRouteNumbers.map((num) => (
|
||||
<li key={num}>
|
||||
Найдены повторяющиеся маршруты под номером №{num}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setDuplicateWarningOpen(false)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ backgroundColor: "#795548", "&:hover": { backgroundColor: "#5D4037" } }}
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
setDuplicateWarningOpen(false);
|
||||
await startExport();
|
||||
}}
|
||||
>
|
||||
Продолжить экспорт
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user