From 2729feed4ed8883c19df1e434078eaa4f80fcb41 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:10:50 +0700 Subject: [PATCH] Update modal Scenario --- BACKEND/app/controllers/brands_controller.ts | 3 +- .../app/controllers/categories_controller.ts | 3 +- FRONTEND/src/App.tsx | 1392 +++++++++-------- FRONTEND/src/components/BottomToolBar.tsx | 15 +- FRONTEND/src/components/DragTabs.tsx | 14 +- .../src/components/Modal/ModalScenario.tsx | 103 +- FRONTEND/src/untils/types.ts | 15 + 7 files changed, 915 insertions(+), 630 deletions(-) diff --git a/BACKEND/app/controllers/brands_controller.ts b/BACKEND/app/controllers/brands_controller.ts index 6df342b..4b7bf86 100644 --- a/BACKEND/app/controllers/brands_controller.ts +++ b/BACKEND/app/controllers/brands_controller.ts @@ -4,7 +4,8 @@ import type { HttpContext } from '@adonisjs/core/http' export default class BrandsController { // GET /models async index({}: HttpContext) { - return await Brand.all() + const brands = await Brand.all() + return brands.sort((a, b) => a.id - b.id) } // POST /models diff --git a/BACKEND/app/controllers/categories_controller.ts b/BACKEND/app/controllers/categories_controller.ts index 0039c9b..5b3ae83 100644 --- a/BACKEND/app/controllers/categories_controller.ts +++ b/BACKEND/app/controllers/categories_controller.ts @@ -4,7 +4,8 @@ import type { HttpContext } from '@adonisjs/core/http' export default class CategoriesController { // GET /models async index({}: HttpContext) { - return await Category.all() + const categories = await Category.all() + return categories.sort((a, b) => a.id - b.id) } // POST /models diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index c8b486c..c5886ca 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -5,9 +5,34 @@ import "./App.css"; import classes from "./App.module.css"; import componentClasses from "./components/Component.module.css"; -import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Tabs, Text, Container, Flex, MantineProvider, Grid, ScrollArea, LoadingOverlay } from "@mantine/core"; -import type { IScenario, ReceivedFile, ResponseData, TLine, TStation, TUser } from "./untils/types"; +import { + Suspense, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + Tabs, + Text, + Container, + Flex, + MantineProvider, + Grid, + ScrollArea, + LoadingOverlay, +} from "@mantine/core"; +import type { + IScenario, + ReceivedFile, + ResponseData, + TBrands, + TCategories, + TLine, + TStation, + TUser, +} from "./untils/types"; import axios from "axios"; import CardLine from "./components/CardLine"; import { SocketProvider, useSocket } from "./context/SocketContext"; @@ -32,683 +57,808 @@ const apiUrl = import.meta.env.VITE_BACKEND_URL; // Helper: chia mảng thành các chunk theo size const chunkArray = (array: T[], size: number): T[][] => { - const result: T[][] = []; - for (let i = 0; i < array.length; i += size) { - result.push(array.slice(i, i + size)); - } - return result; + const result: T[][] = []; + for (let i = 0; i < array.length; i += size) { + result.push(array.slice(i, i + size)); + } + return result; }; /** * Main Component */ function App() { - const user = useMemo(() => { - return localStorage.getItem("user") && isJsonString(localStorage.getItem("user")) ? JSON.parse(localStorage.getItem("user") || "") : null; - }, []); + const user = useMemo(() => { + return localStorage.getItem("user") && + isJsonString(localStorage.getItem("user")) + ? JSON.parse(localStorage.getItem("user") || "") + : null; + }, []); - if (!user) { - localStorage.removeItem("user"); - window.location.href = "/"; - } + if (!user) { + localStorage.removeItem("user"); + window.location.href = "/"; + } - document.title = "Automation Test"; - const { socket } = useSocket(); - const [stations, setStations] = useState([]); - const [selectedLines, setSelectedLines] = useState([]); - const [activeTab, setActiveTab] = useState("0"); - const [isDisable, setIsDisable] = useState(false); - const [isOpenAddStation, setIsOpenAddStation] = useState(false); - const [isEditStation, setIsEditStation] = useState(false); - const [stationEdit, setStationEdit] = useState(); - const [scenarios, setScenarios] = useState([]); - const [openModalTerminal, setOpenModalTerminal] = useState(false); - const [selectedLine, setSelectedLine] = useState(); - const [loadingTerminal, setLoadingTerminal] = useState(false); - const [usersConnecting, setUsersConnecting] = useState([]); - const [testLogContent, setTestLogContent] = useState(""); - const [isLogModalOpen, setIsLogModalOpen] = useState(false); - const [expandedBottomBar, setExpandedBottomBar] = useState(true); - const [activeTabBottom, setActiveTabBottom] = useState("command"); - const lineBuffersRef = useRef(new Map()); - const flushScheduledRef = useRef(false); + document.title = "Automation Test"; + const { socket } = useSocket(); + const [stations, setStations] = useState([]); + const [selectedLines, setSelectedLines] = useState([]); + const [activeTab, setActiveTab] = useState("0"); + const [isDisable, setIsDisable] = useState(false); + const [isOpenAddStation, setIsOpenAddStation] = useState(false); + const [isEditStation, setIsEditStation] = useState(false); + const [stationEdit, setStationEdit] = useState(); + const [scenarios, setScenarios] = useState([]); + const [openModalTerminal, setOpenModalTerminal] = useState(false); + const [selectedLine, setSelectedLine] = useState(); + const [loadingTerminal, setLoadingTerminal] = useState(false); + const [usersConnecting, setUsersConnecting] = useState([]); + const [testLogContent, setTestLogContent] = useState(""); + const [isLogModalOpen, setIsLogModalOpen] = useState(false); + const [expandedBottomBar, setExpandedBottomBar] = useState(true); + const [activeTabBottom, setActiveTabBottom] = useState("command"); + const lineBuffersRef = useRef(new Map()); + const flushScheduledRef = useRef(false); + const [listBrands, setListBrands] = useState([]); + const [listCategories, setListCategories] = useState([]); - const connectApcSwitch = (station: TStation) => { - if (station?.apc_1_ip && station?.apc_1_port) { - socket?.emit("connect_apc", { - station: station, - apcIp: station?.apc_1_ip, - apcName: "apc_1", - }); - } - if (station?.apc_2_ip && station?.apc_2_port) { - socket?.emit("connect_apc", { - station: station, - apcIp: station?.apc_2_ip, - apcName: "apc_2", - }); - } - if (station?.switch_control_ip && station?.switch_control_port) { - socket?.emit("connect_switch", { - station: station, - ip: station?.switch_control_ip, - }); - } - }; + const connectApcSwitch = (station: TStation) => { + if (station?.apc_1_ip && station?.apc_1_port) { + socket?.emit("connect_apc", { + station: station, + apcIp: station?.apc_1_ip, + apcName: "apc_1", + }); + } + if (station?.apc_2_ip && station?.apc_2_port) { + socket?.emit("connect_apc", { + station: station, + apcIp: station?.apc_2_ip, + apcName: "apc_2", + }); + } + if (station?.switch_control_ip && station?.switch_control_port) { + socket?.emit("connect_switch", { + station: station, + ip: station?.switch_control_ip, + }); + } + }; - // function get list station - const getStation = async () => { - try { - const response = await axios.get(apiUrl + "api/stations"); - if (response.status) { - if (Array.isArray(response.data)) { - setStations( - response.data.map((station) => { - connectApcSwitch(station); - const lines = (station?.lines || []).sort((a: TLine, b: TLine) => a?.lineNumber - b?.lineNumber); - return { ...station, lines }; - }) - ); - } - } - } catch (error) { - console.log("Error get station", error); - } - }; + // function get list station + const getStation = async () => { + try { + const response = await axios.get(apiUrl + "api/stations"); + if (response.status) { + if (Array.isArray(response.data)) { + setStations( + response.data.map((station) => { + connectApcSwitch(station); + const lines = (station?.lines || []).sort( + (a: TLine, b: TLine) => a?.lineNumber - b?.lineNumber + ); + return { ...station, lines }; + }) + ); + } + } + } catch (error) { + console.log("Error get station", error); + } + }; - // function get list station - const getScenarios = async () => { - try { - const response = await axios.get(apiUrl + "api/scenarios"); - if (response.data.status) { - if (Array.isArray(response.data.data.data)) { - setScenarios(response.data.data.data); - } - } - } catch (error) { - console.log("Error get station", error); - } - }; + // function get list station + const getScenarios = async () => { + try { + const response = await axios.get(apiUrl + "api/scenarios"); + if (response.data.status) { + if (Array.isArray(response.data.data.data)) { + setScenarios(response.data.data.data); + } + } + } catch (error) { + console.log("Error get station", error); + } + }; - useEffect(() => { - if (!socket) return; - getStation(); - getScenarios(); - }, [socket]); + // function get list brand + const getBrands = async () => { + try { + const response = await axios.get(apiUrl + "api/brands"); + if (response.data) { + if (response.data && Array.isArray(response.data)) { + setListBrands(response.data); + } + } + } catch (error) { + console.log("Error get brand", error); + } + }; - useEffect(() => { - if (!socket || !stations?.length) return; + // function get list brand + const getCategories = async () => { + try { + const response = await axios.get(apiUrl + "api/categories"); + if (response.data && Array.isArray(response.data)) { + setListCategories(response.data); + } + } catch (error) { + console.log("Error get brand", error); + } + }; - socket.on("line_connected", (data) => updateValueLineStation(data?.lineId, { status: data.status, connecting: false }, data?.stationId)); + useEffect(() => { + if (!socket) return; + getStation(); + getScenarios(); + getBrands(); + getCategories(); + }, [socket]); - socket.on("line_disconnected", (data) => updateValueLineStation(data?.lineId, { status: data.status, connecting: false }, data?.stationId)); + useEffect(() => { + if (!socket || !stations?.length) return; - socket?.on("line_output", (data) => { - const { lineId, data: text } = data; - // updateValueLineStation( - // data?.lineId, - // { netOutput: data.data, commands: data.commands || [] }, - // data?.stationId - // ); + socket.on("line_connected", (data) => + updateValueLineStation( + data?.lineId, + { status: data.status, connecting: false }, + data?.stationId + ) + ); - const buf = lineBuffersRef.current.get(lineId) || ""; - lineBuffersRef.current.set(lineId, buf + text); + socket.on("line_disconnected", (data) => + updateValueLineStation( + data?.lineId, + { status: data.status, connecting: false }, + data?.stationId + ) + ); - if (!flushScheduledRef.current) { - flushScheduledRef.current = true; - setTimeout(() => flushBuffers(), 50); - } - }); + socket?.on("line_output", (data) => { + const { lineId, data: text } = data; + // updateValueLineStation( + // data?.lineId, + // { netOutput: data.data, commands: data.commands || [] }, + // data?.stationId + // ); - socket?.on("line_error", (data) => { - updateValueLineStation(data?.lineId, { netOutput: data.error, connecting: false }, data?.stationId); - }); + const buf = lineBuffersRef.current.get(lineId) || ""; + lineBuffersRef.current.set(lineId, buf + text); - socket?.on("init", (data) => { - if (Array.isArray(data)) { - // console.log(data); - setLoadingTerminal(true); - data.forEach((value) => { - updateValueLineStation(value?.id, { ...value, netOutput: value.output }, value?.stationId); - }); - } - }); + if (!flushScheduledRef.current) { + flushScheduledRef.current = true; + setTimeout(() => flushBuffers(), 50); + } + }); - socket?.on("user_connecting", (data) => { - if (Array.isArray(data)) { - setUsersConnecting(data); - } - }); + socket?.on("line_error", (data) => { + updateValueLineStation( + data?.lineId, + { netOutput: data.error, connecting: false }, + data?.stationId + ); + }); - socket?.on("user_open_cli", (data) => { - setTimeout(() => { - updateValueLineStation( - data.lineId, - { - cliOpened: true, - userEmailOpenCLI: data.userEmailOpenCLI, - userOpenCLI: data.userOpenCLI, - }, - data?.stationId - ); - }, 100); - }); + socket?.on("init", (data) => { + if (Array.isArray(data)) { + // console.log(data); + setLoadingTerminal(true); + data.forEach((value) => { + updateValueLineStation( + value?.id, + { ...value, netOutput: value.output }, + value?.stationId + ); + }); + } + }); - socket?.on("user_close_cli", (data) => { - setTimeout(() => { - updateValueLineStation( - data.lineId, - { - cliOpened: false, - userEmailOpenCLI: undefined, - userOpenCLI: undefined, - }, - data?.stationId - ); - }, 100); - }); + socket?.on("user_connecting", (data) => { + if (Array.isArray(data)) { + setUsersConnecting(data); + } + }); - const receivedFiles: Record = {}; - socket?.on("response_content_log", (data: ResponseData) => { - if (!data.chunk) { - const decoder = new TextDecoder("utf-8"); - const str = decoder.decode(data as unknown as ArrayBuffer); - setTestLogContent(str); - return; - } + socket?.on("user_open_cli", (data) => { + setTimeout(() => { + updateValueLineStation( + data.lineId, + { + cliOpened: true, + userEmailOpenCLI: data.userEmailOpenCLI, + userOpenCLI: data.userOpenCLI, + }, + data?.stationId + ); + }, 100); + }); - const { fileId, chunkIndex, totalChunks, chunk } = data.chunk; + socket?.on("user_close_cli", (data) => { + setTimeout(() => { + updateValueLineStation( + data.lineId, + { + cliOpened: false, + userEmailOpenCLI: undefined, + userOpenCLI: undefined, + }, + data?.stationId + ); + }, 100); + }); - if (!receivedFiles[fileId]) { - receivedFiles[fileId] = { - chunks: [], - receivedChunks: 0, - totalChunks, - }; - } + const receivedFiles: Record = {}; + socket?.on("response_content_log", (data: ResponseData) => { + if (!data.chunk) { + const decoder = new TextDecoder("utf-8"); + const str = decoder.decode(data as unknown as ArrayBuffer); + setTestLogContent(str); + return; + } - let bufferChunk: Buffer; + const { fileId, chunkIndex, totalChunks, chunk } = data.chunk; - if (chunk instanceof ArrayBuffer) { - bufferChunk = Buffer.from(new Uint8Array(chunk)); // ✅ convert properly - } else if (chunk instanceof Uint8Array) { - bufferChunk = Buffer.from(chunk); // ✅ direct support - } else { - bufferChunk = chunk as Buffer; // fallback if server sends Buffer - } + if (!receivedFiles[fileId]) { + receivedFiles[fileId] = { + chunks: [], + receivedChunks: 0, + totalChunks, + }; + } - receivedFiles[fileId].chunks[chunkIndex] = bufferChunk; - receivedFiles[fileId].receivedChunks++; + let bufferChunk: Buffer; - if (receivedFiles[fileId].receivedChunks === totalChunks) { - const fileBuffer = Buffer.concat(receivedFiles[fileId].chunks); - const decoder = new TextDecoder("utf-8"); - const str = decoder.decode(fileBuffer); + if (chunk instanceof ArrayBuffer) { + bufferChunk = Buffer.from(new Uint8Array(chunk)); // ✅ convert properly + } else if (chunk instanceof Uint8Array) { + bufferChunk = Buffer.from(chunk); // ✅ direct support + } else { + bufferChunk = chunk as Buffer; // fallback if server sends Buffer + } - setTestLogContent(str); - delete receivedFiles[fileId]; // cleanup ✅ - } - }); + receivedFiles[fileId].chunks[chunkIndex] = bufferChunk; + receivedFiles[fileId].receivedChunks++; - socket?.on("data_textfsm", (data) => { - setTimeout(() => { - updateValueLineStation( - data.lineId, - { - data: data.data, - inventory: data.inventory, - latestScenario: data.latestScenario, - }, - data?.stationId - ); - }, 100); - }); + if (receivedFiles[fileId].receivedChunks === totalChunks) { + const fileBuffer = Buffer.concat(receivedFiles[fileId].chunks); + const decoder = new TextDecoder("utf-8"); + const str = decoder.decode(fileBuffer); - socket?.on("update_ticket", (data) => { - setTimeout(() => { - updateValueLineStation( - data.lineId, - { - tickets: data.data, - }, - data?.stationId - ); - }, 100); - }); + setTestLogContent(str); + delete receivedFiles[fileId]; // cleanup ✅ + } + }); - socket?.on("update_baud", (data) => { - setTimeout(() => { - updateValueLineStation( - data.lineId, - { - baud: data.data, - }, - data?.stationId - ); - }, 100); - }); + socket?.on("data_textfsm", (data) => { + setTimeout(() => { + updateValueLineStation( + data.lineId, + { + data: data.data, + inventory: data.inventory, + latestScenario: data.latestScenario, + }, + data?.stationId + ); + }, 100); + }); - socket?.on("line_connecting", (data) => { - setTimeout(() => { - updateValueLineStation(data?.lineId, { connecting: true }, data?.stationId); - }, 100); - }); + socket?.on("update_ticket", (data) => { + setTimeout(() => { + updateValueLineStation( + data.lineId, + { + tickets: data.data, + }, + data?.stationId + ); + }, 100); + }); - socket?.on("running_scenario", (data) => { - setTimeout(() => { - updateValueLineStation(data?.lineId, { runningScenario: data?.title || "" }, data?.stationId); - }, 100); - }); + socket?.on("update_baud", (data) => { + setTimeout(() => { + updateValueLineStation( + data.lineId, + { + baud: data.data, + }, + data?.stationId + ); + }, 100); + }); - // ✅ cleanup on unmount or when socket changes - return () => { - socket.off("init"); - socket.off("line_output"); - socket.off("line_error"); - socket.off("line_connected"); - socket.off("line_disconnected"); - socket.off("user_connecting"); - socket.off("user_open_cli"); - socket.off("user_close_cli"); - socket.off("response_content_log"); - socket.off("data_textfsm"); - socket.off("update_ticket"); - socket.off("update_baud"); - socket.off("line_connecting"); - socket.off("running_scenario"); - }; - }, [socket, stations, selectedLine]); + socket?.on("line_connecting", (data) => { + setTimeout(() => { + updateValueLineStation( + data?.lineId, + { connecting: true }, + data?.stationId + ); + }, 100); + }); - const flushBuffers = useCallback(() => { - setStations((prev) => - prev.map((station) => ({ - ...station, - lines: station.lines.map((line) => { - const buffered = lineBuffersRef.current.get(line.id || 0); - if (!buffered) return line; // không có update - updateValueSelectedLine(line?.id || 0, { netOutput: buffered }); - return { - ...line, - netOutput: (line.netOutput || "") + buffered, - output: buffered, - loadingOutput: line.loadingOutput ? false : true, - }; - }), - })) - ); - // clear - lineBuffersRef.current.clear(); - flushScheduledRef.current = false; - }, []); + socket?.on("running_scenario", (data) => { + setTimeout(() => { + updateValueLineStation( + data?.lineId, + { runningScenario: data?.title || "" }, + data?.stationId + ); + }, 100); + }); - const updateValueLineStation = useCallback((lineId: number, updates: Partial, stationId?: number) => { - setStations((prevStations) => - prevStations?.map((station: TStation) => - station.id === stationId - ? { - ...station, - lines: station.lines?.map((lineItem: TLine) => { - if (lineItem.id !== lineId) return lineItem; + // ✅ cleanup on unmount or when socket changes + return () => { + socket.off("init"); + socket.off("line_output"); + socket.off("line_error"); + socket.off("line_connected"); + socket.off("line_disconnected"); + socket.off("user_connecting"); + socket.off("user_open_cli"); + socket.off("user_close_cli"); + socket.off("response_content_log"); + socket.off("data_textfsm"); + socket.off("update_ticket"); + socket.off("update_baud"); + socket.off("line_connecting"); + socket.off("running_scenario"); + }; + }, [socket, stations, selectedLine]); - const isNetOutput = typeof updates?.netOutput !== "undefined"; + const flushBuffers = useCallback(() => { + setStations((prev) => + prev.map((station) => ({ + ...station, + lines: station.lines.map((line) => { + const buffered = lineBuffersRef.current.get(line.id || 0); + if (!buffered) return line; // không có update + updateValueSelectedLine(line?.id || 0, { netOutput: buffered }); + return { + ...line, + netOutput: (line.netOutput || "") + buffered, + output: buffered, + loadingOutput: line.loadingOutput ? false : true, + }; + }), + })) + ); + // clear + lineBuffersRef.current.clear(); + flushScheduledRef.current = false; + }, []); - return { - ...lineItem, - ...updates, - lineNumber: lineItem.lineNumber, - line_number: lineItem.line_number, - ...(isNetOutput && { - netOutput: (lineItem.netOutput || "") + (updates.netOutput || ""), - output: updates.netOutput, // Nếu netOutput thì update luôn output - loadingOutput: lineItem.loadingOutput ? false : true, - }), - }; - }), - } - : station - ) - ); + const updateValueLineStation = useCallback( + (lineId: number, updates: Partial, stationId?: number) => { + setStations((prevStations) => + prevStations?.map((station: TStation) => + station.id === stationId + ? { + ...station, + lines: station.lines?.map((lineItem: TLine) => { + if (lineItem.id !== lineId) return lineItem; - // Update selectedLine nếu nó đang được chọn - setSelectedLine((prevSelected) => { - if (!prevSelected || prevSelected.id !== lineId) return prevSelected; + const isNetOutput = typeof updates?.netOutput !== "undefined"; - const isNetOutput = typeof updates?.netOutput !== "undefined"; + return { + ...lineItem, + ...updates, + lineNumber: lineItem.lineNumber, + line_number: lineItem.line_number, + ...(isNetOutput && { + netOutput: + (lineItem.netOutput || "") + (updates.netOutput || ""), + output: updates.netOutput, // Nếu netOutput thì update luôn output + loadingOutput: lineItem.loadingOutput ? false : true, + }), + }; + }), + } + : station + ) + ); - return { - ...prevSelected, - ...updates, - ...(isNetOutput && { - netOutput: (prevSelected.netOutput || "") + (updates.netOutput || ""), - output: updates.netOutput, - loadingOutput: prevSelected.loadingOutput ? false : true, - }), - }; - }); - }, []); + // Update selectedLine nếu nó đang được chọn + setSelectedLine((prevSelected) => { + if (!prevSelected || prevSelected.id !== lineId) return prevSelected; - const updateValueSelectedLine = useCallback((lineId: number, updates: Partial) => { - // Update selectedLine nếu nó đang được chọn - setSelectedLine((prevSelected) => { - if (!prevSelected || prevSelected.id !== lineId) return prevSelected; + const isNetOutput = typeof updates?.netOutput !== "undefined"; - const isNetOutput = typeof updates?.netOutput !== "undefined"; + return { + ...prevSelected, + ...updates, + ...(isNetOutput && { + netOutput: + (prevSelected.netOutput || "") + (updates.netOutput || ""), + output: updates.netOutput, + loadingOutput: prevSelected.loadingOutput ? false : true, + }), + }; + }); + }, + [] + ); - return { - ...prevSelected, - ...updates, - ...(isNetOutput && { - netOutput: (prevSelected.netOutput || "") + (updates.netOutput || ""), - output: updates.netOutput, - loadingOutput: prevSelected.loadingOutput ? false : true, - }), - }; - }); - }, []); + const updateValueSelectedLine = useCallback( + (lineId: number, updates: Partial) => { + // Update selectedLine nếu nó đang được chọn + setSelectedLine((prevSelected) => { + if (!prevSelected || prevSelected.id !== lineId) return prevSelected; - // const getLine = (lineId: number, stationId: number) => { - // const station = stations?.find((sta) => sta.id === stationId); - // if (station) { - // const line = station.lines?.find((li) => li.id === lineId); - // return line; - // } else return null; - // }; + const isNetOutput = typeof updates?.netOutput !== "undefined"; - const openTerminal = (line: TLine) => { - setOpenModalTerminal(true); - const data = { ...line }; - if (!line.userEmailOpenCLI) { - data.cliOpened = true; - data.userEmailOpenCLI = user?.email; - data.userOpenCLI = user?.userName; - socket?.emit("open_cli", { - lineId: line.id, - stationId: line.stationId || line.station_id, - userEmail: user?.email, - userName: user?.userName, - }); - } - setSelectedLine(data); - }; + return { + ...prevSelected, + ...updates, + ...(isNetOutput && { + netOutput: + (prevSelected.netOutput || "") + (updates.netOutput || ""), + output: updates.netOutput, + loadingOutput: prevSelected.loadingOutput ? false : true, + }), + }; + }); + }, + [] + ); - useEffect(() => { - if (!expandedBottomBar) { - setActiveTabBottom("command"); - } - }, [expandedBottomBar]); + // const getLine = (lineId: number, stationId: number) => { + // const station = stations?.find((sta) => sta.id === stationId); + // if (station) { + // const line = station.lines?.find((li) => li.id === lineId); + // return line; + // } else return null; + // }; - return ( - - ( - - - - - - {station.lines.length > 0 ? ( - station.lines.length < 9 ? ( - - {station.lines.map((line, i) => ( - - - - ))} - - ) : ( - // >= 9 lines: chia làm 2 cột, mỗi cột chứa 1/2 số line, - // mỗi cột hiển thị 2 item trên một "hàng" như ví dụ yêu cầu - (() => { - // const total = station.lines.length; - const half = 8; - const leftLines = station.lines.slice(0, half); - const rightLines = station.lines.slice(half); + const openTerminal = (line: TLine) => { + setOpenModalTerminal(true); + const data = { ...line }; + if (!line.userEmailOpenCLI) { + data.cliOpened = true; + data.userEmailOpenCLI = user?.email; + data.userOpenCLI = user?.userName; + socket?.emit("open_cli", { + lineId: line.id, + stationId: line.stationId || line.station_id, + userEmail: user?.email, + userName: user?.userName, + }); + } + setSelectedLine(data); + }; - const leftChunks = chunkArray(leftLines, 2); - const rightChunks = chunkArray(rightLines, 2); - const numRows = Math.max(leftChunks.length, rightChunks.length); + useEffect(() => { + if (!expandedBottomBar) { + setActiveTabBottom("command"); + } + }, [expandedBottomBar]); - return ( - <> - {Array.from({ length: numRows }).map((_, rowIndex) => { - const leftRow = leftChunks[rowIndex] || []; - const rightRow = rightChunks[rowIndex] || []; + return ( + + ( + + + + + + {station.lines.length > 0 ? ( + station.lines.length < 9 ? ( + + {station.lines.map((line, i) => ( + + + + ))} + + ) : ( + // >= 9 lines: chia làm 2 cột, mỗi cột chứa 1/2 số line, + // mỗi cột hiển thị 2 item trên một "hàng" như ví dụ yêu cầu + (() => { + // const total = station.lines.length; + const half = 8; + const leftLines = station.lines.slice(0, half); + const rightLines = station.lines.slice(half); - return ( - - {/* Cột A */} - - - {leftRow.map((line, i) => ( - - - - ))} - - + const leftChunks = chunkArray(leftLines, 2); + const rightChunks = chunkArray(rightLines, 2); + const numRows = Math.max( + leftChunks.length, + rightChunks.length + ); - {/* Cột B */} - - - {rightRow.map((line, i) => ( - - - - ))} - - - - ); - })} - - ); - })() - ) - ) : ( - - No lines configured - - )} - - - - - - - ))} - onChange={(id) => { - const station = stations.find((el) => el.id === Number(activeTab)); - if (station) { - (station?.lines || [])?.forEach((el) => { - if (el?.userOpenCLI === user?.userName) - socket?.emit("close_cli", { - lineId: el?.id, - stationId: Number(activeTab), - }); - }); - } - setActiveTab(id?.toString() || "0"); - setSelectedLines([]); - setLoadingTerminal(false); - setTimeout(() => { - setLoadingTerminal(true); - }, 500); - }} - setActive={setActiveTab} - active={activeTab} - onSendCommand={(value) => { - const listLine = selectedLines.length ? selectedLines : stations.find((el) => el.id === Number(activeTab))?.lines; - if (listLine?.length) { - socket?.emit("write_command_line_from_web", { - lineIds: listLine.map((line) => line.id), - stationId: Number(activeTab), - command: value + "\n", - }); - setTimeout(() => { - socket?.emit("write_command_line_from_web", { - lineIds: listLine.map((line) => line.id), - stationId: Number(activeTab), - command: " \n", - }); - }, 1000); - } - }} - /> + return ( + <> + {Array.from({ length: numRows }).map( + (_, rowIndex) => { + const leftRow = leftChunks[rowIndex] || []; + const rightRow = rightChunks[rowIndex] || []; - { - setIsOpenAddStation(false); - setIsEditStation(false); - setStationEdit(undefined); - }} - isEdit={isEditStation} - setStations={setStations} - setActiveTab={(id: string) => { - setActiveTab(id); - setLoadingTerminal(false); - setTimeout(() => { - setLoadingTerminal(true); - }, 100); - }} - stations={stations} - socket={socket} - /> + return ( + + {/* Cột A */} + + + {leftRow.map((line, i) => ( + + + + ))} + + - { - setOpenModalTerminal(false); - setSelectedLine(undefined); - }} - line={selectedLine} - socket={socket} - stationItem={stations.find((el) => el.id === Number(activeTab))} - scenarios={scenarios} - /> - - ); + {/* Cột B */} + + + {rightRow.map((line, i) => ( + + + + ))} + + + + ); + } + )} + + ); + })() + ) + ) : ( + + No lines configured + + )} + + + + + + + ))} + onChange={(id) => { + const station = stations.find((el) => el.id === Number(activeTab)); + if (station) { + (station?.lines || [])?.forEach((el) => { + if (el?.userOpenCLI === user?.userName) + socket?.emit("close_cli", { + lineId: el?.id, + stationId: Number(activeTab), + }); + }); + } + setActiveTab(id?.toString() || "0"); + setSelectedLines([]); + setLoadingTerminal(false); + setTimeout(() => { + setLoadingTerminal(true); + }, 500); + }} + setActive={setActiveTab} + active={activeTab} + onSendCommand={(value) => { + const listLine = selectedLines.length + ? selectedLines + : stations.find((el) => el.id === Number(activeTab))?.lines; + if (listLine?.length) { + socket?.emit("write_command_line_from_web", { + lineIds: listLine.map((line) => line.id), + stationId: Number(activeTab), + command: value + "\n", + }); + setTimeout(() => { + socket?.emit("write_command_line_from_web", { + lineIds: listLine.map((line) => line.id), + stationId: Number(activeTab), + command: " \n", + }); + }, 1000); + } + }} + listBrands={listBrands} + listCategories={listCategories} + /> + + { + setIsOpenAddStation(false); + setIsEditStation(false); + setStationEdit(undefined); + }} + isEdit={isEditStation} + setStations={setStations} + setActiveTab={(id: string) => { + setActiveTab(id); + setLoadingTerminal(false); + setTimeout(() => { + setLoadingTerminal(true); + }, 100); + }} + stations={stations} + socket={socket} + /> + + { + setOpenModalTerminal(false); + setSelectedLine(undefined); + }} + line={selectedLine} + socket={socket} + stationItem={stations.find((el) => el.id === Number(activeTab))} + scenarios={scenarios} + /> + + ); } export default function Main() { - const user = useMemo(() => { - return localStorage.getItem("user") && isJsonString(localStorage.getItem("user")) ? JSON.parse(localStorage.getItem("user") || "") : null; - }, []); + const user = useMemo(() => { + return localStorage.getItem("user") && + isJsonString(localStorage.getItem("user")) + ? JSON.parse(localStorage.getItem("user") || "") + : null; + }, []); - return ( - - - }> - - {user ? ( - - ) : ( - - - - )} - - - - ); + return ( + + + + } + > + + {user ? ( + + ) : ( + + + + )} + + + + ); } diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx index 85e2723..eee9149 100644 --- a/FRONTEND/src/components/BottomToolBar.tsx +++ b/FRONTEND/src/components/BottomToolBar.tsx @@ -13,7 +13,14 @@ import { } from "@mantine/core"; import { useEffect, useMemo, useRef, useState } from "react"; import classes from "./Component.module.css"; -import type { IScenario, TLine, TStation, TUser } from "../untils/types"; +import type { + IScenario, + TBrands, + TCategories, + TLine, + TStation, + TUser, +} from "../untils/types"; import type { Socket } from "socket.io-client"; import { ButtonDPELP, ButtonSelect } from "./ButtonAction"; import DrawerLogs from "./Drawer/DrawerLogs"; @@ -47,6 +54,8 @@ interface TabsProps { activeTabBottom: string; setActiveTabBottom: (value: React.SetStateAction) => void; isExpand: boolean; + listBrands: TBrands[]; + listCategories: TCategories[]; } // Component cho từng Scenario Card @@ -379,6 +388,8 @@ const BottomToolBar = ({ activeTabBottom, isExpand, stationId, + listBrands, + listCategories, }: TabsProps) => { const user = useMemo(() => { return localStorage.getItem("user") && @@ -1067,6 +1078,8 @@ const BottomToolBar = ({ setScenarios={setScenarios} externalOpened={openDrawerScenario} onExternalClose={() => setOpenDrawerScenario(false)} + listBrands={listBrands} + listCategories={listCategories} /> ); diff --git a/FRONTEND/src/components/DragTabs.tsx b/FRONTEND/src/components/DragTabs.tsx index 7b69b2c..3930e01 100644 --- a/FRONTEND/src/components/DragTabs.tsx +++ b/FRONTEND/src/components/DragTabs.tsx @@ -37,7 +37,13 @@ import { IconUsersGroup, } from "@tabler/icons-react"; import classes from "./Component.module.css"; -import type { IScenario, TStation, TUser } from "../untils/types"; +import type { + IScenario, + TBrands, + TCategories, + TStation, + TUser, +} from "../untils/types"; import type { Socket } from "socket.io-client"; import ModalHistory from "./Modal/ModalHistory"; import ModalConfig from "./Modal/ModalConfig"; @@ -60,6 +66,8 @@ interface DraggableTabsProps { onSendCommand: (value: string) => void; scenarios: IScenario[]; setScenarios: (value: React.SetStateAction) => void; + listBrands: TBrands[]; + listCategories: TCategories[]; } function SortableTab({ @@ -123,6 +131,8 @@ export default function DraggableTabs({ setActive, scenarios, setScenarios, + listBrands, + listCategories, }: DraggableTabsProps) { const user = useMemo(() => { return localStorage.getItem("user") && @@ -396,6 +406,8 @@ export default function DraggableTabs({ setScenarios={setScenarios} externalOpened={openDrawerScenario} onExternalClose={() => setOpenDrawerScenario(false)} + listBrands={listBrands} + listCategories={listCategories} /> ); diff --git a/FRONTEND/src/components/Modal/ModalScenario.tsx b/FRONTEND/src/components/Modal/ModalScenario.tsx index cacc5b6..041391a 100644 --- a/FRONTEND/src/components/Modal/ModalScenario.tsx +++ b/FRONTEND/src/components/Modal/ModalScenario.tsx @@ -10,15 +10,23 @@ import { Flex, CloseButton, Checkbox, + Select, + TagsInput, } from "@mantine/core"; import classes from "../Component.module.css"; import TableRows from "./Scenario/TableRows"; import { useEffect, useState } from "react"; import { useForm } from "@mantine/form"; import DialogConfirm from "../DialogConfirm"; -import type { IBodyScenario, IScenario } from "../../untils/types"; +import type { + IBodyScenario, + IScenario, + TBrands, + TCategories, +} from "../../untils/types"; import axios from "axios"; import { notifications } from "@mantine/notifications"; +import { isJsonString } from "../../untils/helper"; const apiUrl = import.meta.env.VITE_BACKEND_URL; function ModalScenario({ @@ -26,11 +34,15 @@ function ModalScenario({ setScenarios, externalOpened, onExternalClose, + listBrands, + listCategories, }: { scenarios: IScenario[]; setScenarios: (value: React.SetStateAction) => void; externalOpened?: boolean; onExternalClose?: () => void; + listBrands: TBrands[]; + listCategories: TCategories[]; }) { const [opened, { close }] = useDisclosure(false); @@ -60,9 +72,13 @@ function ModalScenario({ repeat: "1", }, ] as IBodyScenario[], - timeout: "30000", + timeout: "30", isReboot: false, send_result: false, + note: "", + series: [] as string[], + brandId: "", + categoryId: "", }, validate: { title: (value) => { @@ -129,6 +145,10 @@ function ModalScenario({ send_result: form.values.send_result, body: body, timeout: Number(form.values.timeout), + categoryId: Number(form.values.categoryId), + brandId: Number(form.values.brandId), + note: form.values.note, + series: form.values.series, }; const url = isEdit ? "api/scenarios/update" : "api/scenarios/create"; const res = await axios.post( @@ -219,7 +239,7 @@ function ModalScenario({ right: 0, bottom: 0, backgroundColor: "rgba(0,0,0,0.6)", - zIndex: 100000, + zIndex: 100, display: "flex", alignItems: "center", justifyContent: "center", @@ -248,13 +268,13 @@ function ModalScenario({ - + {isEdit ? "✏️ Edit Scenarios" : "➕ Add Scenarios"} @@ -319,6 +339,25 @@ function ModalScenario({ "send_result", scenario.send_result ); + form.setFieldValue("note", scenario.note); + form.setFieldValue( + "brandId", + scenario.brand_id + ? scenario.brand_id.toString() + : scenario?.brandId?.toString() + ); + form.setFieldValue( + "categoryId", + scenario.category_id + ? scenario.category_id.toString() + : scenario.categoryId.toString() + ); + form.setFieldValue( + "series", + isJsonString(scenario?.series) + ? JSON.parse(scenario.series) + : [] + ); } }} > @@ -425,6 +464,60 @@ function ModalScenario({ + + + + ({ + value: el.id.toString(), + label: el.name, + }))} + value={form.values.categoryId || null} + onChange={(value) => + form.setFieldValue("categoryId", value || "") + } + /> + + + + form.setFieldValue("note", e.target.value) + } + required + /> + + + + form.setFieldValue("series", value) + } + /> + + +