Update nút mở drawer add/edit scenario

This commit is contained in:
Truong Vo 2025-11-26 16:49:20 +07:00
parent df25b5db3f
commit 065900f48b
3 changed files with 175 additions and 82 deletions

View File

@ -583,6 +583,7 @@ function App() {
setIsLogModalOpen={setIsLogModalOpen}
setTestLogContent={setTestLogContent}
scenarios={scenarios}
setScenarios={setScenarios}
setExpanded={setExpandedBottomBar}
activeTabBottom={activeTabBottom}
setActiveTabBottom={setActiveTabBottom}

View File

@ -19,9 +19,10 @@ import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./DrawerLogs";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
import DrawerScenario from "./DrawerScenario";
import { isJsonString } from "../untils/helper";
import { motion } from "motion/react";
import { IconCaretDown, IconCaretUp, IconPlayerPlay } from "@tabler/icons-react";
import { IconCaretDown, IconCaretUp, IconPlayerPlay, IconPlus } from "@tabler/icons-react";
interface TabsProps {
selectedLines: TLine[];
@ -35,6 +36,7 @@ interface TabsProps {
setIsLogModalOpen: (value: React.SetStateAction<boolean>) => void;
setTestLogContent: (value: React.SetStateAction<string>) => void;
scenarios: IScenario[];
setScenarios: (value: React.SetStateAction<IScenario[]>) => void;
setExpanded: (value: React.SetStateAction<boolean>) => void;
activeTabBottom: string;
setActiveTabBottom: (value: React.SetStateAction<string>) => void;
@ -53,6 +55,7 @@ const BottomToolBar = ({
setIsLogModalOpen,
setTestLogContent,
scenarios,
setScenarios,
setExpanded,
setActiveTabBottom,
activeTabBottom,
@ -67,6 +70,7 @@ const BottomToolBar = ({
const [valueInput, setValueInput] = useState<string>("");
const inputRef = useRef<HTMLInputElement>(null);
const [openScenarioModal, setOpenScenarioModal] = useState<boolean>(false);
const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false);
// const [activeTabBottom, setActiveTabBottom] = useState<string>("command");
// const [isExpand, setIsExpand] = useState<boolean>(true);
@ -123,10 +127,26 @@ const BottomToolBar = ({
<Text fw={700} size="xl">
🎯 Select Scenario to Run
</Text>
<CloseButton
size="lg"
onClick={() => setOpenScenarioModal(false)}
/>
<Flex gap="md" align="center">
<Button
leftSection={<IconPlus size={16} />}
variant="light"
color="green"
size="sm"
onClick={() => {
setOpenScenarioModal(false);
setTimeout(() => {
setOpenDrawerScenario(true);
}, 100);
}}
>
Add/Edit Scenario
</Button>
<CloseButton
size="lg"
onClick={() => setOpenScenarioModal(false)}
/>
</Flex>
</Flex>
{/* Content */}
@ -542,6 +562,14 @@ const BottomToolBar = ({
</Grid>
</Box>
</motion.div>
{/* Drawer Scenario để Add/Edit */}
<DrawerScenario
scenarios={scenarios}
setScenarios={setScenarios}
externalOpened={openDrawerScenario}
onExternalClose={() => setOpenDrawerScenario(false)}
/>
</>
);
};

View File

@ -1,22 +1,21 @@
import { useDisclosure } from "@mantine/hooks";
import {
Drawer,
Box,
ScrollArea,
Table,
Grid,
TextInput,
Button,
Checkbox,
Text,
Flex,
CloseButton,
} from "@mantine/core";
import { IconSettingsPlus } from "@tabler/icons-react";
import classes from "./Component.module.css";
import TableRows from "./Scenario/TableRows";
import { useEffect, useState } from "react";
import { useForm } from "@mantine/form";
import DialogConfirm from "./DialogConfirm";
import type { IBodyScenario, IScenario } from "../untils/types";
import classes from "./Component.module.css";
import axios from "axios";
import { notifications } from "@mantine/notifications";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
@ -24,11 +23,26 @@ const apiUrl = import.meta.env.VITE_BACKEND_URL;
function DrawerScenario({
scenarios,
setScenarios,
externalOpened,
onExternalClose,
}: {
scenarios: IScenario[];
setScenarios: (value: React.SetStateAction<IScenario[]>) => void;
externalOpened?: boolean;
onExternalClose?: () => void;
}) {
const [opened, { open, close }] = useDisclosure(false);
const [opened, { close }] = useDisclosure(false);
// Sử dụng external state nếu được provide, nếu không thì dùng internal state
const isOpened = externalOpened !== undefined ? externalOpened : opened;
const handleClose = () => {
if (onExternalClose) {
onExternalClose();
} else {
close();
}
};
const [isEdit, setIsEdit] = useState(false);
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
const [isSubmit, setIsSubmit] = useState<boolean>(false);
@ -178,62 +192,125 @@ function DrawerScenario({
};
useEffect(() => {
if (!opened) {
if (!isOpened) {
setIsEdit(false);
setIsSubmit(false);
setDataScenario(undefined);
form.reset();
}
}, [opened]);
}, [isOpened]);
return (
<>
<Drawer
size={"70%"}
position="right"
style={{ position: "absolute", left: 0 }}
offset={8}
radius="md"
opened={opened}
onClose={close}
title={isEdit ? "Edit Scenarios" : "Add Scenarios"}
>
<Grid>
<Grid.Col span={2} style={{ borderRight: "1px solid #ccc" }}>
{scenarios.map((scenario) => (
<Button
disabled={isSubmit}
className={classes.buttonScenario}
key={scenario.id}
miw={"100px"}
mb={"6px"}
style={{ minHeight: "24px" }}
mr={"5px"}
variant={
dataScenario && dataScenario?.id === scenario.id
? "filled"
: "outline"
}
onClick={async () => {
if (dataScenario?.id === scenario.id) {
setIsEdit(false);
setDataScenario(undefined);
form.reset();
} else {
setIsEdit(true);
setDataScenario(scenario);
form.setFieldValue("title", scenario.title);
form.setFieldValue("timeout", scenario.timeout.toString());
form.setFieldValue("body", JSON.parse(scenario.body));
form.setFieldValue("isReboot", scenario.isReboot);
}
}}
>
{scenario.title}
</Button>
))}
</Grid.Col>
<Grid.Col span={10}>
{/* Custom Modal - Giống như Modal Select Scenario */}
{isOpened && (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.6)",
zIndex: 100000,
display: "flex",
alignItems: "center",
justifyContent: "center",
backdropFilter: "blur(3px)",
}}
onClick={handleClose}
>
<div
style={{
background: "white",
borderRadius: "12px",
maxWidth: "90vw",
width: "90%",
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">
{isEdit ? "✏️ Edit Scenarios" : " Add Scenarios"}
</Text>
<CloseButton size="lg" onClick={handleClose} />
</Flex>
{/* Content */}
<div
style={{
padding: "20px",
overflowY: "auto",
overflowX: "hidden",
flex: 1,
}}
className={classes.hideScrollBar}
>
<Flex gap="md" style={{ height: "75vh" }}>
{/* Sidebar - List Scenarios */}
<Box
style={{
width: "200px",
borderRight: "1px solid #e9ecef",
paddingRight: "10px",
flexShrink: 0,
}}
>
<ScrollArea h="100%">
<Flex direction="column" gap="xs">
{scenarios.map((scenario) => (
<Button
disabled={isSubmit}
className={classes.buttonScenario}
key={scenario.id}
fullWidth
style={{ minHeight: "36px" }}
variant={
dataScenario && dataScenario?.id === scenario.id
? "filled"
: "outline"
}
onClick={async () => {
if (dataScenario?.id === scenario.id) {
setIsEdit(false);
setDataScenario(undefined);
form.reset();
} else {
setIsEdit(true);
setDataScenario(scenario);
form.setFieldValue("title", scenario.title);
form.setFieldValue(
"timeout",
scenario.timeout.toString()
);
form.setFieldValue("body", JSON.parse(scenario.body));
form.setFieldValue("isReboot", scenario.isReboot);
}
}}
>
{scenario.title}
</Button>
))}
</Flex>
</ScrollArea>
</Box>
{/* Main Content */}
<Box style={{ flex: 1, overflow: "hidden" }}>
<Box>
<Grid>
<Grid.Col span={4}>
@ -318,7 +395,10 @@ function DrawerScenario({
</Box>
<hr style={{ width: "100%" }} />
<Box>
<ScrollArea h={"70vh"} style={{ marginBottom: "20px" }}>
<ScrollArea
h={"calc(75vh - 150px)"}
style={{ marginBottom: "20px" }}
>
<Table
stickyHeader
stickyHeaderOffset={-1}
@ -358,28 +438,12 @@ function DrawerScenario({
</Table>
</ScrollArea>
</Box>
</Grid.Col>
</Grid>
</Drawer>
<Text
fw={700}
c={"#747474"}
style={{
fontSize: "14px",
display: "flex",
alignItems: "center",
gap: "6px",
}}
>
Scenarios
<IconSettingsPlus
color="green"
style={{ cursor: "pointer", width: "18px", height: "18px" }}
onClick={() => {
open();
}}
/>
</Text>
</Box>
</Flex>
</div>
</div>
</div>
)}
<DialogConfirm
opened={openConfirm}
@ -390,7 +454,7 @@ function DrawerScenario({
handle={() => {
setOpenConfirm(false);
handleDelete();
close();
handleClose();
}}
centered={true}
/>