This commit is contained in:
nguyentrungthat 2025-10-30 09:56:09 +07:00
parent 240dfdff2c
commit 54ed6c95e8
8 changed files with 111 additions and 44 deletions

View File

@ -8,38 +8,39 @@ export default class StationsController {
} }
public async store({ request, response }: HttpContext) { public async store({ request, response }: HttpContext) {
let payload = request.only(Array.from(Station.$columnsDefinitions.keys())) const payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
let lines: Line[] = request.body().lines || [] const lines: Line[] = request.body().lines || []
try { try {
const stationName = await Station.findBy('name', payload.name) // Kiểm tra trùng name hoặc ip
if (stationName) return response.status(400).json({ message: 'Station name exist!' }) if (await Station.findBy('name', payload.name))
return response.status(400).json({ message: 'Station name exist!' })
const stationIP = await Station.findBy('ip', payload.ip) if (await Station.findBy('ip', payload.ip))
if (stationIP) return response.status(400).json({ message: 'Ip of station is exist!' }) return response.status(400).json({ message: 'Ip of station is exist!' })
// Tạo station
const station = await Station.create(payload) const station = await Station.create(payload)
// Xử lý lines (chờ từng dòng)
const newLines: Line[] = [] const newLines: Line[] = []
if (lines && Array.isArray(lines)) { for (const line of lines) {
lines.forEach(async (line) => {
if (line.id) { if (line.id) {
const value = await Line.find(line.id) const existing = await Line.find(line.id)
if (value) { if (existing) {
Object.assign(value, line) Object.assign(existing, line)
await value.save() await existing.save()
newLines.push(value) newLines.push(existing)
} else { continue
const value1 = await Line.create({ ...line, stationId: station.id })
newLines.push(value1)
} }
} else {
const value2 = await Line.create({ ...line, stationId: station.id })
newLines.push(value2)
}
})
} }
// Tạo mới nếu không tồn tại
const created = await Line.create({ ...line, stationId: station.id })
newLines.push(created)
}
// Lấy lại station kèm lines
const resStation = await Station.query().where('id', station.id).preload('lines') const resStation = await Station.query().where('id', station.id).preload('lines')
return response.created({ return response.created({
@ -48,7 +49,12 @@ export default class StationsController {
data: resStation.map((el) => ({ ...el.$original, lines: newLines })), data: resStation.map((el) => ({ ...el.$original, lines: newLines })),
}) })
} catch (error) { } catch (error) {
return response.badRequest({ error: error, message: 'Station create failed', status: false }) console.error(error)
return response.badRequest({
error,
message: 'Station create failed',
status: false,
})
} }
} }
@ -65,6 +71,13 @@ export default class StationsController {
let lines: Line[] = request.body().lines || [] let lines: Line[] = request.body().lines || []
try { try {
// Kiểm tra trùng name hoặc ip
if (await Station.findBy('name', payload.name))
return response.status(400).json({ message: 'Station name exist!' })
if (await Station.findBy('ip', payload.ip))
return response.status(400).json({ message: 'Ip of station is exist!' })
const station = await Station.find(request.body().id) const station = await Station.find(request.body().id)
// If the station does not exist, return a 404 response // If the station does not exist, return a 404 response

6
FRONTEND/.gitignore vendored
View File

@ -22,3 +22,9 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Secrets
.env
.env.local
.env.production.local
.env.development.local

View File

@ -37,6 +37,7 @@ import ModalTerminal from "./components/ModalTerminal";
import PageLogin from "./components/Authentication/LoginPage"; import PageLogin from "./components/Authentication/LoginPage";
import DrawerLogs from "./components/DrawerLogs"; import DrawerLogs from "./components/DrawerLogs";
import DraggableTabs from "./components/DragTabs"; import DraggableTabs from "./components/DragTabs";
import { isJsonString } from "./untils/helper";
const apiUrl = import.meta.env.VITE_BACKEND_URL; const apiUrl = import.meta.env.VITE_BACKEND_URL;
@ -46,11 +47,15 @@ const apiUrl = import.meta.env.VITE_BACKEND_URL;
function App() { function App() {
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
typeof localStorage.getItem("user") === "string" isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "") ? JSON.parse(localStorage.getItem("user") || "")
: null; : null;
}, []); }, []);
if (!user) window.location.href = "/";
if (!user) {
localStorage.removeItem("user");
window.location.href = "/";
}
document.title = "Automation Test"; document.title = "Automation Test";
const { socket } = useSocket(); const { socket } = useSocket();
@ -484,6 +489,8 @@ function App() {
setLoadingTerminal(true); setLoadingTerminal(true);
}, 100); }, 100);
}} }}
setActive={setActiveTab}
active={activeTab}
/> />
<StationSetting <StationSetting
@ -496,9 +503,14 @@ function App() {
}} }}
isEdit={isEditStation} isEdit={isEditStation}
setStations={setStations} setStations={setStations}
setActiveTab={() => setActiveTab={(id: string) => {
setActiveTab(stations.length ? stations[0]?.id.toString() : "0") setActiveTab(id);
} setLoadingTerminal(false);
setTimeout(() => {
setLoadingTerminal(true);
}, 100);
}}
stations={stations}
/> />
<ModalTerminal <ModalTerminal
@ -523,7 +535,13 @@ function App() {
} }
export default function Main() { export default function Main() {
const user = localStorage.getItem("user"); const user = useMemo(() => {
return localStorage.getItem("user") &&
isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "")
: null;
}, []);
return ( return (
<MantineProvider> <MantineProvider>
<SocketProvider> <SocketProvider>

View File

@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { Anchor, Image, Paper, Text } from "@mantine/core"; import { Anchor, Image, Paper, Text } from "@mantine/core";
import Login from "./Login"; import Login from "./Login";
import Register from "./Register"; import Register from "./Register";
@ -7,6 +7,12 @@ import classes from "./AuthenticationImage.module.css";
export const PageLogin = () => { export const PageLogin = () => {
const [isRegister, setIsRegister] = useState(false); const [isRegister, setIsRegister] = useState(false);
useEffect(() => {
if (localStorage.getItem("user")) {
localStorage.removeItem("user");
}
}, []);
return ( return (
<div <div
style={{ style={{

View File

@ -41,6 +41,8 @@ interface DraggableTabsProps {
setIsEditStation: (value: React.SetStateAction<boolean>) => void; setIsEditStation: (value: React.SetStateAction<boolean>) => void;
setIsOpenAddStation: (value: React.SetStateAction<boolean>) => void; setIsOpenAddStation: (value: React.SetStateAction<boolean>) => void;
setStationEdit: (value: React.SetStateAction<TStation | undefined>) => void; setStationEdit: (value: React.SetStateAction<TStation | undefined>) => void;
active: string;
setActive: (value: React.SetStateAction<string>) => void;
} }
function SortableTab({ function SortableTab({
@ -99,6 +101,8 @@ export default function DraggableTabs({
setIsEditStation, setIsEditStation,
setIsOpenAddStation, setIsOpenAddStation,
setStationEdit, setStationEdit,
active,
setActive,
}: DraggableTabsProps) { }: DraggableTabsProps) {
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
@ -109,9 +113,9 @@ export default function DraggableTabs({
const [tabs, setTabs] = useState<TStation[]>(tabsData); const [tabs, setTabs] = useState<TStation[]>(tabsData);
const [isChangeTab, setIsChangeTab] = useState<boolean>(false); const [isChangeTab, setIsChangeTab] = useState<boolean>(false);
const [isSetActive, setIsSetActive] = useState<boolean>(false); const [isSetActive, setIsSetActive] = useState<boolean>(false);
const [active, setActive] = useState<string | null>( // const [active, setActive] = useState<string | null>(
tabsData?.length > 0 ? tabsData[0]?.id.toString() : null // tabsData?.length > 0 ? tabsData[0]?.id.toString() : null
); // );
const sensors = useSensors(useSensor(PointerSensor)); const sensors = useSensors(useSensor(PointerSensor));
@ -119,10 +123,12 @@ export default function DraggableTabs({
useEffect(() => { useEffect(() => {
if (isChangeTab) { if (isChangeTab) {
setTabs((pre) => setTabs((pre) =>
pre.map((t) => { pre
.map((t) => {
const updatedTab = tabsData.find((td) => td.id === t.id); const updatedTab = tabsData.find((td) => td.id === t.id);
return updatedTab ? updatedTab : t; return updatedTab ? updatedTab : t;
}) })
.filter((t) => (tabsData.find((td) => td.id === t.id) ? true : false))
); );
} else { } else {
const saved = localStorage.getItem(storageKey); const saved = localStorage.getItem(storageKey);
@ -193,7 +199,7 @@ export default function DraggableTabs({
setIsChangeTab(false); setIsChangeTab(false);
setIsSetActive(false); setIsSetActive(false);
setTabs([]); setTabs([]);
setActive(null); setActive("0");
}; };
}, []); }, []);
@ -208,7 +214,7 @@ export default function DraggableTabs({
onChange={(val) => { onChange={(val) => {
setIsChangeTab(true); setIsChangeTab(true);
onChange(val); onChange(val);
setActive(val); setActive(val || "0");
}} }}
w={w} w={w}
> >
@ -242,7 +248,7 @@ export default function DraggableTabs({
))} ))}
</SortableContext> </SortableContext>
<Flex gap={"sm"}> <Flex gap={"md"} ms={"xs"} align={"center"}>
{Number(active) ? ( {Number(active) ? (
<ActionIcon <ActionIcon
title="Edit Station" title="Edit Station"

View File

@ -36,13 +36,15 @@ const StationSetting = ({
dataStation, dataStation,
setStations, setStations,
setActiveTab, setActiveTab,
stations,
}: { }: {
isOpen: boolean; isOpen: boolean;
isEdit: boolean; isEdit: boolean;
onClose: () => void; onClose: () => void;
dataStation?: TStation; dataStation?: TStation;
setStations: (value: React.SetStateAction<TStation[]>) => void; setStations: (value: React.SetStateAction<TStation[]>) => void;
setActiveTab: () => void; setActiveTab: (value: string) => void;
stations: TStation[];
}) => { }) => {
const [lines, setLines] = useState<TLine[]>([lineInit]); const [lines, setLines] = useState<TLine[]>([lineInit]);
const [openConfirm, setOpenConfirm] = useState<boolean>(false); const [openConfirm, setOpenConfirm] = useState<boolean>(false);
@ -258,8 +260,11 @@ const StationSetting = ({
id: dataStation?.id, id: dataStation?.id,
}); });
if (response.data.status) { if (response.data.status) {
setStations((pre) => pre.filter((el) => el.id !== dataStation?.id)); const listStations = stations.filter((el) => el.id !== dataStation?.id);
setActiveTab(); setStations(listStations);
setActiveTab(
listStations.length ? listStations[0]?.id.toString() : "0"
);
notifications.show({ notifications.show({
title: "Success", title: "Success",
message: response.data.message, message: response.data.message,

View File

@ -8,6 +8,7 @@ import React, {
import { io, Socket } from "socket.io-client"; import { io, Socket } from "socket.io-client";
import { SOCKET_EVENTS } from "../untils/constanst"; import { SOCKET_EVENTS } from "../untils/constanst";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { isJsonString } from "../untils/helper";
interface ISocketContext { interface ISocketContext {
socket: Socket | null; socket: Socket | null;
@ -23,7 +24,7 @@ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
typeof localStorage.getItem("user") === "string" isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "") ? JSON.parse(localStorage.getItem("user") || "")
: null; : null;
}, []); }, []);

View File

@ -7,3 +7,15 @@ export const passwordRegex =
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&#])[A-Za-z\d@$!%*?&#]{8,}$/; /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&#])[A-Za-z\d@$!%*?&#]{8,}$/;
export const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/; export const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
export function isJsonString(str: string | null) {
if (typeof str !== "string") return false;
try {
const parsed = JSON.parse(str);
// Kiểm tra xem parsed có phải là object hoặc array thật sự
return parsed !== null && typeof parsed === "object";
} catch (e) {
console.log("Error isJsonString", e);
return false;
}
}