Enhance port filtering and UI in DrawerControl #1

Merged
andrew.ng merged 1 commits from that into main 2025-11-24 15:18:06 +11:00
3 changed files with 222 additions and 194 deletions

View File

@ -411,7 +411,7 @@ function App() {
borderRadius: 8,
}}
>
<ScrollArea h={expandedBottomBar ? "70vh" : "85vh"}>
<ScrollArea h={expandedBottomBar ? "68vh" : "85vh"}>
{station.lines.length > 8 ? (
<Grid
style={{

View File

@ -71,7 +71,7 @@ const BottomToolBar = ({
<motion.div
initial={false}
animate={{
height: isExpand ? "18vh" : 0,
height: isExpand ? "20vh" : 0,
y: 0, // đẩy xuống khi thu nhỏ
}}
transition={{ type: "spring", stiffness: 180, damping: 20 }}
@ -115,7 +115,7 @@ const BottomToolBar = ({
setActiveTabBottom(val || "command");
}}
className={classes.containerBottom}
style={{ height: "18vh" }}
style={{ height: "20vh" }}
>
<Tabs.List>
<Tabs.Tab
@ -157,7 +157,7 @@ const BottomToolBar = ({
<Tabs.Panel value="command" p={"xs"}>
<Flex justify={"space-between"}>
<ScrollArea h={"13vh"}>
<ScrollArea h={"15vh"}>
<Flex wrap={"wrap"} gap={"xs"} w={"400px"}>
{selectedLines.map((el) => (
<Box

View File

@ -792,19 +792,14 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
const normalizePortName = (port: string): string => {
if (!port) return "";
// Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2"
// Match interface type + numeric hierarchy (e.g. "Gi" + "1/0/24")
const match = port.match(/^([A-Za-z]+)([\d/]+)$/);
if (!match) return port;
const type = match[1]; // Fa, Gi, Te, etc.
const numbers = match[2]; // "0/1" / "0/0/1" / "0/0/2"
const numbers = match[2]; // e.g. "1/0/24"
// Get the last part after slash
const parts = numbers.split("/");
const last = parts[parts.length - 1];
return `${type}${last}`;
return numbers;
};
const changeShowPort = (status: string) => {
@ -812,6 +807,18 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
setCheckedActive(status);
};
const checkFilterPort = (status: string, portName: string) => {
if (checkedActive === "all") return true;
if (checkedActive === "on") return status === "ON";
if (checkedActive === "off") return status === "OFF";
if (checkedActive === "config") {
const listInterface = stationAPI?.lines
?.filter((el) => el.interface)
.map((el) => el.interface);
return listInterface?.includes(portName);
}
};
return loading ? (
<Box
style={{
@ -830,7 +837,6 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
style={{
display: "flex",
justifyContent: "space-between",
borderTop: "1px #ccc solid",
marginTop: "6px",
minWidth: "98%",
}}
@ -878,6 +884,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
""
)}
<Button
className={classes.buttonMenuTool}
disabled={isSubmit}
title={
listPortsSelected.length === listPorts.flat().length
@ -902,6 +909,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
{"(" + listPorts.flat().length + ")"}
</Button>
<Button
className={classes.buttonMenuTool}
disabled={
isSubmit ||
listPorts.flat().length === 0 ||
@ -958,6 +966,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
: "Restart Selected"}
</Button>
<Button
className={classes.buttonMenuTool}
disabled={
isSubmit ||
listPorts.flat().length === 0 ||
@ -1014,6 +1023,7 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
: "Turn On Selected"}
</Button>
<Button
className={classes.buttonMenuTool}
disabled={
isSubmit ||
listPorts.flat().length === 0 ||
@ -1088,6 +1098,12 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
checked={checkedActive === "off"}
onChange={() => changeShowPort("off")}
/>
<Radio
value="config"
label="Config interface"
checked={checkedActive === "config"}
onChange={() => changeShowPort("config")}
/>
</Group>
</div>
</div>
@ -1106,186 +1122,197 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
span={isLarge ? 11 : isMini ? 1 : 12}
>
{isLarge ? (
<ScrollArea h={"12vh"} w={"68vw"}>
<Flex gap={"8px"} wrap={"nowrap"}>
{sortedPorts(group)
.slice(0, sortedPorts(group).length / 2)
.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
flex: "0 0 auto",
position: "relative",
width: "50px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Flex>
<Flex gap={"8px"} wrap={"nowrap"} mt={"8px"} pb={"xs"}>
{sortedPorts(group)
.slice(
sortedPorts(group).length / 2,
sortedPorts(group).length
)
.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
flex: "0 0 auto",
position: "relative",
width: "50px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Flex>
</ScrollArea>
) : (
<Box
<fieldset
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "10px",
overflow: "auto",
maxHeight: "12vh",
maxWidth: "70vw",
borderLeft: "1px solid #dedede",
padding: "0px 4px",
}}
>
{sortedPorts(group)
?.filter((el) => {
if (checkedActive === "all") return true;
if (checkedActive === "on")
return el.status === "ON";
if (checkedActive === "off")
return el.status === "OFF";
})
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
position: "relative",
width: "50px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
<legend>
<Text fw={700} c={"#514d4d"} fz={"xs"}>
{group[0]?.name.substring(0, 2) || ""}
</Text>
</legend>
<ScrollArea h={"11vh"} w={"67vw"}>
<Flex gap={"8px"} wrap={"nowrap"}>
{sortedPorts(group)
.slice(0, sortedPorts(group).length / 2)
.filter((el) =>
checkFilterPort(el.status, el.name)
)
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
flex: "0 0 auto",
position: "relative",
width: "50px",
height: "40px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Flex>
<Flex gap={"8px"} wrap={"nowrap"} mt={"8px"}>
{sortedPorts(group)
.slice(
sortedPorts(group).length / 2,
sortedPorts(group).length
)
.filter((el) =>
checkFilterPort(el.status, el.name)
)
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
flex: "0 0 auto",
position: "relative",
width: "50px",
height: "40px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Flex>
</ScrollArea>
</fieldset>
) : (
<fieldset
style={{
padding: "0px 4px",
}}
>
<legend>
<Text fw={700} c={"#514d4d"} fz={"xs"}>
{group[0]?.name.substring(0, 2) || ""}
</Text>
</legend>
<Box
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "10px",
overflow: "auto",
height: "11vh",
maxWidth: "70vw",
}}
>
{sortedPorts(group)
?.filter((el) =>
checkFilterPort(el.status, el.name)
)
?.map((port, i) => (
<Card
key={i}
shadow="sm"
padding="xs"
radius="md"
withBorder
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
position: "relative",
width: "50px",
height: "40px",
backgroundColor:
port.poe === "ON"
? "#f2dcf8"
: port.status === "ON"
? "#d4f1d3"
: "#f5f5f5",
cursor: "pointer",
border: listPortsSelected.find(
(el) => el.name === port.name
)?.name
? "1px solid #0018ff"
: "",
}}
className={`${
isSubmit ? classes.isDisabled : ""
}`}
onClick={() => {
toggleSelect(port);
}}
>
{/* <IconSection
<Box
style={{
display: "flex",
alignItems: "center",
gap: "2px",
flexDirection: "column",
flexWrap: "wrap",
}}
>
{/* <IconSection
size={"12px"}
color={
port.poe === "ON"
@ -1295,13 +1322,14 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
: "#b8b8b8"
}
/> */}
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Box>
<Text fw={500} fz={"11px"}>
{normalizePortName(port.name)}
</Text>
</Box>
</Card>
))}
</Box>
</fieldset>
)}
</Grid.Col>
);