import { Box, Button, Card, Flex, Grid, Group, Loader, Radio, ScrollArea, Text, } from "@mantine/core"; import { IconRepeat } from "@tabler/icons-react"; import { useEffect, useState } from "react"; import classes from "./Component.module.css"; import type { APCProps, SwitchPortsProps, TStation } from "../untils/types"; import type { Socket } from "socket.io-client"; interface DrawerProps { stationAPI: TStation; socket: Socket | null; } type TSelectedOutlet = { number?: number; name: string; status: string; apc: number; }; export const DrawerAPCControl: React.FC = ({ stationAPI, socket, }) => { const findLineByOutlet = (outlet: TSelectedOutlet) => { return stationAPI.lines.find( (line) => line.outlet === outlet.number && line.apcName === `apc_${outlet.apc}` ); }; const [listOutlet, setListOutlet] = useState( Array.from({ length: 16 }) .map((_, index) => ({ number: index / 8 < 1 ? index + 1 : index - 7, // Outlet numbers 1-8 for APC 1, 1-8 for APC 2 name: `Outlet ${(index % 8) + 1}`, status: "OFF", apc: index / 8 < 1 ? 1 : 2, // Assuming two APCs, alternating outlets })) .map((el) => { const line = findLineByOutlet(el); if (!line) return el; return { ...el, name: "Line " + line.lineNumber || el.name, }; }) ); const [dataStation, setDataStation] = useState(stationAPI); const [listOutletSelected, setListOutletSelected] = useState< TSelectedOutlet[] >([]); const [isSubmit, setIsSubmit] = useState(true); const detectOutlet = ( apc: APCProps, lines: string[], result: TSelectedOutlet[], i: number ) => { let insideDeviceManager = false; let currentBlock: string[] = []; let lastDeviceManagerBlock: string[] = []; for (const line of lines) { // Detect start of any section const isSectionHeader = line.match(/^-{5,}.*-{5,}$/); // If it's the Device Manager section, start capturing if (line.includes("------- Device Manager")) { insideDeviceManager = true; currentBlock = []; continue; } // If a new section starts and we were inside Device Manager, save it if ( insideDeviceManager && isSectionHeader && !line.includes("Device Manager") ) { insideDeviceManager = false; if (currentBlock.length > 0) { lastDeviceManagerBlock = [...currentBlock]; } continue; } // If inside, accumulate if (insideDeviceManager) { currentBlock.push(line); } } // Edge case: block was still open till end of file if (insideDeviceManager && currentBlock.length > 0) { lastDeviceManagerBlock = [...currentBlock]; } // Parse outlets from the lastDeviceManagerBlock let indexOutlet = 1; lastDeviceManagerBlock.forEach((line) => { const match = line.match( /^\s*\d+-\s+((?:Outlet|Port)\s+\d+)\s+(ON|OFF)/i ); if (match) { const [, , status] = match; result.push({ number: indexOutlet, name: "Outlet " + indexOutlet, status: status.toUpperCase(), apc: i + 1, }); indexOutlet++; } }); if (result.length > 0 && apc?.status === "CONNECTED") apc.outlets = result; else { Array.from({ length: 8 }).forEach((_, index) => { result.push({ number: index + 1, name: `Outlet ${index + 1}`, status: "OFF", apc: i + 1, }); }); apc.outlets = result; } }; useEffect(() => { socket?.on("apc_output", (data) => { if (data.stationId !== stationAPI.id) return; const outlets: TSelectedOutlet[] = []; const apc1 = data.apcNumber === 1 ? { output: data.data, status: data.status } : dataStation?.apc1; const apc2 = data.apcNumber === 2 ? { output: data.data, status: data.status } : dataStation?.apc2; setDataStation((prev) => ({ ...prev, apc1: apc1, apc2: apc2 })); [apc1, apc2].forEach((apc, i) => { if (!apc) return; const result: TSelectedOutlet[] = []; const lines = apc?.output?.split("\n") || []; detectOutlet(apc, lines, result, i); outlets.push(...result); }); setTimeout(() => { if (apc1 || apc2) setIsSubmit(false); if (outlets.length > 0) setListOutlet( listOutlet.map((el) => outlets.find((o) => o.number === el.number && o.apc === el.apc) ? { ...el, ...outlets.find( (o) => o.number === el.number && o.apc === el.apc ), } : el ) ); }, 1000); }); return () => { socket?.off("apc_output"); }; }, [socket, dataStation, stationAPI]); const toggleSelect = (outlet: TSelectedOutlet, number: number) => { setListOutletSelected((prev) => prev.find((pid) => pid.name === outlet.name && pid.apc === outlet.apc) ? prev.filter( (pid) => pid.name !== outlet.name || pid.apc !== outlet.apc ) : [...prev, { ...outlet, number }] ); }; const RenderAPCStatus = (apc: APCProps) => { switch (apc?.status) { case "CONNECTED": return ( <> {apc?.status} ); case "DISCONNECTED": return ( <> {apc?.status} ); case "TIMEOUT": return ( <> {apc?.status} ); default: return ( <> DISCONNECTED ); } }; useEffect(() => { setTimeout(() => { setIsSubmit(false); }, 15000); }, []); return (
APC 1 {dataStation?.apc_1_ip ? ( RenderAPCStatus(dataStation?.apc1) ) : ( APC not available )} {dataStation?.apc1?.status !== "CONNECTED" ? ( ) : (
)}
{listOutlet .filter((el) => el.apc === 1) .map((outlet, i) => ( el.name === outlet.name && el.apc === outlet.apc )?.name ? "1px solid #0018ff" : "", }} onClick={() => { toggleSelect(outlet, i + 1); }} > {findLineByOutlet(outlet) ? "Line " + findLineByOutlet(outlet)?.lineNumber : outlet.name} ))}
APC 2 {dataStation?.apc_2_ip ? ( RenderAPCStatus(dataStation?.apc2) ) : ( APC not available )} {dataStation?.apc2?.status !== "CONNECTED" ? ( ) : (
)}
{listOutlet .filter((el) => el.apc === 2) .map((outlet, i) => ( el.name === outlet.name && el.apc === outlet.apc )?.name ? "1px solid #0018ff" : "", }} onClick={() => { toggleSelect(outlet, i + 1); }} > {findLineByOutlet(outlet) ? "Line " + findLineByOutlet(outlet)?.lineNumber : outlet.name} ))}
); }; export const DrawerSwitchControl: React.FC = ({ stationAPI, socket, }) => { const [listPorts, setListPorts] = useState([]); const [dataStation, setDataStation] = useState(stationAPI); const [listPortsSelected, setListPortsSelected] = useState< SwitchPortsProps[] >([]); const [isSubmit, setIsSubmit] = useState(false); const [loading, setLoading] = useState(true); const [checkedActive, setCheckedActive] = useState("all"); useEffect(() => { if (!open) { setListPortsSelected([]); setLoading(true); } }, [open]); useEffect(() => { const value = localStorage.getItem("show-switch-port"); if (value) { setCheckedActive(value); } }, []); useEffect(() => { if (loading) setTimeout(() => { setLoading(false); }, 15000); }, [loading]); useEffect(() => { socket?.on("switch_output", (data) => { if (data.stationId !== stationAPI.id) return; if (data?.portGroups && data?.portGroups.length > 0) setListPorts(data?.portGroups || []); setLoading(false); setDataStation({ ...dataStation, switch: { status: data.status } }); }); return () => { socket?.off("switch_output"); }; }, [socket, dataStation, stationAPI]); const toggleSelect = (port: SwitchPortsProps) => { setListPortsSelected((prev) => prev.find((pid) => pid.name === port.name) ? prev.filter((pid) => pid.name !== port.name) : [...prev, { ...port }] ); }; const RenderAPCStatus = (apc: APCProps) => { switch (apc.status) { case "CONNECTED": return ( <> {apc.status} ); case "DISCONNECTED": return ( <> {apc.status} ); case "TIMEOUT": return ( <> {apc.status} ); default: return ( <> {/* {line.status} */} ); } }; const sortedPorts = (ports: SwitchPortsProps[]) => { return ports.sort((a: SwitchPortsProps, b: SwitchPortsProps) => { const splitNameA = a.name.split("/"); const splitNameB = b.name.split("/"); const numA = parseInt(splitNameA[splitNameA.length - 1], 10); const numB = parseInt(splitNameB[splitNameB.length - 1], 10); const isOddA = numA % 2 !== 0; const isOddB = numB % 2 !== 0; // if (numA / 48 > 1) return 0; // Ưu tiên số lẻ lên trước if (isOddA && !isOddB) return -1; if (!isOddA && isOddB) return 1; // Cùng là chẵn hoặc cùng là lẻ -> sắp theo thứ tự tăng dần return numA - numB; }); }; const normalizePortName = (port: string): string => { if (!port) return ""; // Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2" 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" // Get the last part after slash const parts = numbers.split("/"); const last = parts[parts.length - 1]; return `${last}`; }; const convertPortName = (port: string): string => { if (!port) return ""; // Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2" 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" // Get the last part after slash const parts = numbers.split("/"); const last = parts[parts.length - 1]; return `${type?.slice(0, 2)}${last}`; }; const changeShowPort = (status: string) => { localStorage.setItem("show-switch-port", status); 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) => convertPortName(el.interface || "")); return listInterface?.includes(convertPortName(portName)); } }; return loading ? ( ) : (
{dataStation?.switch_control_ip ? ( dataStation?.switch ? ( RenderAPCStatus(dataStation?.switch) ) : ( DISCONNECTED ) ) : ( Switch not available )} {dataStation?.switch?.status !== "CONNECTED" ? ( ) : ( "" )} changeShowPort("all")} /> changeShowPort("on")} /> changeShowPort("off")} /> changeShowPort("config")} />
{listPorts?.length > 0 && ( {listPorts?.map((group, key) => { const isMini = group?.length > 0 && group?.length < 4; const isLarge = group?.length > 20; return ( {isLarge ? (
{group[0]?.name.substring(0, 2) || ""} {sortedPorts(group) .slice(0, sortedPorts(group).length / 2) .filter((el) => checkFilterPort(el.status, el.name) ) ?.map((port, i) => ( el.name === port.name )?.name ? "1px solid #0018ff" : "", }} className={`${ isSubmit ? classes.isDisabled : "" }`} onClick={() => { toggleSelect(port); }} > {normalizePortName(port.name)} poe ))} {sortedPorts(group) .slice( sortedPorts(group).length / 2, sortedPorts(group).length ) .filter((el) => checkFilterPort(el.status, el.name) ) ?.map((port, i) => ( el.name === port.name )?.name ? "1px solid #0018ff" : "", }} className={`${ isSubmit ? classes.isDisabled : "" }`} onClick={() => { toggleSelect(port); }} > {normalizePortName(port.name)} ))}
) : (
{group[0]?.name.substring(0, 2) || ""} {sortedPorts(group) ?.filter((el) => checkFilterPort(el.status, el.name) ) ?.map((port, i) => ( el.name === port.name )?.name ? "1px solid #0018ff" : "", }} className={`${ isSubmit ? classes.isDisabled : "" }`} onClick={() => { toggleSelect(port); }} > {/* */} {normalizePortName(port.name)} ))}
)}
); })}
)}
); };