10 Commits

Author SHA1 Message Date
f5bae2c0c2 fix(anaylistics): changes to the daily section so it populates correctly now
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 1m9s
2026-05-11 15:41:20 -05:00
05758791be fix(scanner): fixes to be more clear that you need to scan a command to start
closes #16
2026-05-11 15:40:49 -05:00
51026e3e2c ci(notification): removal of more console logs that shouldnt be here 2026-05-11 15:38:44 -05:00
9631736e26 chore(mobile): removed console log that shouldnt be there 2026-05-11 15:38:04 -05:00
ce9d8eaaf5 feat(scan users): added in the place to add the new scanner users in 2026-05-11 15:37:38 -05:00
1bbf5c2a49 fix(table): skelly table causing hydration error 2026-05-11 15:35:46 -05:00
13718fe702 fix(anaylitics): unique values were missing causing a weird crash 2026-05-11 14:00:54 -05:00
0de2579942 fix(scanner): changed to not crash on logging
cloases #19
2026-05-11 13:35:07 -05:00
7c31b43a4a fix(app): emit.maxlistener issue
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m27s
BREAKING CHANGE: moved teh middleware to call the api hits to the main app and removed from
everywhere else

closes #18
2026-05-11 13:25:43 -05:00
85e96f5ed1 fix(scanner): logut out corrections
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
refs #17
2026-05-11 07:59:17 -05:00
36 changed files with 3232 additions and 144 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ node-v24.14.0-x64.msi
postgresql-17.9-2-windows-x64.exe postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.exe VSCodeUserSetup-x64-1.112.0.exe
nssm.exe nssm.exe
frontend/.tanstack
# Logs # Logs
logs logs

View File

@@ -1,16 +1,16 @@
import type { Express } from "express"; import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import build from "./admin.build.js"; import build from "./admin.build.js";
import update from "./admin.updateServer.js"; import update from "./admin.updateServer.js";
export const setupAdminRoutes = (baseUrl: string, app: Express) => { export const setupAdminRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this //stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/admin/build`, requireAuth, routeHitMiddleware, build); app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
app.use( app.use(
`${baseUrl}/api/admin/build`, `${baseUrl}/api/admin/build`,
requireAuth, requireAuth,
routeHitMiddleware,
update, update,
); );

View File

@@ -5,6 +5,7 @@ import express from "express";
import morgan from "morgan"; import morgan from "morgan";
import { umamiConfig } from "./configs/umami.config.js"; import { umamiConfig } from "./configs/umami.config.js";
import { createLogger } from "./logger/logger.controller.js"; import { createLogger } from "./logger/logger.controller.js";
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
import { setupRoutes } from "./routeHandler.routes.js"; import { setupRoutes } from "./routeHandler.routes.js";
import { auth } from "./utils/auth.utils.js"; import { auth } from "./utils/auth.utils.js";
import { lstCors } from "./utils/cors.utils.js"; import { lstCors } from "./utils/cors.utils.js";
@@ -30,6 +31,7 @@ const createApp = async () => {
app.use(morgan("dev")); app.use(morgan("dev"));
app.set("trust proxy", true); app.set("trust proxy", true);
app.use(lstCors()); app.use(lstCors());
app.use(routeHitMiddleware);
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth)); app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
app.use(express.json()); app.use(express.json());
setupRoutes(baseUrl, app); setupRoutes(baseUrl, app);

View File

@@ -1,11 +1,11 @@
import type { Express } from "express"; import type { Express } from "express";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import login from "./login.route.js"; import login from "./login.route.js";
import register from "./register.route.js"; import register from "./register.route.js";
export const setupAuthRoutes = (baseUrl: string, app: Express) => { export const setupAuthRoutes = (baseUrl: string, app: Express) => {
//setup all the routes //setup all the routes
app.use(routeHitMiddleware);
app.use(`${baseUrl}/api/authentication/login`, login); app.use(`${baseUrl}/api/authentication/login`, login);
app.use(`${baseUrl}/api/authentication/register`, register); app.use(`${baseUrl}/api/authentication/register`, register);
}; };

View File

@@ -1,5 +1,5 @@
import type { Express } from "express"; import type { Express } from "express";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import { apiReturn } from "../utils/returnHelper.utils.js"; import { apiReturn } from "../utils/returnHelper.utils.js";
import { datamartData } from "./datamartData.utlis.js"; import { datamartData } from "./datamartData.utlis.js";
import runQuery from "./getDatamart.route.js"; import runQuery from "./getDatamart.route.js";
@@ -30,7 +30,7 @@ export const setupDatamartRoutes = (baseUrl: string, app: Express) => {
// }); // });
//setup all the routes //setup all the routes
app.use(routeHitMiddleware);
app.use(`${baseUrl}/api/datamart`, runQuery); app.use(`${baseUrl}/api/datamart`, runQuery);
// just sending a get on datamart will return all the queries that we can call. // just sending a get on datamart will return all the queries that we can call.

View File

@@ -4,13 +4,16 @@ import {
pgTable, pgTable,
text, text,
timestamp, timestamp,
unique,
uuid, uuid,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
export const analyticsDaily = pgTable("analytics_daily", { export const analyticsDaily = pgTable(
"analytics_daily",
{
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
businessDate: date("business_date").notNull(), businessDate: date("business_date", { mode: "string" }).notNull(),
method: text("method").notNull(), method: text("method").notNull(),
routePattern: text("route_pattern").notNull(), routePattern: text("route_pattern").notNull(),
@@ -30,4 +33,13 @@ export const analyticsDaily = pgTable("analytics_daily", {
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(),
}); },
(table) => [
unique("analytics_daily_business_route_unique").on(
table.businessDate,
table.method,
table.routePattern,
table.module,
),
],
);

View File

@@ -1,6 +1,6 @@
import { type Express, Router } from "express"; import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import restart from "./gpSqlRestart.route.js"; import restart from "./gpSqlRestart.route.js";
import start from "./gpSqlStart.route.js"; import start from "./gpSqlStart.route.js";
import stop from "./gpSqlStop.route.js"; import stop from "./gpSqlStop.route.js";
@@ -9,7 +9,6 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
// Apply auth to entire router // Apply auth to entire router
const router = Router(); const router = Router();
router.use(requireAuth); router.use(requireAuth);
app.use(routeHitMiddleware);
router.use(start); router.use(start);
router.use(stop); router.use(stop);

View File

@@ -0,0 +1,54 @@
import { eq } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { scanUser } from "../db/schema/scanUsers.js";
import { settings } from "../db/schema/settings.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
// scanners that are dedicated to specific users.
const SPECIAL_SCANNERS = [69, 98];
const buildAllowedScannerIds = (scannerCount: number) => {
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
return Array.from(new Set([...generatedIds, ...SPECIAL_SCANNERS])).sort(
(a, b) => a - b,
);
};
r.get("/", async (_, res) => {
// get the scan users and setting
const scanusers = await db.select().from(scanUser);
const scannerIdSetting = await db
.select()
.from(settings)
.where(eq(settings.name, "scannerIds"));
const usedScannerIds = scanusers.map((x) => Number(x.scannerId));
const allowedScannerIds = buildAllowedScannerIds(
Number(scannerIdSetting[0]?.value ?? 0),
);
const availableScannerIds = allowedScannerIds.filter(
(id) => !usedScannerIds.includes(id),
);
const data = availableScannerIds.map((id) => ({
label: `${id}`,
value: id,
}));
return apiReturn(res, {
success: true,
level: "info",
module: "mobile",
subModule: "scanner",
message: `There are ${availableScannerIds.length} scanner id's`,
data,
status: 200,
});
});
export default r;

View File

@@ -1,5 +1,5 @@
import type { Express } from "express"; import type { Express } from "express";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js"; import available from "./availableScanIds.route.js";
import downloads from "./downloadApps.route.js"; import downloads from "./downloadApps.route.js";
import lanes from "./laneCheck.js"; import lanes from "./laneCheck.js";
import authPin from "./mobileAuth.route.js"; import authPin from "./mobileAuth.route.js";
@@ -10,14 +10,13 @@ import version from "./version.route.js";
export const setupMobileRoutes = (baseUrl: string, app: Express) => { export const setupMobileRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this //stats will be like this as we dont need to change this
app.use(routeHitMiddleware);
app.use(`${baseUrl}/api/mobile/version`, version); app.use(`${baseUrl}/api/mobile/version`, version);
app.use(`${baseUrl}/api/mobile/apk`, downloads); app.use(`${baseUrl}/api/mobile/apk`, downloads);
app.use(`${baseUrl}/api/mobile/logs`, logs); app.use(`${baseUrl}/api/mobile/logs`, logs);
app.use(`${baseUrl}/api/mobile/auth`, authPin); app.use(`${baseUrl}/api/mobile/auth`, authPin);
app.use(`${baseUrl}/api/mobile/pin`, newPin); app.use(`${baseUrl}/api/mobile/pin`, newPin);
app.use(`${baseUrl}/api/mobile/laneCheck`, lanes); app.use(`${baseUrl}/api/mobile/laneCheck`, lanes);
app.use(`${baseUrl}/api/mobile/available`, available);
// all other system should be under /api/system/* // all other system should be under /api/system/*
}; };

View File

@@ -12,14 +12,14 @@ router.post("/", async (req, res) => {
const newLog = await db const newLog = await db
.insert(scanLog) .insert(scanLog)
.values({ .values({
scannerId: body.scannerId, scannerId: body.scannerId ?? "",
message: body.message, message: body.message ?? "",
prompt: body.prompt, prompt: body.prompt ?? "",
commandDescription: body.commandDescription, commandDescription: body.commandDescription ?? "",
status: body.status, status: body.status ?? "",
lines: body.lines, lines: body.lines ?? "",
user: body.user, user: body.user ?? "",
runningNumber: body.runningNumber, runningNumber: body.runningNumber ?? "",
}) })
.returning(); .returning();

View File

@@ -1,6 +1,6 @@
import type { Express } from "express"; import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import manual from "./notification.manualTrigger.js"; import manual from "./notification.manualTrigger.js";
import getNotifications from "./notification.route.js"; import getNotifications from "./notification.route.js";
import updateNote from "./notification.update.route.js"; import updateNote from "./notification.update.route.js";
@@ -14,43 +14,43 @@ export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
app.use( app.use(
`${baseUrl}/api/notification`, `${baseUrl}/api/notification`,
requireAuth, requireAuth,
routeHitMiddleware,
getNotifications, getNotifications,
); );
app.use( app.use(
`${baseUrl}/api/notification`, `${baseUrl}/api/notification`,
requireAuth, requireAuth,
routeHitMiddleware,
updateNote, updateNote,
); );
app.use( app.use(
`${baseUrl}/api/notification/manual`, `${baseUrl}/api/notification/manual`,
requireAuth, requireAuth,
routeHitMiddleware,
manual, manual,
); );
app.use( app.use(
`${baseUrl}/api/notification/sub`, `${baseUrl}/api/notification/sub`,
requireAuth, requireAuth,
routeHitMiddleware,
subs, subs,
); );
app.use( app.use(
`${baseUrl}/api/notification/sub`, `${baseUrl}/api/notification/sub`,
requireAuth, requireAuth,
routeHitMiddleware,
newSub, newSub,
); );
app.use( app.use(
`${baseUrl}/api/notification/sub`, `${baseUrl}/api/notification/sub`,
requireAuth, requireAuth,
routeHitMiddleware,
updateSub, updateSub,
); );
app.use( app.use(
`${baseUrl}/api/notification/sub`, `${baseUrl}/api/notification/sub`,
requireAuth, requireAuth,
routeHitMiddleware,
deleteSub, deleteSub,
); );

View File

@@ -1,7 +1,7 @@
import { type Express, Router } from "express"; import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js"; import { featureCheck } from "../middleware/featureActive.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import listener from "./ocp.printer.listener.js"; import listener from "./ocp.printer.listener.js";
import update from "./ocp.printer.update.js"; import update from "./ocp.printer.update.js";
@@ -18,8 +18,6 @@ export const setupOCPRoutes = (baseUrl: string, app: Express) => {
// auth routes below here // auth routes below here
router.use(requireAuth); router.use(requireAuth);
app.use(routeHitMiddleware);
router.use(update); router.use(update);
//router.use(""); //router.use("");

View File

@@ -1,7 +1,7 @@
import { type Express, Router } from "express"; import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js"; import { featureCheck } from "../middleware/featureActive.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import getApt from "./opendockGetRelease.route.js"; import getApt from "./opendockGetRelease.route.js";
export const setupOpendockRoutes = (baseUrl: string, app: Express) => { export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
@@ -14,7 +14,6 @@ export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
// we need to make sure we are authenticated to see the releases // we need to make sure we are authenticated to see the releases
router.use(requireAuth); router.use(requireAuth);
app.use(routeHitMiddleware);
router.use(getApt); router.use(getApt);
app.use(`${baseUrl}/api/opendock`, router); app.use(`${baseUrl}/api/opendock`, router);

View File

@@ -1,6 +1,6 @@
import { type Express, Router } from "express"; import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import restart from "./prodSqlRestart.route.js"; import restart from "./prodSqlRestart.route.js";
import start from "./prodSqlStart.route.js"; import start from "./prodSqlStart.route.js";
import stop from "./prodSqlStop.route.js"; import stop from "./prodSqlStop.route.js";
@@ -9,7 +9,6 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
// Apply auth to entire router // Apply auth to entire router
const router = Router(); const router = Router();
router.use(requireAuth); router.use(requireAuth);
app.use(routeHitMiddleware);
router.use(start); router.use(start);
router.use(stop); router.use(stop);

View File

@@ -346,6 +346,17 @@ const newSettings: NewSetting[] = [
roles: ["admin"], roles: ["admin"],
seedVersion: 1, seedVersion: 1,
}, },
{
name: "scannerIds",
value: "10",
active: false,
description:
"How many scanners ids are setup for this, there should be a lst_scanner instance created.",
moduleName: "mobile",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
]; ];
export const baseSettingValidationCheck = async () => { export const baseSettingValidationCheck = async () => {

View File

@@ -1,6 +1,6 @@
import type { Express } from "express"; import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import getServers from "./serverData.route.js"; import getServers from "./serverData.route.js";
import getSettings from "./settings.route.js"; import getSettings from "./settings.route.js";
import updSetting from "./settingsUpdate.route.js"; import updSetting from "./settingsUpdate.route.js";
@@ -8,7 +8,6 @@ import stats from "./stats.route.js";
export const setupSystemRoutes = (baseUrl: string, app: Express) => { export const setupSystemRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this //stats will be like this as we dont need to change this
app.use(routeHitMiddleware);
app.use(`${baseUrl}/api/stats`, stats); app.use(`${baseUrl}/api/stats`, stats);
app.use(`${baseUrl}/api/settings`, getSettings); app.use(`${baseUrl}/api/settings`, getSettings);
app.use(`${baseUrl}/api/servers`, getServers); app.use(`${baseUrl}/api/servers`, getServers);

View File

@@ -1,6 +1,6 @@
import type { Express } from "express"; import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js"; import { requireAuth } from "../middleware/auth.middleware.js";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import restart from "./tcpRestart.route.js"; import restart from "./tcpRestart.route.js";
import start from "./tcpStart.route.js"; import start from "./tcpStart.route.js";
import stop from "./tcpStop.route.js"; import stop from "./tcpStop.route.js";
@@ -8,12 +8,12 @@ import stop from "./tcpStop.route.js";
export const setupTCPRoutes = (baseUrl: string, app: Express) => { export const setupTCPRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this //stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/tcp/start`, requireAuth, routeHitMiddleware, start); app.use(`${baseUrl}/api/tcp/start`, requireAuth, start);
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, routeHitMiddleware, stop); app.use(`${baseUrl}/api/tcp/stop`, requireAuth, stop);
app.use( app.use(
`${baseUrl}/api/tcp/restart`, `${baseUrl}/api/tcp/restart`,
requireAuth, requireAuth,
routeHitMiddleware,
restart, restart,
); );

View File

@@ -61,7 +61,7 @@ export async function aggregateRouteHitsForBusinessDay() {
const rows = await db const rows = await db
.select({ .select({
businessDate: sql<string>`${businessDate}`, businessDate: sql<string>`CAST(${businessDate} AS date)`,
method: analytics.method, method: analytics.method,
routePattern: analytics.routePattern, routePattern: analytics.routePattern,
module: sql<string>`COALESCE(${analytics.module}, 'unknown')`, module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,
@@ -105,9 +105,16 @@ export async function aggregateRouteHitsForBusinessDay() {
}; };
} }
const values = rows.map((row) => ({
...row,
businessDate: row.businessDate,
firstHitAt: new Date(row.firstHitAt),
lastHitAt: new Date(row.lastHitAt),
}));
await db await db
.insert(analyticsDaily) .insert(analyticsDaily)
.values(rows) .values(values)
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [ target: [
analyticsDaily.businessDate, analyticsDaily.businessDate,

View File

@@ -10,7 +10,7 @@ export async function generateUniquePin() {
const pin = generateSixDigitPin(); const pin = generateSixDigitPin();
const existing = await db.query.scanUser.findFirst({ const existing = await db.query.scanUser.findFirst({
where: (u, { eq }) => eq(u.pinHash, pin), // ⚠️ we'll fix this below where: (u, { eq }) => eq(u.pinHash, pin),
}); });
if (!existing) if (!existing)
@@ -37,3 +37,13 @@ export async function generateUniquePin() {
room: "", room: "",
}); });
} }
// export const pinExists = async (pin: string | number) => {
// const existing = await db.query.scanUser.findFirst({
// where: (u, { eq }) => eq(u.pinHash, pin),
// });
// if (!existing) return true;
// return false;
// };

View File

@@ -1,9 +1,8 @@
import type { Express } from "express"; import type { Express } from "express";
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
import getActiveJobs from "./cronerActiveJobs.route.js"; import getActiveJobs from "./cronerActiveJobs.route.js";
import jobStatusChange from "./cronerStatusChange.route.js"; import jobStatusChange from "./cronerStatusChange.route.js";
export const setupUtilsRoutes = (baseUrl: string, app: Express) => { export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
app.use(routeHitMiddleware);
app.use(`${baseUrl}/api/utils/croner`, getActiveJobs); app.use(`${baseUrl}/api/utils/croner`, getActiveJobs);
app.use(`${baseUrl}/api/utils/croner`, jobStatusChange); app.use(`${baseUrl}/api/utils/croner`, jobStatusChange);
}; };

View File

@@ -68,7 +68,7 @@ export default function AdminSidebar({ session }: any) {
title: "Scan users", title: "Scan users",
url: "/admin/scanUsers", url: "/admin/scanUsers",
icon: UsersRound, icon: UsersRound,
role: ["systemAdmin", "admin"], role: ["systemAdmin", "admin", "manager"],
module: "admin", module: "admin",
active: true, active: true,
}, },
@@ -79,9 +79,9 @@ export default function AdminSidebar({ session }: any) {
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{items.map((item) => ( {items.map((item) => (
<> <div key={item.title}>
{item.role.includes(session.user.role) && ( {item.role.includes(session.user.role) && (
<SidebarMenuItem key={item.title}> <SidebarMenuItem>
<SidebarMenuButton asChild> <SidebarMenuButton asChild>
<Link to={item.url} onClick={() => setOpen(false)}> <Link to={item.url} onClick={() => setOpen(false)}>
<item.icon /> <item.icon />
@@ -90,7 +90,7 @@ export default function AdminSidebar({ session }: any) {
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
)} )}
</> </div>
))} ))}
</SidebarMenu> </SidebarMenu>
</SidebarGroupContent> </SidebarGroupContent>

View File

@@ -25,8 +25,6 @@ export default function MobileBar({ session }: any) {
}, },
]; ];
console.log(session);
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Mobile</SidebarGroupLabel> <SidebarGroupLabel>Mobile</SidebarGroupLabel>

View File

@@ -27,7 +27,8 @@ export function AppSidebar() {
<MobileBar session={session} /> <MobileBar session={session} />
{session && {session &&
(session.user.role === "admin" || (session.user.role === "admin" ||
session.user.role === "systemAdmin") && ( session.user.role === "systemAdmin" ||
session.user.role === "manager") && (
<AdminSidebar session={session} /> <AdminSidebar session={session} />
)} )}
</SidebarContent> </SidebarContent>

View File

@@ -1,42 +1,45 @@
import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"
import { Slot } from "radix-ui"; import { cva, type VariantProps } from "class-variance-authority"
import type * as React from "react"; import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary: secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default:
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
icon: "size-9", lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", icon: "size-8",
"icon-sm": "size-8", "icon-xs":
"icon-lg": "size-10", "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
size: "default", size: "default",
}, },
}, }
); )
function Button({ function Button({
className, className,
@@ -46,9 +49,9 @@ function Button({
...props ...props
}: React.ComponentProps<"button"> & }: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean; asChild?: boolean
}) { }) {
const Comp = asChild ? Slot.Root : "button"; const Comp = asChild ? Slot.Root : "button"
return ( return (
<Comp <Comp
@@ -58,7 +61,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
{...props} {...props}
/> />
); )
} }
export { Button, buttonVariants }; export { Button, buttonVariants }

View File

@@ -0,0 +1,166 @@
import * as React from "react"
import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { XIcon } from "lucide-react"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close data-slot="dialog-close" asChild>
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
>
<XIcon
/>
<span className="sr-only">Close</span>
</Button>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"text-base leading-none font-medium",
className
)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View File

@@ -0,0 +1,25 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getScannerIds() {
return queryOptions({
queryKey: ["getScannerIds"],
queryFn: () => fetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await axios.get("/lst/api/mobile/available", {
withCredentials: true,
timeout: 5000,
});
return data.data;
};

View File

@@ -17,11 +17,13 @@ export default function SkellyTable({ rows = 5, columns = 4 }: TableSkelly) {
<div className="rounded-md border"> <div className="rounded-md border">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow>
{Array.from({ length: columns }).map((_, i) => ( {Array.from({ length: columns }).map((_, i) => (
<TableHead key={i}> <TableHead key={i}>
<Skeleton className="h-4 w-[80px]" /> <Skeleton className="h-4 w-[80px]" />
</TableHead> </TableHead>
))} ))}
</TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{Array.from({ length: rows }).map((_, r) => ( {Array.from({ length: rows }).map((_, r) => (

View File

@@ -13,6 +13,7 @@ import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as DocsIndexRouteImport } from './routes/docs/index' import { Route as DocsIndexRouteImport } from './routes/docs/index'
import { Route as DocsSplatRouteImport } from './routes/docs/$' import { Route as DocsSplatRouteImport } from './routes/docs/$'
import { Route as AdminUsersRouteImport } from './routes/admin/users'
import { Route as AdminSettingsRouteImport } from './routes/admin/settings' import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
import { Route as AdminServersRouteImport } from './routes/admin/servers' import { Route as AdminServersRouteImport } from './routes/admin/servers'
import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers' import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers'
@@ -43,6 +44,11 @@ const DocsSplatRoute = DocsSplatRouteImport.update({
path: '/docs/$', path: '/docs/$',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const AdminUsersRoute = AdminUsersRouteImport.update({
id: '/admin/users',
path: '/admin/users',
getParentRoute: () => rootRouteImport,
} as any)
const AdminSettingsRoute = AdminSettingsRouteImport.update({ const AdminSettingsRoute = AdminSettingsRouteImport.update({
id: '/admin/settings', id: '/admin/settings',
path: '/admin/settings', path: '/admin/settings',
@@ -98,6 +104,7 @@ export interface FileRoutesByFullPath {
'/admin/scanUsers': typeof AdminScanUsersRoute '/admin/scanUsers': typeof AdminScanUsersRoute
'/admin/servers': typeof AdminServersRoute '/admin/servers': typeof AdminServersRoute
'/admin/settings': typeof AdminSettingsRoute '/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/docs/$': typeof DocsSplatRoute '/docs/$': typeof DocsSplatRoute
'/docs/': typeof DocsIndexRoute '/docs/': typeof DocsIndexRoute
'/user/profile': typeof authUserProfileRoute '/user/profile': typeof authUserProfileRoute
@@ -113,6 +120,7 @@ export interface FileRoutesByTo {
'/admin/scanUsers': typeof AdminScanUsersRoute '/admin/scanUsers': typeof AdminScanUsersRoute
'/admin/servers': typeof AdminServersRoute '/admin/servers': typeof AdminServersRoute
'/admin/settings': typeof AdminSettingsRoute '/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/docs/$': typeof DocsSplatRoute '/docs/$': typeof DocsSplatRoute
'/docs': typeof DocsIndexRoute '/docs': typeof DocsIndexRoute
'/user/profile': typeof authUserProfileRoute '/user/profile': typeof authUserProfileRoute
@@ -129,6 +137,7 @@ export interface FileRoutesById {
'/admin/scanUsers': typeof AdminScanUsersRoute '/admin/scanUsers': typeof AdminScanUsersRoute
'/admin/servers': typeof AdminServersRoute '/admin/servers': typeof AdminServersRoute
'/admin/settings': typeof AdminSettingsRoute '/admin/settings': typeof AdminSettingsRoute
'/admin/users': typeof AdminUsersRoute
'/docs/$': typeof DocsSplatRoute '/docs/$': typeof DocsSplatRoute
'/docs/': typeof DocsIndexRoute '/docs/': typeof DocsIndexRoute
'/(auth)/user/profile': typeof authUserProfileRoute '/(auth)/user/profile': typeof authUserProfileRoute
@@ -146,6 +155,7 @@ export interface FileRouteTypes {
| '/admin/scanUsers' | '/admin/scanUsers'
| '/admin/servers' | '/admin/servers'
| '/admin/settings' | '/admin/settings'
| '/admin/users'
| '/docs/$' | '/docs/$'
| '/docs/' | '/docs/'
| '/user/profile' | '/user/profile'
@@ -161,6 +171,7 @@ export interface FileRouteTypes {
| '/admin/scanUsers' | '/admin/scanUsers'
| '/admin/servers' | '/admin/servers'
| '/admin/settings' | '/admin/settings'
| '/admin/users'
| '/docs/$' | '/docs/$'
| '/docs' | '/docs'
| '/user/profile' | '/user/profile'
@@ -176,6 +187,7 @@ export interface FileRouteTypes {
| '/admin/scanUsers' | '/admin/scanUsers'
| '/admin/servers' | '/admin/servers'
| '/admin/settings' | '/admin/settings'
| '/admin/users'
| '/docs/$' | '/docs/$'
| '/docs/' | '/docs/'
| '/(auth)/user/profile' | '/(auth)/user/profile'
@@ -192,6 +204,7 @@ export interface RootRouteChildren {
AdminScanUsersRoute: typeof AdminScanUsersRoute AdminScanUsersRoute: typeof AdminScanUsersRoute
AdminServersRoute: typeof AdminServersRoute AdminServersRoute: typeof AdminServersRoute
AdminSettingsRoute: typeof AdminSettingsRoute AdminSettingsRoute: typeof AdminSettingsRoute
AdminUsersRoute: typeof AdminUsersRoute
DocsSplatRoute: typeof DocsSplatRoute DocsSplatRoute: typeof DocsSplatRoute
DocsIndexRoute: typeof DocsIndexRoute DocsIndexRoute: typeof DocsIndexRoute
authUserProfileRoute: typeof authUserProfileRoute authUserProfileRoute: typeof authUserProfileRoute
@@ -229,6 +242,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsSplatRouteImport preLoaderRoute: typeof DocsSplatRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/admin/users': {
id: '/admin/users'
path: '/admin/users'
fullPath: '/admin/users'
preLoaderRoute: typeof AdminUsersRouteImport
parentRoute: typeof rootRouteImport
}
'/admin/settings': { '/admin/settings': {
id: '/admin/settings' id: '/admin/settings'
path: '/admin/settings' path: '/admin/settings'
@@ -304,6 +324,7 @@ const rootRouteChildren: RootRouteChildren = {
AdminScanUsersRoute: AdminScanUsersRoute, AdminScanUsersRoute: AdminScanUsersRoute,
AdminServersRoute: AdminServersRoute, AdminServersRoute: AdminServersRoute,
AdminSettingsRoute: AdminSettingsRoute, AdminSettingsRoute: AdminSettingsRoute,
AdminUsersRoute: AdminUsersRoute,
DocsSplatRoute: DocsSplatRoute, DocsSplatRoute: DocsSplatRoute,
DocsIndexRoute: DocsIndexRoute, DocsIndexRoute: DocsIndexRoute,
authUserProfileRoute: authUserProfileRoute, authUserProfileRoute: authUserProfileRoute,

View File

@@ -51,6 +51,8 @@ export default function NotificationsSubCard({ user }: any) {
})); }));
} }
console.log(n);
return ( return (
<div> <div>
<Card className="p-3 w-lg"> <Card className="p-3 w-lg">

View File

@@ -0,0 +1,161 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "../../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../../../components/ui/dialog";
import { useAppForm } from "../../../lib/formSutff";
import { getScannerIds } from "../../../lib/queries/getScannerIds";
export default function NewScanUser({ refetch }: { refetch: any }) {
const [open, setOpen] = useState(false);
const { data, refetch: scannerFetch } = useSuspenseQuery(getScannerIds());
const form = useAppForm({
defaultValues: {
name: "",
scannerId: "",
pinNumber: "",
},
onSubmit: async ({ value }) => {
if (value.scannerId === "") {
toast.error(
"Scanner id is required please select a scanner id before submitting ",
);
return;
}
try {
const { data } = await axios.post(
"/lst/api/mobile/auth/user",
{
name: value.name,
pinNumber: value.pinNumber,
scannerId: value.scannerId,
},
{
withCredentials: true,
timeout: 15000,
validateStatus: () => true,
},
);
if (data.success) {
toast.success(
`${value.name}, was just created and can now log into the scanner with PIN: ${value.pinNumber}`,
);
form.reset();
setOpen(false);
refetch();
}
if (!data.success) {
toast.error(data.message);
return;
}
} catch (error) {
console.error(error);
}
},
});
const closeModel = (e: boolean) => {
setOpen(e);
if (!e) {
form.reset();
scannerFetch();
}
};
const openForm = () => {
setOpen(true);
scannerFetch();
};
let n: any = [];
if (data) {
n = data.map((i: any) => ({
label: i.label,
value: i.value.toString(),
}));
}
return (
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
<Button onClick={openForm}>Create new user</Button>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>Create New Scan user.</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div className="mb-2">
<form.AppField name="name">
{(field) => (
<field.InputField
label="Name"
inputType="text"
required={true}
/>
)}
</form.AppField>
</div>
<div className="w-32">
<form.AppField name="scannerId">
{(field) => (
<field.SelectField
label="Scanner Id"
placeholder="Select New scanner Id"
options={n}
/>
)}
</form.AppField>
</div>
<div className="flex flex-row">
<div>
<form.AppField name="pinNumber">
{(field) => (
<field.InputField
label="Pin Number"
inputType="number"
required={true}
/>
)}
</form.AppField>
</div>
<div className="mt-9 ml-2">
<Button
type="button"
onClick={async () => {
const { data } = await axios.get("/lst/api/mobile/pin/new");
form.setFieldValue("pinNumber", data.data[0].pin);
}}
>
New Pin
</Button>
</div>
</div>
<div className="flex justify-end mt-2 ">
<form.AppForm>
<form.SubmitButton>Submit</form.SubmitButton>
</form.AppForm>
</div>
</form>
</DialogContent>
</Dialog>
);
}

View File

@@ -1,16 +1,258 @@
import { useSuspenseQuery } from "@tanstack/react-query"; import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, redirect } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import axios from "axios";
import { format } from "date-fns-tz";
import { CircleFadingArrowUp, Trash } from "lucide-react";
import { Suspense, useState } from "react";
import { toast } from "sonner";
import { Button } from "../../components/ui/button";
import { Spinner } from "../../components/ui/spinner";
import { authClient } from "../../lib/auth-client";
import { getScanUsers } from "../../lib/queries/getScanUsers"; import { getScanUsers } from "../../lib/queries/getScanUsers";
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
import LstTable from "../../lib/tableStuff/LstTable";
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
import SkellyTable from "../../lib/tableStuff/SkellyTable";
import NewScanUser from "./-components/NewScanUser";
export const Route = createFileRoute("/admin/scanUsers")({ export const Route = createFileRoute("/admin/scanUsers")({
beforeLoad: async ({ location }) => {
const { data: session } = await authClient.getSession();
const allowedRole = ["systemAdmin", "admin"];
if (!session?.user) {
throw redirect({
to: "/",
search: {
redirect: location.href,
},
});
}
if (!allowedRole.includes(session.user.role as string)) {
throw redirect({
to: "/",
});
}
return { user: session.user };
},
component: RouteComponent, component: RouteComponent,
}); });
const ScanUserTable = () => { const updateSettings = async (
const { data } = useSuspenseQuery(getScanUsers()); id: string,
console.log(data); data: Record<string, string | number | boolean | null>,
return <div>Hello "/admin/scanUsers"!</div>; ) => {
}; //console.log(id, data);
function RouteComponent() { try {
return <ScanUserTable />; const res = await axios.patch(`/lst/api/mobile/auth/user/${id}`, data, {
withCredentials: true,
timeout: 15000,
validateStatus: () => true,
});
toast.success(`User was just updated`);
return res;
} catch (err) {
toast.error("Error in updating the user");
return err;
}
};
const ScanUserTable = () => {
const { data, refetch } = useSuspenseQuery(getScanUsers());
const columnHelper = createColumnHelper<any>();
const updateSetting = useMutation({
mutationFn: ({
id,
field,
value,
}: {
id: string;
field: string;
value: string | number | boolean | null;
}) => updateSettings(id, { [field]: value }),
onSuccess: () => {
// refetch or update cache
refetch();
},
});
const columns = [
columnHelper.accessor("name", {
header: ({ column }) => (
<SearchableHeader column={column} title="Name" searchable={true} />
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("scannerId", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Scanner ID"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => i.getValue(),
}),
columnHelper.accessor("pinNumber", {
header: ({ column }) => (
<SearchableHeader column={column} title="Pin Number" />
),
filterFn: "includesString",
cell: ({ row, getValue }) => (
<div className="flex flex-row gap-2">
<div>
<EditableCellInput
value={getValue()}
id={row.original.name}
field="value"
onSubmit={({ id, field, value }) => {
updateSetting.mutate({ id, field, value });
}}
/>
</div>
<div className="">
<Button
type="button"
onClick={async () => {
const { data } = await axios.get("/lst/api/mobile/pin/new");
updateSetting.mutate({
id: row.original.id,
field: "pinNumber",
value: data.data[0].pin,
});
}}
>
New Pin
</Button>
</div>
</div>
),
}),
columnHelper.accessor("lastScan", {
header: ({ column }) => (
<SearchableHeader column={column} title="Last Scan" />
),
cell: (i) => <span>{format(i.getValue(), "M/d/yyyy HH:mm")}</span>,
}),
columnHelper.accessor("excludedCommand", {
header: ({ column }) => (
<SearchableHeader column={column} title="Command id's Not Allowed" />
),
cell: (i) => {
const commands = i.getValue().join();
return (
<span>{commands === "" ? "All commands allowed" : commands}</span>
);
},
}),
columnHelper.accessor("deleteUser", {
header: ({ column }) => (
<SearchableHeader
column={column}
title="Delete User"
searchable={false}
/>
),
filterFn: "includesString",
cell: (i) => {
// biome-ignore lint: just removing the lint for now to get this going will maybe fix later
const [activeToggle, setActiveToggle] = useState(false);
const onTrigger = async () => {
setActiveToggle(true);
try {
const res = await axios.delete(
`/lst/api/mobile/auth/user/${i.row.original.id}`,
{
withCredentials: true,
timeout: 5000,
validateStatus: () => true,
},
);
if (res.data.success) {
toast.success(`${i.row.original.name} was deleted.`);
refetch();
setActiveToggle(false);
}
if (!res.data.success) {
toast.error(
`${i.row.original.name} encountered an error when trying to delete: ${res.data.message}`,
);
refetch();
setActiveToggle(false);
}
} catch (error) {
setActiveToggle(false);
console.error(error);
}
};
return (
<div>
<div className="flex items-center space-x-2">
<Button
variant="destructive"
disabled={activeToggle}
onClick={onTrigger}
>
{activeToggle ? (
<span>
<Spinner />
</span>
) : (
<span>
<Trash />
</span>
)}
</Button>
</div>
</div>
);
},
}),
];
return (
<div>
<div className="flex justify-end m-2">
<Suspense
fallback={
<div>
<p>Loading...</p>
</div>
}
>
<NewScanUser refetch={refetch} />
</Suspense>
</div>
<div>
<LstTable data={data} columns={columns} pageSize={50} />
</div>
</div>
);
};
// const NewUserForm = ()=>{
// const { data, refetch } = useSuspenseQuery(getScanUsers());
// }
function RouteComponent() {
//const { data: session } = useSession();
return (
<Suspense fallback={<SkellyTable />}>
<ScanUserTable />
</Suspense>
);
} }

View File

@@ -15,8 +15,8 @@
"foregroundImage": "./assets/adaptive-icon-white.png", "foregroundImage": "./assets/adaptive-icon-white.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"versionCode": 32, "versionCode": 33,
"minSupportedVersionCode": 26, "minSupportedVersionCode": 33,
"predictiveBackGestureEnabled": false, "predictiveBackGestureEnabled": false,
"package": "net.alpla.lst.mobile" "package": "net.alpla.lst.mobile"
}, },

View File

@@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
import { format } from "date-fns-tz"; import { format } from "date-fns-tz";
import { useFocusEffect } from "expo-router"; import { Redirect, useFocusEffect, useRouter } from "expo-router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Alert, Button, Text, View } from "react-native"; import { Alert, Button, Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore"; import { useAppStore } from "../hooks/useAppStore";
@@ -23,14 +23,13 @@ const formatName = (name?: string) =>
export default function LSTScanner() { export default function LSTScanner() {
const user = useMobileAuthStore((s) => s.user); const user = useMobileAuthStore((s) => s.user);
const logout = useMobileAuthStore((s) => s.logout); const logout = useMobileAuthStore((s) => s.logout);
const router = useRouter();
// TODO : move to off tcp stuff after od // TODO : move to off tcp stuff after od
const lastScan = useScannerStore((s) => s.lastScan); const lastScan = useScannerStore((s) => s.lastScan);
const setLastScan = useScannerStore((s) => s.setLastScan); const setLastScan = useScannerStore((s) => s.setLastScan);
const [tagScans, setTagScans] = useState<any>([]); const [tagScans, setTagScans] = useState<any>([]);
const scannerIdFromStore = useAppStore((s) => s.scannerId);
const serverIp = useAppStore((s) => s.serverIp); const serverIp = useAppStore((s) => s.serverIp);
const serverPort = useAppStore((s) => s.serverPort);
const [bgColor, setBGColor] = useState<string | null>(null); const [bgColor, setBGColor] = useState<string | null>(null);
const handleScan = useCallback( const handleScan = useCallback(
@@ -154,6 +153,13 @@ export default function LSTScanner() {
setTagScans([]); setTagScans([]);
}; };
const logoutScanner = () => {
setTagScans([]);
setLastScan(null);
logout();
router.replace("/");
};
//console.log(lastScan); //console.log(lastScan);
useFocusEffect( useFocusEffect(
@@ -189,7 +195,11 @@ export default function LSTScanner() {
{!lastScan ? ( {!lastScan ? (
<View style={{ marginTop: 10, alignItems: "center" }}> <View style={{ marginTop: 10, alignItems: "center" }}>
<Text className="text-xl font-bold">Ready to scan</Text> <Text className="text-xl font-bold">Ready to scan</Text>
<Text>Waiting for first scan...</Text> <Text>Please Scan a command to start scanning...</Text>
<Text className="text-sm">
Scanning a label could cause errors due to incorrect previous
command scanned
</Text>
</View> </View>
) : ( ) : (
<View <View
@@ -228,7 +238,7 @@ export default function LSTScanner() {
<View className="m-2"> <View className="m-2">
{user && ( {user && (
<View className="items-center"> <View className="items-center">
<Button title="Logout" onPress={logout} /> <Button title="Logout" onPress={logoutScanner} />
</View> </View>
)} )}
</View> </View>

View File

@@ -0,0 +1 @@
ALTER TABLE "analytics_daily" ADD CONSTRAINT "analytics_daily_business_route_unique" UNIQUE("business_date","method","route_pattern","module");

File diff suppressed because it is too large Load Diff

View File

@@ -358,6 +358,13 @@
"when": 1778169641819, "when": 1778169641819,
"tag": "0050_concerned_vivisector", "tag": "0050_concerned_vivisector",
"breakpoints": true "breakpoints": true
},
{
"idx": 51,
"version": "7",
"when": 1778525497824,
"tag": "0051_sad_war_machine",
"breakpoints": true
} }
] ]
} }