ATC_SIMPLE/FRONTEND/src/components/BottomToolBar.tsx

665 lines
26 KiB
TypeScript

import {
ActionIcon,
Box,
Button,
CloseButton,
Flex,
Grid,
ScrollArea,
Tabs,
Text,
} from "@mantine/core";
import { useMemo, useState } from "react";
import classes from "./Component.module.css";
import type {
IScenario,
TBrands,
TCategories,
TLine,
TStation,
} from "../untils/types";
import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./Drawer/DrawerLogs";
import { DrawerAPCControl, DrawerSwitchControl } from "./Drawer/DrawerControl";
import DrawerScenario from "./Modal/ModalScenario";
import { isJsonString } from "../untils/helper";
import { motion } from "motion/react";
import { IconCaretDown, IconCaretUp } from "@tabler/icons-react";
import InputHistory from "./InputHistory";
import ModalRunScenario from "./Modal/ModalRunScenario";
interface TabsProps {
selectedLines: TLine[];
socket: Socket | null;
setSelectedLines: (value: React.SetStateAction<TLine[]>) => void;
isDisable: boolean;
station: TStation;
stationId: number;
setIsDisable: (value: React.SetStateAction<boolean>) => void;
testLogContent: string;
isLogModalOpen: boolean;
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;
isExpand: boolean;
listBrands: TBrands[];
listCategories: TCategories[];
}
const BottomToolBar = ({
selectedLines,
socket,
setSelectedLines,
isDisable,
station,
setIsDisable,
testLogContent,
isLogModalOpen,
setIsLogModalOpen,
setTestLogContent,
scenarios,
setScenarios,
setExpanded,
setActiveTabBottom,
activeTabBottom,
isExpand,
stationId,
listBrands,
listCategories,
}: TabsProps) => {
const user = useMemo(() => {
return localStorage.getItem("user") &&
isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "")
: 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);
return (
<>
{/* Modal chọn Scenario - Custom Simple Modal */}
<motion.div
initial={false}
animate={{
height: isExpand ? "150px" : "60px",
y: 0,
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
style={{
width: "100%",
position: "fixed",
bottom: 0,
left: 0,
zIndex: 1,
overflow: "visible",
}}
>
{/* ActionIcon đặt bên ngoài Box để không bị overflow: hidden cắt */}
<ActionIcon
style={{
position: "absolute",
top: isExpand ? "-15px" : "-30px",
left: "50%",
transform: "translateX(-50%)",
backgroundColor: "#e3e0e0",
width: "55px",
height: "30px",
zIndex: 10,
}}
variant="light"
onClick={() => {
setExpanded((prev) => !prev);
}}
>
{isExpand ? (
<IconCaretDown color="green" size={20} />
) : (
<IconCaretUp color="green" size={20} />
)}
</ActionIcon>
<Box
style={{
position: "relative",
height: isExpand ? "150px" : "60px",
overflow: "hidden",
}}
>
<Grid style={{ height: "100%" }}>
<Grid.Col span={isExpand ? 1 : 2}></Grid.Col>
<Grid.Col span={isExpand ? 10 : 8}>
{isExpand ? (
<Tabs
defaultValue="command"
orientation="vertical"
value={activeTabBottom}
onChange={(val) => {
setActiveTabBottom(val || "command");
}}
className={classes.containerBottom}
style={{ height: "100%", backgroundColor: "white" }}
>
<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}
style={{ height: "200px", overflow: "auto" }}
>
<Flex justify={"space-between"} align="flex-start">
<Box>
<ScrollArea h={"9vh"}>
<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"
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 || el.line_number || ""}
</Text>
</Flex>
</Box>
))}
{selectedLines.length > 0 && (
<Box
style={{
padding: "4px 10px",
height: "26px",
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={"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,
});
});
setSelectedLines([]);
}
}}
/>
</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}
dataDPELP={scenarios?.find(
(el) => el.title.toUpperCase() === "DPELP"
)}
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"
p={0}
ps={"xs"}
style={{
height: "100%",
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<Box
style={{
overflow: "auto",
height: "200px",
padding: "4px 8px 0 0",
}}
>
<DrawerAPCControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Box>
</Tabs.Panel>
<Tabs.Panel
value="switch"
ps={"xs"}
style={{
height: "100%",
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<Box style={{ height: "200px", overflow: "auto" }}>
<DrawerSwitchControl
socket={socket}
stationAPI={station}
stationId={stationId}
/>
</Box>
</Tabs.Panel>
</Tabs>
) : (
<Box p={3}>
<Flex
direction="row"
align="center"
gap="xs"
wrap="nowrap"
style={{ height: "100%" }}
>
{/* Danh sách Line - chiếm 1 phần, cố định width và height, hiển thị 2 hàng */}
<Box
style={{
flex: "1 1 0",
width: "100%",
height: "50px",
overflow: "auto",
}}
>
<Flex
wrap={"wrap"}
gap={"6px"}
style={{ height: "100%" }}
>
{selectedLines.map((el) => (
<Box
key={el.id}
style={{
position: "relative",
padding: "3px 5px",
height: "22px",
width: "55px",
backgroundColor: "#d4e3ff",
borderRadius: "6px",
flexShrink: 0,
}}
>
<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 || el.line_number || ""}
</Text>
</Flex>
</Box>
))}
{selectedLines.length > 0 && (
<Box
style={{
padding: "3px 8px",
height: "22px",
backgroundColor: "#ffe3e3",
borderRadius: "999px",
cursor: "pointer",
display: "flex",
alignItems: "center",
flexShrink: 0,
}}
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>
</Box>
{/* Selected count - ở giữa */}
<Flex
align={"center"}
style={{ flex: "0 0 auto", flexShrink: 0 }}
>
<Text fz={"10px"} c="dark" fw={600}>
Selected: {selectedLines.length} /{" "}
{station.lines.length}
</Text>
</Flex>
{/* Input + Select All - chiếm 1 phần */}
<Flex
direction="row"
align="center"
gap="xs"
style={{
flex: "1 1 0",
minWidth: "200px",
overflow: "hidden",
}}
>
{/* Input */}
<Box
style={{
flexGrow: 1.5,
flexShrink: 1,
flexBasis: "auto",
maxWidth: "100%",
overflow: "hidden",
}}
>
<InputHistory
selectedLines={selectedLines}
socket={socket}
station={station}
inputStyle={{ width: "100%" }}
/>
</Box>
{/* Select All */}
<Flex
align={"center"}
style={{ flex: "0 0 auto", flexShrink: 0 }}
>
<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,
});
});
setSelectedLines([]);
}
}}
/>
</Flex>
</Flex>
</Flex>
</Box>
)}
</Grid.Col>
<Grid.Col span={isExpand ? 1 : 3.5}></Grid.Col>
</Grid>
</Box>
</motion.div>
<ModalRunScenario
open={openScenarioModal}
setOpen={setOpenScenarioModal}
scenarios={scenarios}
isDisable={isDisable}
selectedLines={selectedLines}
user={user}
socket={socket}
setIsDisable={setIsDisable}
station={station}
setOpenEdit={setOpenDrawerScenario}
listBrands={listBrands}
listCategories={listCategories}
/>
{/* Drawer Scenario để Add/Edit */}
<DrawerScenario
scenarios={scenarios}
setScenarios={setScenarios}
externalOpened={openDrawerScenario}
onExternalClose={() => setOpenDrawerScenario(false)}
listBrands={listBrands}
listCategories={listCategories}
/>
</>
);
};
export default BottomToolBar;