ATC_SIMPLE/FRONTEND/src/components/CardLine.tsx

560 lines
18 KiB
TypeScript

import { Card, Text, Box, Flex, Menu, Button, Input } from "@mantine/core";
import type { IScenario, TLine, TStation } from "../untils/types";
import classes from "./Component.module.css";
import TerminalCLI from "./TerminalXTerm";
import type { Socket } from "socket.io-client";
import { memo, useEffect, useMemo, useState } from "react";
import { convertTimestampToDate } from "../untils/helper";
import { ButtonDPELP, ButtonScenario } from "./ButtonAction";
import { notifications } from "@mantine/notifications";
import { listBaudDefault } from "../untils/constanst";
import { IconCaretRight } from "@tabler/icons-react";
const CardLine = ({
line,
selectedLines,
setSelectedLines,
socket,
stationItem,
openTerminal,
loadTerminal,
scenarios,
}: {
line: TLine;
selectedLines: TLine[];
setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void;
socket: Socket | null;
stationItem: TStation;
openTerminal: (value: TLine) => void;
loadTerminal: boolean;
scenarios: IScenario[];
}) => {
const user = useMemo(() => {
return localStorage.getItem("user") &&
typeof localStorage.getItem("user") === "string"
? JSON.parse(localStorage.getItem("user") || "")
: null;
}, []);
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const [valueBaud, setValueBaud] = useState<string>("");
useEffect(() => {
if (
typeof line?.userOpenCLI !== "undefined" &&
typeof line?.userOpenCLI === "string" &&
line?.userOpenCLI.length > 0 &&
line?.userOpenCLI !== user?.userName
)
setIsDisabled(true);
else setIsDisabled(false);
}, [line?.userOpenCLI]);
const controlApc = (action: string) => {
if (!line.outlet) {
notifications.show({
title: "Error",
message: "Hasn't config outlet number",
color: "red",
});
return;
}
const apcName = line.apcName || line.apc_name;
if (!apcName) {
notifications.show({
title: "Error",
message: "Hasn't config apc",
color: "red",
});
return;
}
if (
(apcName === "apc_1" && !stationItem.apc_1_ip) ||
(apcName === "apc_2" && !stationItem.apc_2_ip)
) {
notifications.show({
title: "Error",
message: "Hasn't config apc ip",
color: "red",
});
return;
}
socket?.emit("control_apc", {
outletNumbers: [line.outlet],
station: stationItem,
action: action,
apcName: line.apcName || line.apc_name,
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
};
const handleClick = (isSelect = false) => {
if (
typeof line?.userOpenCLI !== "undefined" &&
typeof line?.userOpenCLI === "string" &&
line?.userOpenCLI.length > 0 &&
line?.userOpenCLI !== user?.userName
)
return;
if (selectedLines.find((val) => val.id === line.id)) {
if (isSelect) {
setSelectedLines(selectedLines.filter((val) => val.id !== line.id));
socket?.emit("close_cli", {
lineId: line?.id,
stationId: line.stationId || line.station_id,
});
}
} else {
setSelectedLines((pre) => [...pre, line]);
socket?.emit("open_cli", {
lineId: line.id,
stationId: line.stationId || line.station_id,
userEmail: user?.email,
userName: user?.userName,
});
}
};
return (
<Card
key={line.id}
shadow="sm"
radius="md"
withBorder
className={classes.card_line}
style={
selectedLines.find((val) => val.id === line.id)
? { backgroundColor: "#8bf55940" }
: {}
}
onDoubleClick={(e) => {
e.preventDefault();
e.stopPropagation();
openTerminal(line);
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleClick(true);
}}
// onMouseLeave={() => setTimeout(() => setShowMenu(false), 150)}
>
<Flex
justify={"space-between"}
direction={"column"}
// gap={"md"}
// align={"center"}
>
<Menu
shadow="md"
position="right-start"
transitionProps={{ transition: "pop-top-right" }}
// opened={showMenu}
// onChange={setShowMenu}
trigger="hover"
withArrow
arrowSize={8}
closeOnItemClick={false}
closeOnClickOutside={false}
>
<Menu.Target>
<Flex
justify={"space-between"}
direction={"column"}
// className={classes.topBarLine}
// onMouseEnter={() => setShowMenu(true)}
>
<Flex gap={"8px"}>
<Flex direction={"column"} justify={"center"} align={"center"}>
<Box>
<Text
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(
`PID: ${line?.inventory?.pid || ""} | SN: ${
line?.inventory?.sn || ""
}`
);
}}
className={classes.buttonCopy}
fw={600}
style={{
fontSize: "24px",
lineHeight: "normal",
// color: line.status === "connected" ? "green" : "dark",
border: "1px solid #ccc",
borderRadius: "12px",
paddingLeft: "4px",
paddingRight: "4px",
backgroundColor:
line.status === "connected" ? "#4cc64c" : "",
}}
>
{line.lineNumber || line.line_number}
</Text>
</Box>
<Box>
<Text
fw={500}
style={{ fontSize: "12px", color: "#767676" }}
>
{line.port}
</Text>
</Box>
</Flex>
<Box w={"100%"}>
<Flex justify={"space-between"} w={"100%"}>
<div className={classes.info_line}>
PID:{" "}
<Text
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(
`PID: ${line?.inventory?.pid || ""} | SN: ${
line?.inventory?.sn || ""
}`
);
}}
className={`${classes.info_line} ${classes.buttonCopy}`}
fs={"italic"}
>
{line?.inventory?.pid || ""}
</Text>
{line?.inventory?.vid ? (
<Text className={classes.info_line} fs={"italic"}>
{" | " + line?.inventory?.vid}
</Text>
) : (
""
)}
</div>
<div
className={classes.info_line}
style={{ width: "120px" }}
>
SN:{" "}
<Text
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(
`PID: ${line?.inventory?.pid || ""} | SN: ${
line?.inventory?.sn || ""
}`
);
}}
className={`${classes.info_line} ${classes.buttonCopy}`}
fs={"italic"}
>
{line?.inventory?.sn || ""}
</Text>
</div>
</Flex>
<Flex justify={"space-between"} w={"100%"}>
<Box></Box>
<div
style={{
fontSize: "11px",
color: "red",
}}
>
{line?.userOpenCLI
? (line?.userOpenCLI === user?.userName
? "You are"
: line?.userOpenCLI + " is") + " using"
: ""}
</div>
</Flex>
</Box>
</Flex>
</Flex>
</Menu.Target>
<Menu.Dropdown style={{ width: "80px", backgroundColor: "#2d2d2d" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onDoubleClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
// onMouseEnter={() => setShowMenu(true)}
// onMouseLeave={() => setTimeout(() => setShowMenu(false), 300)}
>
<Button
disabled={isDisabled}
variant="filled"
color="orange"
// size="xs"
className={classes.buttonMenuTool}
onClick={() => {
socket?.emit("write_command_line_from_web", {
lineIds: [line.id],
stationId: Number(stationItem.id),
command: "spam_break",
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 2000);
}}
>
Send Break
</Button>
<ButtonDPELP
className={classes.buttonMenuTool}
socket={socket}
selectedLines={[line]}
isDisable={isDisabled}
onClick={() => {
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
/>
<Menu
closeOnItemClick={false}
closeOnClickOutside={false}
trigger="hover"
shadow="md"
position="right"
>
<Menu.Target>
<Button
disabled={isDisabled}
variant="filled"
color="yellow"
className={classes.buttonMenuTool}
onClick={() => {}}
>
Scenario <IconCaretRight size={"14px"} />
</Button>
</Menu.Target>
<Menu.Dropdown style={{ width: "120px" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
>
{scenarios.map((el, i) => (
<ButtonScenario
key={i}
socket={socket}
selectedLines={[line]}
isDisable={isDisabled}
onClick={() => {
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
scenario={el}
fontSize="9px"
/>
))}
</Flex>
</Menu.Dropdown>
</Menu>
<Menu
closeOnItemClick={false}
closeOnClickOutside={false}
trigger="hover"
shadow="md"
position="right"
>
<Menu.Target>
<Button
className={classes.buttonMenuTool}
disabled={isDisabled}
variant="filled"
size="xs"
onClick={() => {}}
>
BAUD <IconCaretRight size={"14px"} />
</Button>
</Menu.Target>
<Menu.Dropdown style={{ width: "90px" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
>
{listBaudDefault.map((el, i) => (
<Button
key={i}
disabled={isDisabled}
className={classes.buttonMenuTool}
variant="outline"
size="xs"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
socket?.emit("set_baud", {
lineId: line.id,
baud: el,
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
>
{el}
</Button>
))}
<Input
placeholder="Custom"
value={valueBaud}
onChange={(e) => setValueBaud(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
socket?.emit("set_baud", {
lineId: line.id,
baud: Number(valueBaud),
});
setValueBaud("");
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}
}}
/>
</Flex>
</Menu.Dropdown>
</Menu>
<Flex justify={"space-between"} direction={"column"}>
<hr style={{ width: "100%" }} />
<Box style={{ textAlign: "center" }}>
<Text p={0} style={{ fontSize: "13px" }} c={"white"}>
Power
</Text>
</Box>
<Button
className={classes.buttonMenuTool}
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlApc("on");
}}
>
ON
</Button>
<Button
className={classes.buttonMenuTool}
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlApc("off");
}}
>
OFF
</Button>
<Button
className={classes.buttonMenuTool}
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlApc("restart");
}}
>
Restart
</Button>
</Flex>
</Flex>
</Menu.Dropdown>
</Menu>
<Box
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
style={{ height: "160px", width: "332px" }}
>
<TerminalCLI
cliOpened={loadTerminal}
socket={socket}
content={line?.output ?? ""}
initContent={line?.netOutput ?? ""}
loadingContent={line?.loadingOutput}
line_id={Number(line?.id)}
line={line}
station_id={Number(stationItem.id)}
// isDisabled={
// typeof line?.userOpenCLI !== "undefined" &&
// typeof line?.userOpenCLI === "string" &&
// line?.userOpenCLI.length > 0 &&
// line?.userOpenCLI !== user?.userName
// }
isDisabled={false}
line_status={line?.status || ""}
fontSize={11}
miniSize={true}
customStyle={{
maxHeight: "160px",
height: "160px",
fontSize: "7px",
padding: "0px",
paddingBottom: "0px",
}}
onDoubleClick={() => {
openTerminal(line);
}}
onFocus={() => {
handleClick();
}}
onBlur={() => {
if (
!selectedLines.find((value) => value.id === line?.id) &&
line?.userOpenCLI === user?.userName
)
socket?.emit("close_cli", {
lineId: line?.id,
stationId: line.stationId || line.station_id,
});
}}
/>
</Box>
<Box>
<div className={classes.info_line}>
Latest: {line?.latestScenario?.name || ""}
<Text style={{ fontStyle: "italic", fontSize: "11px" }}>
{line?.latestScenario?.time
? "(" + convertTimestampToDate(line?.latestScenario?.time) + ")"
: ""}
</Text>
</div>
</Box>
</Flex>
</Card>
);
};
export default memo(CardLine);