Update modal select scenario

This commit is contained in:
Truong Vo 2025-11-26 14:59:55 +07:00
parent 55bcc055ea
commit 6f785644be
2 changed files with 233 additions and 77 deletions

View File

@ -6,21 +6,22 @@ import {
Flex,
Grid,
Input,
Menu,
ScrollArea,
Tabs,
Text,
Card,
Badge,
} from "@mantine/core";
import { useEffect, useMemo, useRef, useState } from "react";
import classes from "./Component.module.css";
import type { IScenario, TLine, TStation } from "../untils/types";
import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonScenario, ButtonSelect } from "./ButtonAction";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./DrawerLogs";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
import { isJsonString } from "../untils/helper";
import { motion } from "motion/react";
import { IconCaretDown, IconCaretUp } from "@tabler/icons-react";
import { IconCaretDown, IconCaretUp, IconPlayerPlay } from "@tabler/icons-react";
interface TabsProps {
selectedLines: TLine[];
@ -65,6 +66,7 @@ const BottomToolBar = ({
}, []);
const [valueInput, setValueInput] = useState<string>("");
const inputRef = useRef<HTMLInputElement>(null);
const [openScenarioModal, setOpenScenarioModal] = useState<boolean>(false);
// const [activeTabBottom, setActiveTabBottom] = useState<string>("command");
// const [isExpand, setIsExpand] = useState<boolean>(true);
@ -75,21 +77,205 @@ const BottomToolBar = ({
}, [selectedLines?.length]);
return (
<motion.div
initial={false}
animate={{
height: isExpand ? "20vh" : 0,
y: 0, // đẩy xuống khi thu nhỏ
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
style={{
width: "100%",
position: "fixed",
bottom: 0,
left: 0,
zIndex: 1,
}}
>
<>
{/* Modal chọn Scenario - Custom Simple Modal */}
{openScenarioModal && (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.6)",
zIndex: 99998,
display: "flex",
alignItems: "center",
justifyContent: "center",
backdropFilter: "blur(3px)",
}}
onClick={() => setOpenScenarioModal(false)}
>
<div
style={{
background: "white",
borderRadius: "12px",
maxWidth: "1000px",
width: "auto",
maxHeight: "85vh",
display: "flex",
flexDirection: "column",
boxShadow: "0 20px 60px rgba(0,0,0,0.3)",
overflow: "hidden",
}}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<Flex
justify="space-between"
align="center"
p="lg"
style={{
borderBottom: "1px solid #e9ecef",
flexShrink: 0,
}}
>
<Text fw={700} size="xl">
🎯 Select Scenario to Run
</Text>
<CloseButton
size="lg"
onClick={() => setOpenScenarioModal(false)}
/>
</Flex>
{/* Content */}
<div
style={{
padding: "20px",
overflowY: "auto",
overflowX: "hidden",
flex: 1,
}}
className={classes.hideScrollBar}
>
{scenarios.length > 0 ? (
<Grid gutter="md" style={{ margin: 0 }}>
{scenarios.map((scenario, index) => (
<Grid.Col key={scenario.id} span={6}>
<Card
shadow="sm"
padding="lg"
radius="md"
withBorder
style={{
cursor: "pointer",
transition: "all 0.2s ease",
height: "100%",
}}
className={classes.scenarioCard}
>
<Flex direction="column" gap="xs" h="100%">
<Flex justify="space-between" align="center">
<Text fw={600} size="md" lineClamp={1}>
{scenario.title}
</Text>
<Badge color="blue" variant="light">
#{index + 1}
</Badge>
</Flex>
<Flex gap="xs" wrap="wrap">
<Badge size="sm" color="gray" variant="dot">
Timeout: {scenario.timeout}ms
</Badge>
{scenario.isReboot && (
<Badge size="sm" color="orange" variant="dot">
Reboot
</Badge>
)}
<Badge size="sm" color="green" variant="dot">
{JSON.parse(scenario.body || "[]").length} steps
</Badge>
</Flex>
<Text
size="xs"
c="dimmed"
lineClamp={2}
style={{ flex: 1 }}
>
{JSON.parse(scenario.body || "[]")
.slice(0, 2)
.map((step: { send: string }) => step.send)
.join(" → ")}
{JSON.parse(scenario.body || "[]").length > 2 &&
" ..."}
</Text>
<Button
fullWidth
variant="light"
color="green"
leftSection={<IconPlayerPlay size={16} />}
disabled={
isDisable ||
selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
).length === 0
}
onClick={() => {
setOpenScenarioModal(false);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
// Run scenario cho các line được chọn
selectedLines
.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
)
.forEach((el) => {
socket?.emit(
"run_scenario",
Object.assign(el, {
scenario: scenario,
})
);
});
}}
>
Run Scenario
</Button>
</Flex>
</Card>
</Grid.Col>
))}
</Grid>
) : (
<Flex
direction="column"
align="center"
justify="center"
style={{ minHeight: "300px" }}
gap="md"
>
<Text size="xl" c="dimmed">
📋
</Text>
<Text ta="center" c="dimmed" size="lg">
No scenarios available
</Text>
<Text ta="center" c="dimmed" size="sm">
Please create a new scenario to get started
</Text>
</Flex>
)}
</div>
</div>
</div>
)}
<motion.div
initial={false}
animate={{
height: isExpand ? "20vh" : 0,
y: 0, // đẩy xuống khi thu nhỏ
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
style={{
width: "100%",
position: "fixed",
bottom: 0,
left: 0,
zIndex: 1,
}}
>
<Box style={{ position: "relative" }}>
<ActionIcon
style={{
@ -323,64 +509,16 @@ const BottomToolBar = ({
}, 5000);
}}
/>
<Menu
trigger="hover"
withArrow
shadow="md"
position="top"
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="yellow"
style={{ height: "30px", width: "100px" }}
onClick={() => setOpenScenarioModal(true)}
>
<Menu.Target>
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="yellow"
style={{ height: "30px", width: "100px" }}
onClick={() => {}}
>
Scenario
</Button>
</Menu.Target>
<Menu.Dropdown>
<Box
px="xs"
py="sm"
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
}}
>
{scenarios.map((el, i) => (
<ButtonScenario
key={i}
socket={socket}
selectedLines={selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
)}
isDisable={
isDisable ||
selectedLines.filter(
(el) =>
!el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
).length === 0
}
onClick={() => {
// setSelectedLines([]);
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
scenario={el}
/>
))}
</Box>
</Menu.Dropdown>
</Menu>
Scenario
</Button>
<DrawerLogs
socket={socket}
isLogModalOpen={isLogModalOpen}
@ -403,7 +541,8 @@ const BottomToolBar = ({
<Grid.Col span={1}></Grid.Col>
</Grid>
</Box>
</motion.div>
</motion.div>
</>
);
};

View File

@ -109,8 +109,13 @@
border-top: solid rgba(201, 201, 201, 0.377) 1px; */
}
.hideScrollBar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.hideScrollBar::-webkit-scrollbar {
display: none;
display: none; /* Chrome, Safari and Opera */
}
.containerBottom {
@ -148,3 +153,15 @@
padding-right: 4px !important;
padding-left: 4px !important;
}
.scenarioCard {
border: 2px solid transparent;
background: linear-gradient(white, white) padding-box,
linear-gradient(145deg, #e0e0e0, #f5f5f5) border-box;
}
.scenarioCard:hover {
border-color: #4dabf7;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}