From f7b4de813051f1bd28fea98c6473a156024b1a1c Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Thu, 10 Jul 2025 21:29:01 -0500 Subject: [PATCH] feat(rfid): front end view of the readers and there status --- .../layout/side-components/production.tsx | 48 +++-- frontend/src/components/rfid/RfidPage.tsx | 28 +++ frontend/src/routeTree.gen.ts | 21 +++ frontend/src/routes/rfid/index.tsx | 14 ++ frontend/src/utils/querys/rfid/getReaders.tsx | 20 ++ .../tableData/rfid/readers/readerColumns.tsx | 97 ++++++++++ .../tableData/rfid/readers/readerData.tsx | 174 ++++++++++++++++++ server/globalUtils/routeDefs/returnRes.ts | 56 ++++-- .../controllers/userAdmin/getAllUserRoles.ts | 8 +- .../auth/controllers/userAdmin/getUsers.ts | 3 + server/services/rfid/controller/getReaders.ts | 21 +++ .../services/rfid/controller/readerControl.ts | 7 +- server/services/rfid/rfidService.ts | 12 +- server/services/rfid/route/getReaders.ts | 43 +++++ .../services/server/utils/subModuleCheck.ts | 17 ++ 15 files changed, 533 insertions(+), 36 deletions(-) create mode 100644 frontend/src/components/rfid/RfidPage.tsx create mode 100644 frontend/src/routes/rfid/index.tsx create mode 100644 frontend/src/utils/querys/rfid/getReaders.tsx create mode 100644 frontend/src/utils/tableData/rfid/readers/readerColumns.tsx create mode 100644 frontend/src/utils/tableData/rfid/readers/readerData.tsx create mode 100644 server/services/rfid/controller/getReaders.ts create mode 100644 server/services/rfid/route/getReaders.ts diff --git a/frontend/src/components/layout/side-components/production.tsx b/frontend/src/components/layout/side-components/production.tsx index f472d9a..a6d00b3 100644 --- a/frontend/src/components/layout/side-components/production.tsx +++ b/frontend/src/components/layout/side-components/production.tsx @@ -1,4 +1,4 @@ -import {Printer} from "lucide-react"; +import { Printer } from "lucide-react"; import { SidebarGroup, SidebarGroupContent, @@ -7,8 +7,8 @@ import { SidebarMenuButton, SidebarMenuItem, } from "../../ui/sidebar"; -import {hasPageAccess} from "@/utils/userAccess"; -import {User} from "@/types/users"; +import { hasPageAccess } from "@/utils/userAccess"; +import { User } from "@/types/users"; const items = [ { @@ -19,9 +19,32 @@ const items = [ module: "ocp", active: true, }, + { + title: "RFID", + moduleName: "prodcution", + description: "RFID stuff", + url: "/rfid", + icon: "Tags", + active: true, + roles: [ + "viewer", + "technician", + "supervisor", + "manager", + "admin", + "systemAdmin", + ], + subSubModule: [], + }, ]; -export function ProductionSideBar({user, moduleID}: {user: User | null; moduleID: string}) { +export function ProductionSideBar({ + user, + moduleID, +}: { + user: User | null; + moduleID: string; +}) { return ( Production @@ -30,14 +53,15 @@ export function ProductionSideBar({user, moduleID}: {user: User | null; moduleID {items.map((item) => ( <> - {hasPageAccess(user, item.role, moduleID) && item.active && ( - - - - {item.title} - - - )} + {hasPageAccess(user, item.role, moduleID) && + item.active && ( + + + + {item.title} + + + )} ))} diff --git a/frontend/src/components/rfid/RfidPage.tsx b/frontend/src/components/rfid/RfidPage.tsx new file mode 100644 index 0000000..f12ebf4 --- /dev/null +++ b/frontend/src/components/rfid/RfidPage.tsx @@ -0,0 +1,28 @@ +import { getReaders } from "@/utils/querys/rfid/getReaders"; +import { readerColumns } from "@/utils/tableData/rfid/readers/readerColumns"; +import { ReaderTable } from "@/utils/tableData/rfid/readers/readerData"; +import { useQuery } from "@tanstack/react-query"; + +export default function RfidPage() { + const { data, isError, isLoading } = useQuery(getReaders()); + + if (isError) { + return
Error
; + } + + if (isLoading) { + return
Loading
; + } + + return ( +
+ + a.reader.localeCompare(b.reader) + )} + //style={style} + /> +
+ ); +} diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 1d1665d..63848d6 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -17,6 +17,7 @@ import { Route as EomRouteImport } from './routes/_eom' import { Route as AuthRouteImport } from './routes/_auth' import { Route as AdminRouteImport } from './routes/_admin' import { Route as IndexRouteImport } from './routes/index' +import { Route as RfidIndexRouteImport } from './routes/rfid/index' import { Route as OcpIndexRouteImport } from './routes/ocp/index' import { Route as EomEomRouteImport } from './routes/_eom/eom' import { Route as AuthProfileRouteImport } from './routes/_auth/profile' @@ -78,6 +79,11 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) +const RfidIndexRoute = RfidIndexRouteImport.update({ + id: '/rfid/', + path: '/rfid/', + getParentRoute: () => rootRouteImport, +} as any) const OcpIndexRoute = OcpIndexRouteImport.update({ id: '/ocp/', path: '/ocp/', @@ -220,6 +226,7 @@ export interface FileRoutesByFullPath { '/profile': typeof AuthProfileRoute '/eom': typeof EomEomRoute '/ocp': typeof OcpIndexRoute + '/rfid': typeof RfidIndexRoute '/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute '/article/$av': typeof EomArticleAvRoute '/barcodegen': typeof logisticsBarcodegenIndexRoute @@ -250,6 +257,7 @@ export interface FileRoutesByTo { '/profile': typeof AuthProfileRoute '/eom': typeof EomEomRoute '/ocp': typeof OcpIndexRoute + '/rfid': typeof RfidIndexRoute '/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute '/article/$av': typeof EomArticleAvRoute '/barcodegen': typeof logisticsBarcodegenIndexRoute @@ -284,6 +292,7 @@ export interface FileRoutesById { '/_auth/profile': typeof AuthProfileRoute '/_eom/eom': typeof EomEomRoute '/ocp/': typeof OcpIndexRoute + '/rfid/': typeof RfidIndexRoute '/(logistics)/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute '/_eom/article/$av': typeof EomArticleAvRoute '/(logistics)/barcodegen/': typeof logisticsBarcodegenIndexRoute @@ -316,6 +325,7 @@ export interface FileRouteTypes { | '/profile' | '/eom' | '/ocp' + | '/rfid' | '/siloAdjustments/$hist' | '/article/$av' | '/barcodegen' @@ -346,6 +356,7 @@ export interface FileRouteTypes { | '/profile' | '/eom' | '/ocp' + | '/rfid' | '/siloAdjustments/$hist' | '/article/$av' | '/barcodegen' @@ -379,6 +390,7 @@ export interface FileRouteTypes { | '/_auth/profile' | '/_eom/eom' | '/ocp/' + | '/rfid/' | '/(logistics)/siloAdjustments/$hist' | '/_eom/article/$av' | '/(logistics)/barcodegen/' @@ -404,6 +416,7 @@ export interface RootRouteChildren { RegisterRoute: typeof RegisterRoute userPasswordChangeRoute: typeof userPasswordChangeRoute OcpIndexRoute: typeof OcpIndexRoute + RfidIndexRoute: typeof RfidIndexRoute logisticsSiloAdjustmentsHistRoute: typeof logisticsSiloAdjustmentsHistRoute logisticsBarcodegenIndexRoute: typeof logisticsBarcodegenIndexRoute logisticsDmIndexRoute: typeof logisticsDmIndexRoute @@ -475,6 +488,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/rfid/': { + id: '/rfid/' + path: '/rfid' + fullPath: '/rfid' + preLoaderRoute: typeof RfidIndexRouteImport + parentRoute: typeof rootRouteImport + } '/ocp/': { id: '/ocp/' path: '/ocp' @@ -694,6 +714,7 @@ const rootRouteChildren: RootRouteChildren = { RegisterRoute: RegisterRoute, userPasswordChangeRoute: userPasswordChangeRoute, OcpIndexRoute: OcpIndexRoute, + RfidIndexRoute: RfidIndexRoute, logisticsSiloAdjustmentsHistRoute: logisticsSiloAdjustmentsHistRoute, logisticsBarcodegenIndexRoute: logisticsBarcodegenIndexRoute, logisticsDmIndexRoute: logisticsDmIndexRoute, diff --git a/frontend/src/routes/rfid/index.tsx b/frontend/src/routes/rfid/index.tsx new file mode 100644 index 0000000..4fc226a --- /dev/null +++ b/frontend/src/routes/rfid/index.tsx @@ -0,0 +1,14 @@ +import RfidPage from "@/components/rfid/RfidPage"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/rfid/")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+ +
+ ); +} diff --git a/frontend/src/utils/querys/rfid/getReaders.tsx b/frontend/src/utils/querys/rfid/getReaders.tsx new file mode 100644 index 0000000..6b6424b --- /dev/null +++ b/frontend/src/utils/querys/rfid/getReaders.tsx @@ -0,0 +1,20 @@ +import { queryOptions } from "@tanstack/react-query"; +import axios from "axios"; + +export function getReaders() { + return queryOptions({ + queryKey: ["getReaders"], + queryFn: () => fetchReaders(), + //enabled: + staleTime: 1000, + refetchInterval: 60 * 1000, + refetchOnWindowFocus: true, + }); +} + +const fetchReaders = async () => { + const { data } = await axios.get(`/api/rfid/getreaders`); + // if we are not localhost ignore the devDir setting. + //const url: string = window.location.host.split(":")[0]; + return data.data ?? []; +}; diff --git a/frontend/src/utils/tableData/rfid/readers/readerColumns.tsx b/frontend/src/utils/tableData/rfid/readers/readerColumns.tsx new file mode 100644 index 0000000..5123bb8 --- /dev/null +++ b/frontend/src/utils/tableData/rfid/readers/readerColumns.tsx @@ -0,0 +1,97 @@ +//import { fixTime } from "@/utils/fixTime"; +import { ColumnDef } from "@tanstack/react-table"; +import { format } from "date-fns-tz"; + +// This type is used to define the shape of our data. +// You can use a Zod schema here if you want. +export type Readers = { + rfidReader_id: string; + reader: string; + readerIP: string; + lastHeartBeat: string; + lastTrigger: string; + lastTriggerGood: boolean; + active: boolean; + lastTagScanned: string; + goodReads: number; + badReads: number; + totalReads: number; + goodRatio: number; +}; + +export const readerColumns: ColumnDef[] = [ + { + accessorKey: "reader", + header: () =>
Name
, + }, + { + accessorKey: "lastHeartBeat", + header: "Last HeartBeat", + cell: ({ row }) => { + if (row.getValue("lastHeartBeat")) { + const correctDate: any = row.getValue("lastHeartBeat"); + const strippedDate = correctDate.replace("Z", ""); // Remove Z + const formattedDate = format(strippedDate, "MM/dd/yyyy HH:mm"); + return ( +
{formattedDate}
+ ); + } + }, + }, + { + accessorKey: "lastTrigger", + header: "Last Trigger", + cell: ({ row }) => { + if (row.getValue("lastTrigger")) { + const correctDate: any = row.getValue("lastTrigger"); + const strippedDate = correctDate.replace("Z", ""); // Remove Z + const formattedDate = format(strippedDate, "MM/dd/yyyy HH:mm"); + return ( +
{formattedDate}
+ ); + } + }, + }, + { + accessorKey: "lastTriggerGood", + header: "Last Trigger Status", + }, + { + accessorKey: "lastTagScanned", + header: "Last Scanned Tag", + }, + { + accessorKey: "goodReads", + header: "Total Good Reads", + }, + { + accessorKey: "badReads", + header: "Total Bad Reads", + }, + { + accessorKey: "totalReads", + header: "Total Reads", + cell: ({ row }) => { + const total = + parseInt(row.getValue("goodReads")) + + parseInt(row.getValue("badReads")); + return
{total}
; + }, + }, + { + accessorKey: "goodRatio", + header: "Good Ratio", + cell: ({ row }) => { + const goodRatio = + (parseInt(row.getValue("goodReads")) / + (parseInt(row.getValue("goodReads")) + + parseInt(row.getValue("badReads")))) * + 100; + return ( +
+ {isNaN(goodRatio) ? 0 : goodRatio}% +
+ ); + }, + }, +]; diff --git a/frontend/src/utils/tableData/rfid/readers/readerData.tsx b/frontend/src/utils/tableData/rfid/readers/readerData.tsx new file mode 100644 index 0000000..8b17106 --- /dev/null +++ b/frontend/src/utils/tableData/rfid/readers/readerData.tsx @@ -0,0 +1,174 @@ +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + getPaginationRowModel, +} from "@tanstack/react-table"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { LstCard } from "@/components/extendedUI/LstCard"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + //style: any; +} + +export function ReaderTable({ + columns, + data, + //style, +}: DataTableProps) { + const [pagination, setPagination] = useState({ + pageIndex: 0, //initial page index + pageSize: 10, //default page size + }); + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + state: { + //... + pagination, + }, + }); + + //console.log(parseInt(style.height.replace("px", "")) - 50); + return ( + +
+
+ {data.length === 0 ? ( + No readers + ) : ( + Current reader Info + )} + +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column + .columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No labels. + + + )} + +
+
+
+
+ + +
+
+ ); +} diff --git a/server/globalUtils/routeDefs/returnRes.ts b/server/globalUtils/routeDefs/returnRes.ts index deece3f..0251703 100644 --- a/server/globalUtils/routeDefs/returnRes.ts +++ b/server/globalUtils/routeDefs/returnRes.ts @@ -1,23 +1,43 @@ -import type { Context } from "hono"; +import { createLog } from "../../services/logger/logger.js"; -export type ReturnRes = - | { success: true; message: string; data: T } - | { success: false; message: string; error: any }; +export function returnRes( + success: true, + message: string, + service: string, + user: string, + level: "info" | "error", + data: T +): { success: true; message: string; data: T }; -export const returnRes = ( - success: boolean, - message: string, - data: T | null = null -): ReturnRes => { - /** - * just a simple return to reduce the typing and make sure we are always consitant with our returns. - * - * data can be an error as well. - */ - return success - ? { success, message, data: data as T } - : { success, message, error: data ?? "An unknown error occurred" }; -}; +export function returnRes( + success: false, + message: string, + service: string, + user: string, + level: "info" | "error", + data?: T +): { success: false; message: string; error: T | string }; + +export function returnRes( + success: boolean, + message: string, + service: string, + user: string, + level: "info" | "error", + data?: T +) { + createLog(level, user, service, message); + + if (success) { + return { success: true, message, data: data as T }; + } else { + return { + success: false, + message, + error: data ?? "An unknown error occurred", + }; + } +} // export const returnApi = (c:Context,success: boolean, message: string, data?: any, code: number)=>{ // /** diff --git a/server/services/auth/controllers/userAdmin/getAllUserRoles.ts b/server/services/auth/controllers/userAdmin/getAllUserRoles.ts index 621d463..e690eef 100644 --- a/server/services/auth/controllers/userAdmin/getAllUserRoles.ts +++ b/server/services/auth/controllers/userAdmin/getAllUserRoles.ts @@ -12,13 +12,15 @@ export const getAllUsersRoles = async () => { const { data, error } = await tryCatch(db.select().from(userRoles)); if (error) { - returnRes( + return returnRes( false, + "auth", + "auth", "There was an error getting users", + "error", new Error("No user exists.") ); } - returnRes(true, "All users.", data); - return { success: true, message: "All users", data }; + return returnRes(true, "auth", "auth", "All users.", "info", data); }; diff --git a/server/services/auth/controllers/userAdmin/getUsers.ts b/server/services/auth/controllers/userAdmin/getUsers.ts index 4a53bc9..57e4e47 100644 --- a/server/services/auth/controllers/userAdmin/getUsers.ts +++ b/server/services/auth/controllers/userAdmin/getUsers.ts @@ -32,7 +32,10 @@ export const getAllUsers = async () => { if (error) { returnRes( false, + "auth", + "auth", "There was an error getting users", + "error", new Error("No user exists.") ); } diff --git a/server/services/rfid/controller/getReaders.ts b/server/services/rfid/controller/getReaders.ts new file mode 100644 index 0000000..f8cb601 --- /dev/null +++ b/server/services/rfid/controller/getReaders.ts @@ -0,0 +1,21 @@ +import { db } from "../../../../database/dbclient.js"; +import { rfidReaders } from "../../../../database/schema/rfidReaders.js"; +import { returnRes } from "../../../globalUtils/routeDefs/returnRes.js"; +import { tryCatch } from "../../../globalUtils/tryCatch.js"; + +export const getReaders = async () => { + const { data, error } = await tryCatch(db.select().from(rfidReaders)); + + if (error) { + return returnRes( + false, + "There was an error getting the Readers", + "rfid", + "rfid", + "error", + error + ); + } + + return returnRes(true, "Current readers", "rfid", "rfid", "info", data); +}; diff --git a/server/services/rfid/controller/readerControl.ts b/server/services/rfid/controller/readerControl.ts index 4789976..41a4908 100644 --- a/server/services/rfid/controller/readerControl.ts +++ b/server/services/rfid/controller/readerControl.ts @@ -52,6 +52,7 @@ export const badRead = async (reader: string) => { lastTrigger: sql`NOW()`, lastTriggerGood: false, lastTagScanned: null, + badReads: sql`${rfidReaders.badReads} + 1`, }) .where(eq(rfidReaders.reader, reader)); createLog( @@ -85,7 +86,11 @@ export const goodRead = async (reader: string) => { try { const goodRead = await db .update(rfidReaders) - .set({ lastTrigger: sql`NOW()`, lastTriggerGood: true }) + .set({ + lastTrigger: sql`NOW()`, + lastTriggerGood: true, + goodReads: sql`${rfidReaders.goodReads} + 1`, + }) .where(eq(rfidReaders.reader, reader)); createLog( "info", diff --git a/server/services/rfid/rfidService.ts b/server/services/rfid/rfidService.ts index 91bd477..dcda4f3 100644 --- a/server/services/rfid/rfidService.ts +++ b/server/services/rfid/rfidService.ts @@ -1,13 +1,21 @@ -import {OpenAPIHono} from "@hono/zod-openapi"; +import { OpenAPIHono } from "@hono/zod-openapi"; import mgtEvents from "./route/mgtEvents.js"; import tagInfo from "./route/tagInfo.js"; import addReader from "./route/addReader.js"; import updateReader from "./route/updateReader.js"; import manualTrigger from "./route/manualTagRead.js"; +import getReaders from "./route/getReaders.js"; const app = new OpenAPIHono(); -const routes = [mgtEvents, tagInfo, addReader, updateReader, manualTrigger] as const; +const routes = [ + mgtEvents, + tagInfo, + addReader, + updateReader, + manualTrigger, + getReaders, +] as const; // app.route("/server", modules); const appRoutes = routes.forEach((route) => { diff --git a/server/services/rfid/route/getReaders.ts b/server/services/rfid/route/getReaders.ts new file mode 100644 index 0000000..2cfde5e --- /dev/null +++ b/server/services/rfid/route/getReaders.ts @@ -0,0 +1,43 @@ +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { addReader } from "../controller/addReader.js"; +import { authMiddleware } from "../../auth/middleware/authMiddleware.js"; +import { responses } from "../../../globalUtils/routeDefs/responses.js"; +import type { User } from "../../../types/users.js"; + +import { apiHit } from "../../../globalUtils/apiHits.js"; +import { tryCatch } from "../../../globalUtils/tryCatch.js"; +import { getReaders } from "../controller/getReaders.js"; + +const app = new OpenAPIHono(); + +export const ReaderBody = z.object({ + reader: z.string().openapi({ example: "wrapper1" }), + readerIP: z.string().openapi({ example: "192.168.1.52" }), +}); + +app.openapi( + createRoute({ + tags: ["rfid"], + summary: "Get readers", + method: "get", + path: "/getreaders", + responses: responses(), + }), + async (c: any) => { + apiHit(c, { endpoint: "/getreaders" }); + + const { data, error } = await tryCatch(getReaders()); + + if (error) { + return c.json({ + error, + }); + } + + return c.json({ + data, + }); + } +); + +export default app; diff --git a/server/services/server/utils/subModuleCheck.ts b/server/services/server/utils/subModuleCheck.ts index 5be29cc..fcd5d46 100644 --- a/server/services/server/utils/subModuleCheck.ts +++ b/server/services/server/utils/subModuleCheck.ts @@ -8,6 +8,23 @@ import { subModules } from "../../../../database/schema/subModules.js"; import { createLog } from "../../logger/logger.js"; // "view", "technician", "supervisor","manager", "admin", "systemAdmin" const newSubModules = [ + { + name: "RFID", + moduleName: "prodcution", + description: "RFID stuff", + link: "/rfid", + icon: "Tags", + active: true, + roles: [ + "viewer", + "technician", + "supervisor", + "manager", + "admin", + "systemAdmin", + ], + subSubModule: [], + }, { name: "Silo Adjustments", moduleName: "logistics",