update thanh bottom bar

This commit is contained in:
Truong Vo 2025-12-12 14:27:01 +07:00
parent 92cb59cebc
commit 8b986232e6
3 changed files with 359 additions and 354 deletions

View File

@ -496,6 +496,12 @@ function App() {
setSelectedLine(data);
};
useEffect(() => {
if(!expandedBottomBar){
setActiveTabBottom("command");
}
}, [expandedBottomBar]);
return (
<Container w={"100%"} style={{ maxWidth: "100%" }}>
<DraggableTabs
@ -526,7 +532,7 @@ function App() {
h={
expandedBottomBar
? activeTabBottom !== "switch"
? "75vh"
? "80vh"
: "70vh"
: "85vh"
}

View File

@ -347,134 +347,13 @@ const BottomToolBar = ({
return (
<>
{/* 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: "90vw",
width: "90%",
height: "85vh",
display: "flex",
flexDirection: "column",
boxShadow: "0 20px 60px rgba(0,0,0,0.3)",
overflowY: "auto",
overflowX: "visible",
}}
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>
<Flex gap="md" align="center">
<Button
leftSection={<IconPlus size={16} />}
variant="light"
color="green"
size="sm"
onClick={(e) => {
e.stopPropagation();
setOpenDrawerScenario(true);
}}
>
Add/Edit Scenario
</Button>
<CloseButton
size="lg"
onClick={() => setOpenScenarioModal(false)}
/>
</Flex>
</Flex>
{/* Content */}
<div
style={{
padding: "20px",
overflowY: "auto",
overflowX: "visible",
flex: 1,
position: "relative",
}}
className={classes.hideScrollBar}
>
{scenarios.length > 0 ? (
<Grid
gutter="md"
style={{
margin: 0,
overflow: "visible",
position: "relative",
}}
>
{scenarios.map((scenario, index) => (
<ScenarioCard
key={scenario.id}
scenario={scenario}
index={index}
isDisable={isDisable}
selectedLines={selectedLines}
user={user}
socket={socket}
setOpenScenarioModal={setOpenScenarioModal}
setIsDisable={setIsDisable}
/>
))}
</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 ? "auto" : 0,
y: 0, // đẩy xuống khi thu nhỏ
height: isExpand ? "auto" : "auto",
y: 0,
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
style={{
@ -483,7 +362,6 @@ const BottomToolBar = ({
bottom: 0,
left: 0,
zIndex: 1,
// overflow: "hidden",
}}
>
<Box style={{ position: "relative" }}>
@ -509,139 +387,359 @@ const BottomToolBar = ({
)}
</ActionIcon>
<Grid>
<Grid.Col span={1}></Grid.Col>
<Grid.Col span={10}>
<Tabs
defaultValue="command"
orientation="vertical"
value={activeTabBottom}
onChange={(val) => {
setActiveTabBottom(val || "command");
}}
className={classes.containerBottom}
>
<Tabs.List>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "command" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="command"
>
Command Line
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "apc" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="apc"
>
APC
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "switch" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="switch"
>
Switch
</Tabs.Tab>
</Tabs.List>
<Grid.Col span={isExpand ? 1 : 3.5}></Grid.Col>
<Grid.Col span={isExpand ? 10 : 5}>
{isExpand ? (
<Tabs
defaultValue="command"
orientation="vertical"
value={activeTabBottom}
onChange={(val) => {
setActiveTabBottom(val || "command");
}}
className={classes.containerBottom}
>
<Tabs.List>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "command" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="command"
>
Command Line
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "apc" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="apc"
>
APC
</Tabs.Tab>
<Tabs.Tab
style={{
backgroundColor:
activeTabBottom === "switch" ? "#c8d9fd" : "",
fontSize: "13px",
paddingTop: "8px",
paddingBottom: "8px",
}}
value="switch"
>
Switch
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="command" p={4}>
<Flex justify={"space-between"} align="flex-start">
<Box>
<ScrollArea h={"8vh"}>
<Flex wrap={"wrap"} gap={"8px"} w={"420px"}>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
position: "relative",
padding: "4px 6px",
height: "26px",
width: "60px",
backgroundColor: "#d4e3ff",
borderRadius: "8px",
}}
>
{/* Close button góc trên phải */}
<CloseButton
size="xs"
<Tabs.Panel value="command" p={4}>
<Flex justify={"space-between"} align="flex-start">
<Box>
<ScrollArea h={"8vh"}>
<Flex wrap={"wrap"} gap={"8px"} w={"420px"}>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
position: "absolute",
top: "-4px",
right: "-6px",
minWidth: "18px",
width: "18px",
height: "18px",
zIndex: 10,
position: "relative",
padding: "4px 6px",
height: "26px",
width: "60px",
backgroundColor: "#d4e3ff",
borderRadius: "8px",
}}
>
{/* Close button góc trên phải */}
<CloseButton
size="xs"
style={{
position: "absolute",
top: "-4px",
right: "-6px",
minWidth: "18px",
width: "18px",
height: "18px",
zIndex: 10,
}}
onClick={() => {
setSelectedLines(
selectedLines.filter(
(line) => line.id !== el.id
)
);
socket?.emit("close_cli", {
lineId: el?.id,
stationId: el.stationId || el.station_id,
});
}}
/>
<Flex
align={"center"}
justify={"center"}
h="100%"
>
<Text fz={"11px"}>Line {el.lineNumber}</Text>
</Flex>
</Box>
))}
{selectedLines.length > 0 && (
<Box
style={{
padding: "4px 10px",
height: "26px",
backgroundColor: "#ffe3e3",
borderRadius: "999px",
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
onClick={() => {
setSelectedLines(
selectedLines.filter(
(line) => line.id !== el.id
)
);
socket?.emit("close_cli", {
lineId: el?.id,
stationId: el.stationId || el.station_id,
selectedLines.forEach((line) => {
socket?.emit("close_cli", {
lineId: line?.id,
stationId:
line.stationId || line.station_id,
});
});
setSelectedLines([]);
}}
/>
<Flex
align={"center"}
justify={"center"}
h="100%"
>
<Text fz={"11px"}>Line {el.lineNumber}</Text>
</Flex>
</Box>
))}
{selectedLines.length > 0 && (
<Box
style={{
padding: "4px 10px",
height: "26px",
backgroundColor: "#ffe3e3",
borderRadius: "999px",
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
onClick={() => {
<Text fz={"11px"} c="red">
Clear
</Text>
</Box>
)}
</Flex>
</ScrollArea>
<Flex justify={"space-between"} align={"center"} mt={4}>
<Text fz={"11px"} c="dimmed">
Selected: {selectedLines.length} /{" "}
{station.lines.length}
</Text>
<ButtonSelect
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
station={station}
userName={user?.userName}
onClick={() => {
const lines = station.lines.filter(
(line) =>
!line?.userOpenCLI ||
line?.userOpenCLI === user?.userName
);
if (selectedLines.length !== lines.length) {
setSelectedLines(lines);
lines.forEach((line) => {
socket?.emit("open_cli", {
lineId: line.id,
stationId: line.stationId || line.station_id,
userEmail: user?.email,
userName: user?.userName,
});
});
} else {
selectedLines.forEach((line) => {
socket?.emit("close_cli", {
lineId: line?.id,
stationId:
line.stationId || line.station_id,
stationId: line.stationId || line.station_id,
});
});
setSelectedLines([]);
}}
>
<Text fz={"11px"} c="red">
Clear
</Text>
</Box>
)}
}
}}
/>
</Flex>
</ScrollArea>
<Flex justify={"space-between"} align={"center"} mt={4}>
<Text fz={"11px"} c="dimmed">
</Box>
<Box pl={"md"} pr={"md"}>
<Flex justify={"space-between"} mb={"xs"}>
<Flex></Flex>
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="orange"
size="xs"
radius="md"
onClick={() => {
const listLine = selectedLines.length
? selectedLines
: station?.lines;
if (listLine.length) {
socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id),
stationId: station.id,
command: "spam_break",
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}
}}
>
Send Break
</Button>
</Flex>
<Box>
<InputHistory
selectedLines={selectedLines}
socket={socket}
station={station}
/>
</Box>
</Box>
<Box style={{ width: "260px" }}>
<Flex
align={"center"}
justify={"flex-end"}
gap={"xs"}
wrap={"wrap"}
>
<ButtonDPELP
socket={socket}
selectedLines={selectedLines}
isDisable={isDisable || selectedLines.length === 0}
onClick={() => {
if (
selectedLines.length > 0
// &&
// selectedLines.length === station?.lines?.length
) {
socket?.emit("run_all_dpelp", {
lineIds: selectedLines.map((line) => line.id),
stationName: station.name,
stationId: station.id,
});
}
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
/>
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="yellow"
style={{ height: "30px", width: "100px" }}
onClick={() => setOpenScenarioModal(true)}
>
Scenario
</Button>
<DrawerLogs
socket={socket}
isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen}
testLogContent={testLogContent}
setTestLogContent={setTestLogContent}
/>
</Flex>
</Box>
</Flex>
</Tabs.Panel>
<Tabs.Panel value="apc" ps={"xs"}>
<DrawerAPCControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Tabs.Panel>
<Tabs.Panel value="switch" ps={"xs"}>
<DrawerSwitchControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Tabs.Panel>
</Tabs>
) : (
<Box p={3}>
<Flex direction="column" gap="xs">
<Flex justify={"space-between"} align={"center"} wrap="wrap" gap="xs">
<Flex wrap={"wrap"} gap={"6px"} style={{ flex: 1, minWidth: 0 }}>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
position: "relative",
padding: "3px 5px",
height: "22px",
width: "55px",
backgroundColor: "#d4e3ff",
borderRadius: "6px",
}}
>
<CloseButton
size="xs"
style={{
position: "absolute",
top: "-4px",
right: "-6px",
minWidth: "16px",
width: "16px",
height: "16px",
zIndex: 10,
}}
onClick={() => {
setSelectedLines(
selectedLines.filter(
(line) => line.id !== el.id
)
);
socket?.emit("close_cli", {
lineId: el?.id,
stationId: el.stationId || el.station_id,
});
}}
/>
<Flex
align={"center"}
justify={"center"}
h="100%"
>
<Text fz={"10px"}>Line {el.lineNumber}</Text>
</Flex>
</Box>
))}
{selectedLines.length > 0 && (
<Box
style={{
padding: "3px 8px",
height: "22px",
backgroundColor: "#ffe3e3",
borderRadius: "999px",
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
onClick={() => {
selectedLines.forEach((line) => {
socket?.emit("close_cli", {
lineId: line?.id,
stationId:
line.stationId || line.station_id,
});
});
setSelectedLines([]);
}}
>
<Text fz={"10px"} c="red">
Clear
</Text>
</Box>
)}
</Flex>
<Flex align={"center"} gap="xs">
<Text fz={"10px"} c="dark" fw={600}>
Selected: {selectedLines.length} /{" "}
{station.lines.length}
</Text>
@ -678,112 +776,19 @@ const BottomToolBar = ({
}}
/>
</Flex>
</Box>
<Box pl={"md"} pr={"md"}>
<Flex justify={"space-between"} mb={"xs"}>
<Flex></Flex>
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="orange"
size="xs"
radius="md"
onClick={() => {
const listLine = selectedLines.length
? selectedLines
: station?.lines;
if (listLine.length) {
socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id),
stationId: station.id,
command: "spam_break",
});
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}
}}
>
Send Break
</Button>
</Flex>
<Box>
<InputHistory
selectedLines={selectedLines}
socket={socket}
station={station}
/>
</Box>
</Box>
<Box style={{ width: "260px" }}>
<Flex
align={"center"}
justify={"flex-end"}
gap={"xs"}
wrap={"wrap"}
>
<ButtonDPELP
socket={socket}
selectedLines={selectedLines}
isDisable={isDisable || selectedLines.length === 0}
onClick={() => {
if (
selectedLines.length > 0
// &&
// selectedLines.length === station?.lines?.length
) {
socket?.emit("run_all_dpelp", {
lineIds: selectedLines.map((line) => line.id),
stationName: station.name,
stationId: station.id,
});
}
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 5000);
}}
/>
<Button
fw={400}
disabled={isDisable || selectedLines.length === 0}
variant="filled"
color="yellow"
style={{ height: "30px", width: "100px" }}
onClick={() => setOpenScenarioModal(true)}
>
Scenario
</Button>
<DrawerLogs
socket={socket}
isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen}
testLogContent={testLogContent}
setTestLogContent={setTestLogContent}
/>
</Flex>
</Flex>
<Box style={{ width: "100%" }}>
<InputHistory
selectedLines={selectedLines}
socket={socket}
station={station}
/>
</Box>
</Flex>
</Tabs.Panel>
<Tabs.Panel value="apc" ps={"xs"}>
<DrawerAPCControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Tabs.Panel>
<Tabs.Panel value="switch" ps={"xs"}>
<DrawerSwitchControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Tabs.Panel>
</Tabs>
</Box>
)}
</Grid.Col>
<Grid.Col span={1}></Grid.Col>
<Grid.Col span={isExpand ? 1 : 3.5}></Grid.Col>
</Grid>
</Box>
</motion.div>

View File

@ -1170,12 +1170,6 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
<Flex gap={"8px"} wrap={"wrap"}>
{listPorts?.map((group, key) => {
const isLarge = group?.length > 20;
// console.log("isLarge::", isLarge);
// console.log("group::", group);
// console.log("checkFilterPort::", sortedPorts(group)
// .filter((el) =>
// checkFilterPort(el.status, el.name)
// ));
return (
<Box
key={key}