Refactor BottomToolBar UI and remove debug log

Removed a console.log statement from line_connection.ts to clean up debug output. Refactored BottomToolBar.tsx for improved layout, spacing, and usability, including changes to selected line display, button arrangement, and input handling for better user experience.
This commit is contained in:
nguyentrungthat 2025-11-27 08:53:06 +07:00
parent 065900f48b
commit f2a78c76d2
2 changed files with 305 additions and 258 deletions

View File

@ -696,8 +696,6 @@ export default class LineConnection {
timestamp: now, timestamp: now,
}) })
console.log(newItem)
// Lấy phần tử cuối // Lấy phần tử cuối
const lastItems = await redis.zrevrange(key, 0, 0) const lastItems = await redis.zrevrange(key, 0, 0)
if (lastItems.length > 0) { if (lastItems.length > 0) {

View File

@ -22,7 +22,12 @@ import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
import DrawerScenario from "./DrawerScenario"; import DrawerScenario from "./DrawerScenario";
import { isJsonString } from "../untils/helper"; import { isJsonString } from "../untils/helper";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { IconCaretDown, IconCaretUp, IconPlayerPlay, IconPlus } from "@tabler/icons-react"; import {
IconCaretDown,
IconCaretUp,
IconPlayerPlay,
IconPlus,
} from "@tabler/icons-react";
interface TabsProps { interface TabsProps {
selectedLines: TLine[]; selectedLines: TLine[];
@ -296,217 +301,143 @@ const BottomToolBar = ({
zIndex: 1, zIndex: 1,
}} }}
> >
<Box style={{ position: "relative" }}> <Box style={{ position: "relative" }}>
<ActionIcon <ActionIcon
style={{ style={{
position: "absolute", position: "absolute",
top: isExpand ? -4 : -24, top: isExpand ? -4 : -24,
left: "50%", left: "50%",
translate: "-19px 0", translate: "-19px 0",
backgroundColor: "#e3e0e0", backgroundColor: "#e3e0e0",
width: "55px", width: "55px",
}} }}
variant="light" variant="light"
onClick={() => { onClick={() => {
setExpanded((prev) => !prev); setExpanded((prev) => !prev);
}} }}
> >
{isExpand ? ( {isExpand ? (
<IconCaretDown color="green" /> <IconCaretDown color="green" />
) : ( ) : (
<IconCaretUp color="green" /> <IconCaretUp color="green" />
)} )}
</ActionIcon> </ActionIcon>
<Grid> <Grid>
<Grid.Col span={1}></Grid.Col> <Grid.Col span={1}></Grid.Col>
<Grid.Col span={10}> <Grid.Col span={10}>
<Tabs <Tabs
defaultValue="command" defaultValue="command"
orientation="vertical" orientation="vertical"
value={activeTabBottom} value={activeTabBottom}
onChange={(val) => { onChange={(val) => {
setActiveTabBottom(val || "command"); setActiveTabBottom(val || "command");
}} }}
className={classes.containerBottom} className={classes.containerBottom}
style={{ height: "20vh" }} style={{ height: "20vh" }}
> >
<Tabs.List> <Tabs.List>
<Tabs.Tab <Tabs.Tab
style={{ style={{
backgroundColor: backgroundColor:
activeTabBottom === "command" ? "#c8d9fd" : "", activeTabBottom === "command" ? "#c8d9fd" : "",
fontSize: "13px", fontSize: "13px",
paddingTop: "8px", paddingTop: "8px",
paddingBottom: "8px", paddingBottom: "8px",
}} }}
value="command" value="command"
> >
Command Line Command Line
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab <Tabs.Tab
style={{ style={{
backgroundColor: activeTabBottom === "apc" ? "#c8d9fd" : "", backgroundColor:
fontSize: "13px", activeTabBottom === "apc" ? "#c8d9fd" : "",
paddingTop: "8px", fontSize: "13px",
paddingBottom: "8px", paddingTop: "8px",
}} paddingBottom: "8px",
value="apc" }}
> value="apc"
APC >
</Tabs.Tab> APC
<Tabs.Tab </Tabs.Tab>
style={{ <Tabs.Tab
backgroundColor: style={{
activeTabBottom === "switch" ? "#c8d9fd" : "", backgroundColor:
fontSize: "13px", activeTabBottom === "switch" ? "#c8d9fd" : "",
paddingTop: "8px", fontSize: "13px",
paddingBottom: "8px", paddingTop: "8px",
}} paddingBottom: "8px",
value="switch" }}
> value="switch"
Switch >
</Tabs.Tab> Switch
</Tabs.List> </Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="command" p={"xs"}> <Tabs.Panel value="command" p={"xs"}>
<Flex justify={"space-between"}> <Flex justify={"space-between"}>
<ScrollArea h={"15vh"}>
<Flex wrap={"wrap"} gap={"xs"} w={"400px"}>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
paddingLeft: "4px",
height: "30px",
width: "80px",
backgroundColor: "#d4e3ff",
borderRadius: "8px",
}}
>
<Flex align={"center"} justify={"center"} gap={"4px"}>
<Text fz={"12px"}>Line {el.lineNumber}</Text>
<CloseButton
style={{ minWidth: "24px" }}
aria-label="Clear input"
onClick={() => {
setSelectedLines(
selectedLines.filter(
(line) => line.id !== el.id
)
);
socket?.emit("close_cli", {
lineId: el?.id,
stationId: el.stationId || el.station_id,
});
}}
/>
</Flex>
</Box>
))}
</Flex>
</ScrollArea>
<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> <Box>
<Input <ScrollArea h={"12vh"}>
ref={inputRef} <Flex wrap={"wrap"} gap={"8px"} w={"420px"}>
style={{ {selectedLines.map((el) => (
width: "30vw", <Box
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)", key={el.id}
}} style={{
placeholder={"Send command to port(s)"} position: "relative",
value={valueInput} padding: "4px 6px",
onChange={(event) => { height: "26px",
const newValue = event.currentTarget.value; width: "60px",
setValueInput(newValue); backgroundColor: "#d4e3ff",
}} borderRadius: "8px",
onKeyDown={(event) => { }}
if (event.key === "Enter") { >
const listLine = selectedLines.length {/* Close button góc trên phải */}
? selectedLines <CloseButton
: station?.lines; size="xs"
if (listLine?.length) { style={{
socket?.emit("write_command_line_from_web", { position: "absolute",
lineIds: listLine.map((line) => line.id), top: "-4px",
stationId: station.id, right: "-6px",
command: valueInput + "\r\n", minWidth: "18px",
}); width: "18px",
// setTimeout(() => { height: "18px",
// socket?.emit("write_command_line_from_web", { zIndex: 10,
// lineIds: listLine.map((line) => line.id), }}
// stationId: station.id, onClick={() => {
// command: " \n", setSelectedLines(
// }); selectedLines.filter(
// }, 1000); (line) => line.id !== el.id
} )
setValueInput(""); );
} socket?.emit("close_cli", {
}} lineId: el?.id,
rightSectionPointerEvents="all" stationId: el.stationId || el.station_id,
rightSection={ });
<CloseButton }}
aria-label="Clear input" />
onClick={() => setValueInput("")}
style={{ <Flex
display: valueInput ? undefined : "none", align={"center"}
}} justify={"center"}
/> h="100%"
} >
/> <Text fz={"11px"}>Line {el.lineNumber}</Text>
</Box> </Flex>
</Box> </Box>
<Box style={{ width: "220px" }}> ))}
<Flex align={"center"} wrap={"wrap"} gap={"xs"}> </Flex>
<ButtonSelect </ScrollArea>
selectedLines={selectedLines} {selectedLines?.length > 0 ? (
setSelectedLines={setSelectedLines} <Button
station={station} fw={400}
userName={user?.userName} className={classes.buttonControl}
onClick={() => { variant="outline"
const lines = station.lines.filter( onClick={() => {
(line) => const lines = station.lines.filter(
!line?.userOpenCLI || (line) =>
line?.userOpenCLI === user?.userName !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 {
lines.forEach((line) => { lines.forEach((line) => {
socket?.emit("close_cli", { socket?.emit("close_cli", {
lineId: line?.id, lineId: line?.id,
@ -514,53 +445,171 @@ const BottomToolBar = ({
}); });
}); });
setSelectedLines([]); setSelectedLines([]);
}}
>
Clear
</Button>
) : (
""
)}
</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>
<Input
ref={inputRef}
style={{
width: "30vw",
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
}}
placeholder={"Send command to port(s)"}
value={valueInput}
onChange={(event) => {
const newValue = event.currentTarget.value;
setValueInput(newValue);
}}
onKeyDown={(event) => {
if (event.key === "Enter") {
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: valueInput + "\r\n",
});
// setTimeout(() => {
// socket?.emit("write_command_line_from_web", {
// lineIds: listLine.map((line) => line.id),
// stationId: station.id,
// command: " \n",
// });
// }, 1000);
}
setValueInput("");
}
}}
rightSectionPointerEvents="all"
rightSection={
<CloseButton
aria-label="Clear input"
onClick={() => setValueInput("")}
style={{
display: valueInput ? undefined : "none",
}}
/>
} }
}} />
/> </Box>
<ButtonDPELP </Box>
socket={socket} <Box style={{ width: "220px" }}>
selectedLines={selectedLines} <Flex align={"center"} wrap={"wrap"} gap={"xs"}>
isDisable={isDisable || selectedLines.length === 0} <ButtonSelect
onClick={() => { selectedLines={selectedLines}
// setSelectedLines([]); setSelectedLines={setSelectedLines}
setIsDisable(true); station={station}
setTimeout(() => { userName={user?.userName}
setIsDisable(false); onClick={() => {
}, 5000); const lines = station.lines.filter(
}} (line) =>
/> !line?.userOpenCLI ||
<Button line?.userOpenCLI === user?.userName
fw={400} );
disabled={isDisable || selectedLines.length === 0} if (selectedLines.length !== lines.length) {
variant="filled" setSelectedLines(lines);
color="yellow" lines.forEach((line) => {
style={{ height: "30px", width: "100px" }} socket?.emit("open_cli", {
onClick={() => setOpenScenarioModal(true)} lineId: line.id,
> stationId: line.stationId || line.station_id,
Scenario userEmail: user?.email,
</Button> userName: user?.userName,
<DrawerLogs });
socket={socket} });
isLogModalOpen={isLogModalOpen} } else {
setIsLogModalOpen={setIsLogModalOpen} lines.forEach((line) => {
testLogContent={testLogContent} socket?.emit("close_cli", {
setTestLogContent={setTestLogContent} lineId: line?.id,
/> stationId: line.stationId || line.station_id,
</Flex> });
</Box> });
</Flex> setSelectedLines([]);
</Tabs.Panel> }
<Tabs.Panel value="apc" ps={"xs"}> }}
<DrawerAPCControl socket={socket} stationAPI={station} /> />
</Tabs.Panel> <ButtonDPELP
<Tabs.Panel value="switch" ps={"xs"}> socket={socket}
<DrawerSwitchControl socket={socket} stationAPI={station} /> selectedLines={selectedLines}
</Tabs.Panel> isDisable={isDisable || selectedLines.length === 0}
</Tabs> onClick={() => {
</Grid.Col> // setSelectedLines([]);
<Grid.Col span={1}></Grid.Col> setIsDisable(true);
</Grid> setTimeout(() => {
</Box> 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} />
</Tabs.Panel>
<Tabs.Panel value="switch" ps={"xs"}>
<DrawerSwitchControl socket={socket} stationAPI={station} />
</Tabs.Panel>
</Tabs>
</Grid.Col>
<Grid.Col span={1}></Grid.Col>
</Grid>
</Box>
</motion.div> </motion.div>
{/* Drawer Scenario để Add/Edit */} {/* Drawer Scenario để Add/Edit */}