Refactor BottomToolBar and update user types

Improved BottomToolBar command line UI with better line selection and clearing logic. Updated TUser type to include 'id' and 'email' fields, and fixed DragTabs to support both 'userId' and 'id' for user key mapping.
This commit is contained in:
nguyentrungthat 2025-11-27 10:35:37 +07:00
parent b5bb90ca4e
commit 1b9b18ce3b
3 changed files with 337 additions and 288 deletions

View File

@ -14,7 +14,7 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import classes from "./Component.module.css"; import classes from "./Component.module.css";
import type { IScenario, TLine, TStation } from "../untils/types"; import type { IScenario, TLine, TStation, TUser } from "../untils/types";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction"; import { ButtonDPELP, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./DrawerLogs"; import DrawerLogs from "./DrawerLogs";
@ -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[];
@ -58,7 +63,7 @@ const ScenarioCard = ({
index: number; index: number;
isDisable: boolean; isDisable: boolean;
selectedLines: TLine[]; selectedLines: TLine[];
user: any; user: TUser;
socket: Socket | null; socket: Socket | null;
setOpenScenarioModal: (value: boolean) => void; setOpenScenarioModal: (value: boolean) => void;
setIsDisable: (value: boolean) => void; setIsDisable: (value: boolean) => void;
@ -138,10 +143,7 @@ const ScenarioCard = ({
return ( return (
<Grid.Col key={scenario.id} span={3}> <Grid.Col key={scenario.id} span={3}>
<div <div ref={cardRef} style={{ position: "relative" }}>
ref={cardRef}
style={{ position: "relative" }}
>
<Card <Card
shadow="sm" shadow="sm"
padding="md" padding="md"
@ -178,7 +180,8 @@ const ScenarioCard = ({
isDisable || isDisable ||
selectedLines.filter( selectedLines.filter(
(el) => (el) =>
!el?.userEmailOpenCLI || el?.userEmailOpenCLI === user?.email !el?.userEmailOpenCLI ||
el?.userEmailOpenCLI === user?.email
).length === 0 ).length === 0
} }
onClick={() => { onClick={() => {
@ -270,9 +273,7 @@ const ScenarioCard = ({
overflow: "auto", overflow: "auto",
}} }}
> >
{steps {steps.slice(0, 5).map((step: { send: string }, i: number) => (
.slice(0, 5)
.map((step: { send: string }, i: number) => (
<Text <Text
key={i} key={i}
size="xs" size="xs"
@ -420,7 +421,11 @@ const BottomToolBar = ({
{scenarios.length > 0 ? ( {scenarios.length > 0 ? (
<Grid <Grid
gutter="md" gutter="md"
style={{ margin: 0, overflow: "visible", position: "relative" }} style={{
margin: 0,
overflow: "visible",
position: "relative",
}}
> >
{scenarios.map((scenario, index) => ( {scenarios.map((scenario, index) => (
<ScenarioCard <ScenarioCard
@ -524,7 +529,8 @@ const BottomToolBar = ({
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab <Tabs.Tab
style={{ style={{
backgroundColor: activeTabBottom === "apc" ? "#c8d9fd" : "", backgroundColor:
activeTabBottom === "apc" ? "#c8d9fd" : "",
fontSize: "13px", fontSize: "13px",
paddingTop: "8px", paddingTop: "8px",
paddingBottom: "8px", paddingBottom: "8px",
@ -549,24 +555,33 @@ const BottomToolBar = ({
<Tabs.Panel value="command" p={"xs"}> <Tabs.Panel value="command" p={"xs"}>
<Flex justify={"space-between"}> <Flex justify={"space-between"}>
<ScrollArea h={"15vh"}> <Box>
<Flex wrap={"wrap"} gap={"xs"} w={"400px"}> <ScrollArea h={"12vh"}>
<Flex wrap={"wrap"} gap={"8px"} w={"420px"}>
{selectedLines.map((el) => ( {selectedLines.map((el) => (
<Box <Box
key={el.id} key={el.id}
style={{ style={{
paddingLeft: "4px", position: "relative",
height: "30px", padding: "4px 6px",
width: "80px", height: "26px",
width: "60px",
backgroundColor: "#d4e3ff", backgroundColor: "#d4e3ff",
borderRadius: "8px", borderRadius: "8px",
}} }}
> >
<Flex align={"center"} justify={"center"} gap={"4px"}> {/* Close button góc trên phải */}
<Text fz={"12px"}>Line {el.lineNumber}</Text>
<CloseButton <CloseButton
style={{ minWidth: "24px" }} size="xs"
aria-label="Clear input" style={{
position: "absolute",
top: "-4px",
right: "-6px",
minWidth: "18px",
width: "18px",
height: "18px",
zIndex: 10,
}}
onClick={() => { onClick={() => {
setSelectedLines( setSelectedLines(
selectedLines.filter( selectedLines.filter(
@ -579,11 +594,44 @@ const BottomToolBar = ({
}); });
}} }}
/> />
<Flex
align={"center"}
justify={"center"}
h="100%"
>
<Text fz={"11px"}>Line {el.lineNumber}</Text>
</Flex> </Flex>
</Box> </Box>
))} ))}
</Flex> </Flex>
</ScrollArea> </ScrollArea>
{selectedLines?.length > 0 ? (
<Button
fw={400}
className={classes.buttonControl}
variant="outline"
onClick={() => {
const lines = station.lines.filter(
(line) =>
!line?.userOpenCLI ||
line?.userOpenCLI === user?.userName
);
lines.forEach((line) => {
socket?.emit("close_cli", {
lineId: line?.id,
stationId: line.stationId || line.station_id,
});
});
setSelectedLines([]);
}}
>
Clear
</Button>
) : (
""
)}
</Box>
<Box pl={"md"} pr={"md"}> <Box pl={"md"} pr={"md"}>
<Flex justify={"space-between"} mb={"xs"}> <Flex justify={"space-between"} mb={"xs"}>
<Flex></Flex> <Flex></Flex>

View File

@ -2,7 +2,6 @@ import {
ActionIcon, ActionIcon,
Avatar, Avatar,
Box, Box,
Button,
Flex, Flex,
Group, Group,
Menu, Menu,
@ -296,7 +295,7 @@ export default function DraggableTabs({
<Tooltip <Tooltip
withArrow withArrow
label={usersConnecting.map((el) => ( label={usersConnecting.map((el) => (
<Text key={el.userId}>{el.userName}</Text> <Text key={el.userId || el.id}>{el.userName}</Text>
))} ))}
> >
<Avatar radius="xl" me={"sm"}> <Avatar radius="xl" me={"sm"}>

View File

@ -105,6 +105,8 @@ export type TLine = {
export type TUser = { export type TUser = {
userId: number; userId: number;
userName: string; userName: string;
id: number;
email: string;
}; };
export type APCProps = { export type APCProps = {