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) {
let payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
let lines: Line[] = request.body().lines || []
const payload = request.only(Array.from(Station.$columnsDefinitions.keys()))
const lines: Line[] = request.body().lines || []
try {
const stationName = await Station.findBy('name', payload.name)
if (stationName) return response.status(400).json({ message: 'Station name exist!' })
// 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!' })
const stationIP = await Station.findBy('ip', payload.ip)
if (stationIP) return response.status(400).json({ message: 'Ip of station is exist!' })
if (await Station.findBy('ip', payload.ip))
return response.status(400).json({ message: 'Ip of station is exist!' })
// Tạo station
const station = await Station.create(payload)
// Xử lý lines (chờ từng dòng)
const newLines: Line[] = []
if (lines && Array.isArray(lines)) {
lines.forEach(async (line) => {
if (line.id) {
const value = await Line.find(line.id)
if (value) {
Object.assign(value, line)
await value.save()
newLines.push(value)
} else {
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)
for (const line of lines) {
if (line.id) {
const existing = await Line.find(line.id)
if (existing) {
Object.assign(existing, line)
await existing.save()
newLines.push(existing)
continue
}
})
}
// 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')
return response.created({
@ -48,7 +49,12 @@ export default class StationsController {
data: resStation.map((el) => ({ ...el.$original, lines: newLines })),
})
} 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 || []
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)
// If the station does not exist, return a 404 response

6
FRONTEND/.gitignore vendored
View File

@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.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 DrawerLogs from "./components/DrawerLogs";
import DraggableTabs from "./components/DragTabs";
import { isJsonString } from "./untils/helper";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
@ -46,11 +47,15 @@ const apiUrl = import.meta.env.VITE_BACKEND_URL;
function App() {
const user = useMemo(() => {
return localStorage.getItem("user") &&
typeof localStorage.getItem("user") === "string"
isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "")
: null;
}, []);
if (!user) window.location.href = "/";
if (!user) {
localStorage.removeItem("user");
window.location.href = "/";
}
document.title = "Automation Test";
const { socket } = useSocket();
@ -484,6 +489,8 @@ function App() {
setLoadingTerminal(true);
}, 100);
}}
setActive={setActiveTab}
active={activeTab}
/>
<StationSetting
@ -496,9 +503,14 @@ function App() {
}}
isEdit={isEditStation}
setStations={setStations}
setActiveTab={() =>
setActiveTab(stations.length ? stations[0]?.id.toString() : "0")
}
setActiveTab={(id: string) => {
setActiveTab(id);
setLoadingTerminal(false);
setTimeout(() => {
setLoadingTerminal(true);
}, 100);
}}
stations={stations}
/>
<ModalTerminal
@ -523,7 +535,13 @@ function App() {
}
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 (
<MantineProvider>
<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 Login from "./Login";
import Register from "./Register";
@ -7,6 +7,12 @@ import classes from "./AuthenticationImage.module.css";
export const PageLogin = () => {
const [isRegister, setIsRegister] = useState(false);
useEffect(() => {
if (localStorage.getItem("user")) {
localStorage.removeItem("user");
}
}, []);
return (
<div
style={{

View File

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

View File

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

View File

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

View File

@ -7,3 +7,15 @@ export const passwordRegex =
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&#])[A-Za-z\d@$!%*?&#]{8,}$/;
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;
}
}