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 { useRef, useEffect } from "react";
|
||||||
import '../../styles/AppealWidget.css'
|
import "../../styles/AppealWidget.css";
|
||||||
import { TouchableLayout } from '../TouchableLayout'
|
import { TouchableLayout } from "../TouchableLayout";
|
||||||
|
|
||||||
function AppealWidget({widgetImgPath, widgetLabel, widgetText, style, isOpen}) {
|
function AppealWidget({
|
||||||
const stopProp = (e) => { e.stopPropagation(); e.preventDefault(); };
|
widgetImgPath,
|
||||||
const layoutRef = useRef(null);
|
widgetLabel,
|
||||||
|
widgetText,
|
||||||
|
style,
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
const stopProp = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
const layoutRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && layoutRef.current) {
|
if (isOpen && layoutRef.current) {
|
||||||
const scrollable = layoutRef.current.querySelector('.scrollable');
|
const scrollable = layoutRef.current.querySelector(".scrollable");
|
||||||
if (scrollable) scrollable.scrollTop = 0;
|
if (scrollable) scrollable.scrollTop = 0;
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className='dynamic-widget'
|
<div
|
||||||
onPointerDown={stopProp}
|
style={style}
|
||||||
onPointerMove={stopProp}
|
className="dynamic-widget"
|
||||||
onPointerUp={stopProp}
|
onPointerDown={stopProp}
|
||||||
>
|
onPointerMove={stopProp}
|
||||||
{widgetImgPath && <img className='dynamic-widget-image' src={widgetImgPath} />}
|
onPointerUp={stopProp}
|
||||||
<div className='dynamic-widget-label'>{widgetLabel}</div>
|
>
|
||||||
<TouchableLayout ref={layoutRef} className="dynamic-widget-text-scroll" maxHeight="calc(100vh - 150px - 100px - 300px)">
|
{widgetImgPath && (
|
||||||
<div className='dynamic-widget-text'>{widgetText}</div>
|
<img className="dynamic-widget-image" src={widgetImgPath} />
|
||||||
</TouchableLayout>
|
)}
|
||||||
</div>
|
<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;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 420px;
|
width: 420px;
|
||||||
max-height: calc(100vh - 150px - 100px);
|
max-height: calc(100vh - 150px - 98px);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background:
|
background:
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
@@ -13,49 +13,61 @@
|
|||||||
rgba(255, 255, 255, 0) 8.71%,
|
rgba(255, 255, 255, 0) 8.71%,
|
||||||
rgba(255, 255, 255, 0.16) 69.69%
|
rgba(255, 255, 255, 0.16) 69.69%
|
||||||
),
|
),
|
||||||
var(--carrier-left, #006F3A);
|
var(--carrier-left, #006f3a);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dynamic-widget-image {
|
.dynamic-widget-image {
|
||||||
padding-top: 4px;
|
padding-top: 2px;
|
||||||
margin-left: 4px;
|
margin-left: 2px;
|
||||||
margin-right: 4px;
|
margin-right: 2px;
|
||||||
width: 412px;
|
width: 416px;
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dynamic-widget-label {
|
.dynamic-widget-label {
|
||||||
width: 380px;
|
width: 100%;
|
||||||
margin-top: 20px;
|
padding: 10px 20px;
|
||||||
margin-bottom: 5px;
|
box-sizing: border-box;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
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 {
|
.dynamic-widget-text-scroll.scrollable-container {
|
||||||
margin-top: 0;
|
flex: 1;
|
||||||
margin-bottom: 25px;
|
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;
|
flex: 1;
|
||||||
min-height: 0;
|
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 {
|
.dynamic-widget-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 190%;
|
line-height: 190%;
|
||||||
padding-bottom: 10px;
|
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { Button, TextField } from "@mui/material";
|
import {
|
||||||
import { snapshotStore } from "@shared";
|
Button,
|
||||||
|
TextField,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { snapshotStore, authStore, routeStore } 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 } from "react";
|
import { useState } from "react";
|
||||||
@@ -12,6 +19,76 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
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 (
|
return (
|
||||||
<div className="w-full h-[400px] flex justify-center items-center">
|
<div className="w-full h-[400px] flex justify-center items-center">
|
||||||
@@ -40,35 +117,7 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
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={async () => {
|
onClick={handleSave}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isLoading || !name.trim()}
|
disabled={isLoading || !name.trim()}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -87,6 +136,47 @@ export const SnapshotCreatePage = observer(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user