Merge pull request 'Refactor BottomToolBar UI and remove debug log' (#12) from that into main

Reviewed-on: #12
This commit is contained in:
andrew.ng 2025-11-27 12:53:31 +11:00
commit 3415af6eb0
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 */}