42 Commits

Author SHA1 Message Date
3a0c729b9a chore(release): 0.1.0-alpha.2
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m45s
Release and Build Image / release (push) Successful in 15s
2026-05-23 11:42:48 -05:00
057a570e43 fix(docs): wrong location for images
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m39s
2026-05-23 11:41:43 -05:00
52974aa0b4 refactor(mobile): added missing error to the scanner
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m55s
2026-05-23 11:22:44 -05:00
ecfbda9036 fix(mobile): correction to axios helper 2026-05-23 11:22:25 -05:00
389211186f feat(opendock): added in new article link setup for fine tuning how od works 2026-05-23 11:22:02 -05:00
3a27fd8542 docs(mobile): updated imgs to be a little smaller 2026-05-23 11:21:19 -05:00
1f6637c512 fix(build): crashes when files changed :(
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m34s
BREAKING CHANGE: gives a rabbit hole error

closes #24
2026-05-21 21:51:21 -05:00
1840ac5e58 feat(opendock): scheduing updates
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m39s
ref #23
2026-05-21 21:42:18 -05:00
636daaed0a fix(sql queries): disable job would error so now we will check if it exists before trying to kill it
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m37s
2026-05-20 20:49:54 -05:00
71c83062cb ci(docker): changes to the ignore file 2026-05-20 20:49:21 -05:00
cd67c4de80 refactor(opendock): changes to how we do the intergration scheduling
ref #23
2026-05-20 20:49:00 -05:00
36ac1dccb4 chore(release): 0.1.0-alpha.1
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m39s
Release and Build Image / release (push) Successful in 15s
2026-05-18 21:39:59 -05:00
514a44b6de refactor(servers): changed activeity around and trying to make use of it
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
2026-05-18 21:38:08 -05:00
a7bb364a2f fix(settings): failed build due it dormant import
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 3m42s
2026-05-18 21:23:34 -05:00
047cc7cdf0 refactor(users): lots of auth stuff added to make it more easy to manage users
Some checks failed
Build and Push LST Docker Image / docker (push) Failing after 2m9s
2026-05-18 21:19:20 -05:00
8dc4d70e28 ci(app): added in chokidar to monitor folders 2026-05-18 21:18:42 -05:00
c8931c7249 fix(notifications): reprinting
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m20s
correction to external labeling

ref #20
2026-05-14 14:18:40 -05:00
67f36c5499 chore(release): 0.1.0-alpha.0
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m55s
Release and Build Image / release (push) Successful in 9s
2026-05-13 20:56:14 -05:00
ebf1060475 refactor(servers): server name now links to the actual server:port
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m24s
2026-05-13 20:53:27 -05:00
c64392f457 refactor(api): changes to call a helper api to quit and redirect if needed 2026-05-13 20:52:55 -05:00
e9e73c829c ci(servives): helpers moved around 2026-05-13 20:52:16 -05:00
bcb7773007 ci(updateserver): changes to actually add the new env stuff 2026-05-13 20:51:54 -05:00
eb950d2c29 refactor(scanner): more scanner admin stuff 2026-05-13 20:51:22 -05:00
2616acf106 fix(notification subs): made it so only acitve show
closes #14
2026-05-13 20:50:51 -05:00
30ff7b71d9 refactor(app): changed ways we get data so we can have better reasons why app no worky 2026-05-13 20:49:43 -05:00
e7af3d1182 refactor(scanner): removed 69 as an option lol 2026-05-13 20:48:43 -05:00
3e66c3920d feat(notification): migrated sql cleanup 2026-05-13 20:48:22 -05:00
eb9d77c3d4 docs(scanner): added in westbend and dayton commands to scan for updates 2026-05-13 20:47:38 -05:00
342a97f6b1 refactor(users): some user refactoring and configuring 2026-05-13 16:42:36 -05:00
b0c7277a6c docs(scanner): added in instructions on how to update the scanner
only test2 stage now commands for now.
2026-05-12 20:26:19 -05:00
dc95e50a84 ci(mobile): added in ehs config to make it more easy for users to update the scanner app on the fly 2026-05-12 12:04:19 -05:00
d2a9e1d110 fix(app): required auth was in wrong spot caused entire app to want you logged in 2026-05-12 12:02:59 -05:00
a9c69250bd refactor(mobile): scanner response
ref #16
2026-05-12 08:53:12 -05:00
d61be61f44 refactor(scanner): logging - version of app 2026-05-11 19:06:25 -05:00
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
150 changed files with 19953 additions and 473 deletions

View File

@@ -9,4 +9,4 @@ builds
testFiles
nssm.exe
postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.msi
VSCodeSetup-x64-1.120.0.msi

3
.gitignore vendored
View File

@@ -9,8 +9,9 @@ downloads
.scriptCreds
node-v24.14.0-x64.msi
postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.exe
VSCodeSetup-x64-1.120.0.exe
nssm.exe
frontend/.tanstack
# Logs
logs

View File

@@ -1,5 +1,117 @@
# All Changes to LST can be found below.
## [0.1.0-alpha.2](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) (2026-05-23)
### ⚠ BREAKING CHANGES
* **build:** gives a rabbit hole error
### 🌟 Enhancements
* **opendock:** added in new article link setup for fine tuning how od works ([3892111](https://git.tuffraid.net/cowch/lst_v3/commits/389211186f00cb8a6fdd5de092a944fa7e5898aa))
* **opendock:** scheduing updates ([1840ac5](https://git.tuffraid.net/cowch/lst_v3/commits/1840ac5e580c726c452216480b6e14e7c52a0f35)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
### 🐛 Bug fixes
* **build:** crashes when files changed :( ([1f6637c](https://git.tuffraid.net/cowch/lst_v3/commits/1f6637c512dcd465c5000f8d1baaa8e76766edc1)), closes [#24](https://git.tuffraid.net/cowch/lst_v3/issues/24)
* **docs:** wrong location for images ([057a570](https://git.tuffraid.net/cowch/lst_v3/commits/057a570e43a8e1763652d98244c90999c3fccd42))
* **mobile:** correction to axios helper ([ecfbda9](https://git.tuffraid.net/cowch/lst_v3/commits/ecfbda9036f3d68c93e9c1d81021efa8093f18e2))
* **sql queries:** disable job would error so now we will check if it exists before trying to kill it ([636daae](https://git.tuffraid.net/cowch/lst_v3/commits/636daaed0adeda908e7e850a4f5bb20d7bbef861))
### 📚 Documentation
* **mobile:** updated imgs to be a little smaller ([3a27fd8](https://git.tuffraid.net/cowch/lst_v3/commits/3a27fd8542c3fa4ad5520532c2f10c6e3eaa951c))
### 🛠️ Code Refactor
* **mobile:** added missing error to the scanner ([52974aa](https://git.tuffraid.net/cowch/lst_v3/commits/52974aa0b4f21431777b773200a57f185b4babd2))
* **opendock:** changes to how we do the intergration scheduling ([cd67c4d](https://git.tuffraid.net/cowch/lst_v3/commits/cd67c4de80b6f0244afc639a7360e9dc2ba97a21)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
### 📈 Project changes
* **docker:** changes to the ignore file ([71c8306](https://git.tuffraid.net/cowch/lst_v3/commits/71c83062cb644796ebbfd845084ac6c019206faa))
## [0.1.0-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) (2026-05-19)
### 🐛 Bug fixes
* **notifications:** reprinting ([c8931c7](https://git.tuffraid.net/cowch/lst_v3/commits/c8931c7249b8f532b5dd37df3271da98f14ee710)), closes [#20](https://git.tuffraid.net/cowch/lst_v3/issues/20)
* **settings:** failed build due it dormant import ([a7bb364](https://git.tuffraid.net/cowch/lst_v3/commits/a7bb364a2fd49d96b6195aca0cd58ba57c58f3a6))
### 🛠️ Code Refactor
* **servers:** changed activeity around and trying to make use of it ([514a44b](https://git.tuffraid.net/cowch/lst_v3/commits/514a44b6de3efe8dd8b308d98bdbc82e31ed8427))
* **users:** lots of auth stuff added to make it more easy to manage users ([047cc7c](https://git.tuffraid.net/cowch/lst_v3/commits/047cc7cdf036c39a89a0b87ab59dda8328efe0c0))
### 📈 Project changes
* **app:** added in chokidar to monitor folders ([8dc4d70](https://git.tuffraid.net/cowch/lst_v3/commits/8dc4d70e2827f0a40d2f54886fd757c8a2dc5ac4))
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
### ⚠ BREAKING CHANGES
* **app:** moved teh middleware to call the api hits to the main app and removed from
everywhere else
### 🌟 Enhancements
* **notification:** migrated sql cleanup ([3e66c39](https://git.tuffraid.net/cowch/lst_v3/commits/3e66c3920d65cee7a0a788f3910c1ddf09a07805))
* **scan users:** added in the place to add the new scanner users in ([ce9d8ea](https://git.tuffraid.net/cowch/lst_v3/commits/ce9d8eaaf5bcb8f53ea4bdc191347df8d589fdfa))
### 🐛 Bug fixes
* **anaylistics:** changes to the daily section so it populates correctly now ([f5bae2c](https://git.tuffraid.net/cowch/lst_v3/commits/f5bae2c0c24b85423c5c421164d94d58159ff70a))
* **anaylitics:** unique values were missing causing a weird crash ([13718fe](https://git.tuffraid.net/cowch/lst_v3/commits/13718fe70293c039bd1d9bf8cf395852e6ea6c21))
* **app:** emit.maxlistener issue ([7c31b43](https://git.tuffraid.net/cowch/lst_v3/commits/7c31b43a4a313237fa63c0c9bbc3690b74f63a6f)), closes [#18](https://git.tuffraid.net/cowch/lst_v3/issues/18)
* **app:** required auth was in wrong spot caused entire app to want you logged in ([d2a9e1d](https://git.tuffraid.net/cowch/lst_v3/commits/d2a9e1d1107ea05f13725e9528bc6ab1566c8efb))
* **notification subs:** made it so only acitve show ([2616acf](https://git.tuffraid.net/cowch/lst_v3/commits/2616acf106530f5c5ee04d1b79033795cf06b42d)), closes [#14](https://git.tuffraid.net/cowch/lst_v3/issues/14)
* **scanner:** changed to not crash on logging ([0de2579](https://git.tuffraid.net/cowch/lst_v3/commits/0de25799420f38a293ee9acc70eb36e3287145c4)), closes [#19](https://git.tuffraid.net/cowch/lst_v3/issues/19)
* **scanner:** fixes to be more clear that you need to scan a command to start ([0575879](https://git.tuffraid.net/cowch/lst_v3/commits/05758791be7a50e90b5da05d4977e618c311f654)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
* **scanner:** logut out corrections ([85e96f5](https://git.tuffraid.net/cowch/lst_v3/commits/85e96f5ed13a81fd466c6bbff31c539244750838)), closes [#17](https://git.tuffraid.net/cowch/lst_v3/issues/17)
* **table:** skelly table causing hydration error ([1bbf5c2](https://git.tuffraid.net/cowch/lst_v3/commits/1bbf5c2a4955107a36ace05595886d19cc8e64f4))
### 📝 Chore
* **mobile:** removed console log that shouldnt be there ([9631736](https://git.tuffraid.net/cowch/lst_v3/commits/9631736e263ed00189f8118f686690cab25f09d3))
### 📚 Documentation
* **scanner:** added in instructions on how to update the scanner ([b0c7277](https://git.tuffraid.net/cowch/lst_v3/commits/b0c7277a6cdb5becec3a994ea1d5cc2d7b0326aa))
* **scanner:** added in westbend and dayton commands to scan for updates ([eb9d77c](https://git.tuffraid.net/cowch/lst_v3/commits/eb9d77c3d4767fd961759662ef44c3e09e00946b))
### 🛠️ Code Refactor
* **api:** changes to call a helper api to quit and redirect if needed ([c64392f](https://git.tuffraid.net/cowch/lst_v3/commits/c64392f45769108aa4134c7fd865f3d4bc664179))
* **app:** changed ways we get data so we can have better reasons why app no worky ([30ff7b7](https://git.tuffraid.net/cowch/lst_v3/commits/30ff7b71d9d159ced263a5330d70d53b97393157))
* **mobile:** scanner response ([a9c6925](https://git.tuffraid.net/cowch/lst_v3/commits/a9c69250bd3272ad682751e41b671c119cb678f1)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
* **scanner:** logging - version of app ([d61be61](https://git.tuffraid.net/cowch/lst_v3/commits/d61be61f4433a2be2678d724f4724301931614c9))
* **scanner:** more scanner admin stuff ([eb950d2](https://git.tuffraid.net/cowch/lst_v3/commits/eb950d2c29f692b806d5cc4ab7014bd59a726a8d))
* **scanner:** removed 69 as an option lol ([e7af3d1](https://git.tuffraid.net/cowch/lst_v3/commits/e7af3d11824b42915cf6789f9c508a727511d678))
* **servers:** server name now links to the actual server:port ([ebf1060](https://git.tuffraid.net/cowch/lst_v3/commits/ebf1060475d37627b371bc6c79507cdde411600b))
* **users:** some user refactoring and configuring ([342a97f](https://git.tuffraid.net/cowch/lst_v3/commits/342a97f6b1054443b9126186d2c7872fbd8586da))
### 📈 Project changes
* **mobile:** added in ehs config to make it more easy for users to update the scanner app on the fly ([dc95e50](https://git.tuffraid.net/cowch/lst_v3/commits/dc95e50a8412b4fbc629fd44fcb5c77295583ca8))
* **notification:** removal of more console logs that shouldnt be here ([51026e3](https://git.tuffraid.net/cowch/lst_v3/commits/51026e3e2cce4d7f696d26aae305b3fd221f5bb1))
* **servives:** helpers moved around ([e9e73c8](https://git.tuffraid.net/cowch/lst_v3/commits/e9e73c829c2e5726650c0ac7ffa6a9055dbc982b))
* **updateserver:** changes to actually add the new env stuff ([bcb7773](https://git.tuffraid.net/cowch/lst_v3/commits/bcb7773007894ac2f85fe2a0b47faf14c7b474ad))
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2026-05-08)

View File

@@ -3,16 +3,14 @@ import { requireAuth } from "../middleware/auth.middleware.js";
import build from "./admin.build.js";
import update from "./admin.updateServer.js";
import users from "./admin.users.js";
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
//stats will be like this as we dont need to change this
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
app.use(
`${baseUrl}/api/admin/build`,
requireAuth,
update,
);
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
app.use(`${baseUrl}/api/admin/build`, requireAuth, update);
app.use(`${baseUrl}/api/admin/user`, requireAuth, users);
// all other system should be under /api/system/*
};

View File

@@ -0,0 +1,46 @@
/**
* To be able to run this we need to set our dev pc in the .env.
* if its empty just ignore it. this will just be the double catch
*/
import { fromNodeHeaders } from "better-auth/node";
import { Router } from "express";
import { auth } from "../utils/auth.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.get("/", async (req, res) => {
const { users } = await auth.api.listUsers({
query: {
limit: 50,
},
headers: fromNodeHeaders(req.headers),
});
// console.log(error);
// if (error) {
// return apiReturn(res, {
// success: false,
// level: "info",
// module: "admin",
// subModule: "user",
// message: `There was an error getting the users.`,
// data: users,
// status: 400,
// });
// }
return apiReturn(res, {
success: true,
level: "info",
module: "admin",
subModule: "users",
message: `Current active users.`,
data: users,
status: 200,
});
});
export default r;

View File

@@ -34,7 +34,6 @@ const createApp = async () => {
app.use(routeHitMiddleware);
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
app.use(express.json());
setupRoutes(baseUrl, app);
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
res.type("application/javascript");
@@ -52,6 +51,8 @@ const createApp = async () => {
`);
});
setupRoutes(baseUrl, app);
app.use(
`${baseUrl}/app`,
express.static(join(__dirname, "../frontend/dist")),

View File

@@ -1,7 +1,8 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
import * as scanUserSchema from "./schema/scanUsers.js";
import * as settingsSchema from "./schema/settings.schema.js";
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
@@ -20,5 +21,7 @@ const queryClient = postgres(dbURL, {
export const db = drizzle(queryClient, {
schema: {
...scanUserSchema,
...settingsSchema,
...opendockAVCheck,
},
});

View File

@@ -4,13 +4,16 @@ import {
pgTable,
text,
timestamp,
unique,
uuid,
} from "drizzle-orm/pg-core";
export const analyticsDaily = pgTable("analytics_daily", {
export const analyticsDaily = pgTable(
"analytics_daily",
{
id: uuid("id").defaultRandom().primaryKey(),
businessDate: date("business_date").notNull(),
businessDate: date("business_date", { mode: "string" }).notNull(),
method: text("method").notNull(),
routePattern: text("route_pattern").notNull(),
@@ -30,4 +33,13 @@ export const analyticsDaily = pgTable("analytics_daily", {
createdAt: timestamp("created_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

@@ -14,14 +14,13 @@ export const opendockApt = pgTable(
"opendock_apt",
{
id: uuid("id").defaultRandom().primaryKey(),
release: integer("release").notNull().unique(),
release: integer("release").notNull().unique("opendock_apt_release_unique"),
openDockAptId: text("open_dock_apt_id").notNull(),
appointment: jsonb("appointment").notNull().default([]),
upd_date: timestamp("upd_date").notNull().defaultNow(),
createdAt: timestamp("created_at").notNull().defaultNow(),
},
(table) => ({
releaseIdx: index("opendock_apt_release_idx").on(table.release),
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
table.openDockAptId,
),

View File

@@ -0,0 +1,46 @@
import {
integer,
pgEnum,
pgTable,
text,
timestamp,
unique,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const loadTypeEnum = pgEnum("load_type", ["drop", "live"]);
export const opendockArticleSetup = pgTable(
"opendock_article_setup",
{
id: uuid("id").defaultRandom().primaryKey(),
av: integer("av").notNull(),
description: text("description").notNull(),
customer: text("customer").notNull(), // customer should be a concat of the ID - Desc
customerDescription: text("customer_description").notNull(),
loadType: loadTypeEnum("load_type").notNull().default("drop"),
dock: text("dock").notNull(),
upd_date: timestamp("upd_date").notNull().defaultNow(),
upd_user: text("upd_user").notNull().default("lst-system"),
createdAt: timestamp("created_at").notNull().defaultNow(),
add_user: text("add_user").notNull().default("lst-system"),
},
(table) => ({
uniqueAvCustomer: unique("uq_opendock_article_setup_av_customer").on(
table.av,
table.customer,
),
}),
);
export const opendockArticleSetupSchema =
createSelectSchema(opendockArticleSetup);
export const newOpendockArticleSetupSchema =
createInsertSchema(opendockArticleSetup);
export type OpendockArticleSetup = z.infer<typeof opendockArticleSetupSchema>;
export type NewOpendockArticleSetup = z.infer<
typeof newOpendockArticleSetupSchema
>;

View File

@@ -0,0 +1,21 @@
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const opendockDockSetup = pgTable("opendock_dock_setup", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
dockID: text("dock_id").notNull(),
upd_date: timestamp("upd_date").notNull().defaultNow(),
upd_user: text("upd_user").notNull().default("lst-system"),
createdAt: timestamp("created_at").notNull().defaultNow(),
add_user: text("add_user").notNull().default("lst-system"),
});
export const opendockDockSetupSchema = createSelectSchema(opendockDockSetup);
export const newOpendockDockSetupSchema = createInsertSchema(opendockDockSetup);
export type OpendockArticleSetup = z.infer<typeof opendockDockSetupSchema>;
export type NewOpendockArticleSetup = z.infer<
typeof newOpendockDockSetupSchema
>;

View File

@@ -11,6 +11,7 @@ export const scanLog = pgTable("scan_log", {
commandDescription: text("command_description"),
runningNumber: text("running_number").default("0"),
status: text("status"),
scannerVersion: text("scanner_version").default("0"),
lines: jsonb("lines").default([]),
add_Date: timestamp("add_date").defaultNow(),
});

View File

@@ -38,7 +38,7 @@ export const settings = pgTable(
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
uniqueIndex("name").on(table.name),
uniqueIndex("settings_name_unique").on(table.name),
],
);

View File

@@ -8,11 +8,10 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
// Apply auth to entire router
const router = Router();
router.use(requireAuth);
router.use(start);
router.use(stop);
router.use(restart);
app.use(`${baseUrl}/api/system/gpSql`, router);
app.use(`${baseUrl}/api/system/gpSql`, requireAuth, router);
};

View File

@@ -8,7 +8,7 @@ import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
// scanners that are dedicated to specific users.
const SPECIAL_SCANNERS = [69, 98];
const SPECIAL_SCANNERS = [98];
const buildAllowedScannerIds = (scannerCount: number) => {
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);

View File

@@ -38,7 +38,86 @@ router.get("/ehs", (_, res) => {
}
res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk}"`);
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk"`);
return res.sendFile(apkPath);
});
router.get("/ehs/xml", (_, res) => {
const xmlPath = path.join(downloadDir, "enterprisehomescreen.xml");
if (!fs.existsSync(xmlPath)) {
return res.status(404).json({
success: false,
message: "EHS XML not found",
});
}
res.setHeader("Content-Type", "application/xml");
res.setHeader(
"Content-Disposition",
`attachment; filename="enterprisehomescreen.xml"`,
);
return res.sendFile(xmlPath);
});
router.get("/android/upgrade/11", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_11-70-20.00-RG-U00-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_11.zip"`,
);
return res.sendFile(apkPath);
});
router.get("/android/upgrade/13", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_13.zip"`,
);
return res.sendFile(apkPath);
});
router.get("/android/upgrade/14", (_, res) => {
const apkPath = path.join(
downloadDir,
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
);
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Type", "application/zip");
res.setHeader(
"Content-Disposition",
`attachment; filename="HE_FULL_UPDATE_14.zip"`,
);
return res.sendFile(apkPath);
});

View File

@@ -1,6 +1,12 @@
import { Router } from "express";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const router = Router();
@@ -9,7 +15,27 @@ router.post("/", async (req, res) => {
const lane = body.lane.split("#");
console.log(lane[2]);
// check if the plant has warehousing activated
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
const { data: fd, error: fe } = await tryCatch(
prodQuery(featureQ.query, `Running feature check`),
);
if (fe) {
return returnFunc({
success: false,
level: "error",
module: "datamart",
subModule: "query",
message: `feature check failed`,
data: fe as any,
notify: false,
});
}
console.log(fd);
const laneData = await runProdApi({
method: "post",
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",

View File

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

View File

@@ -1,25 +1,34 @@
import { eq, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { scanLog } from "../db/schema/scanlog.schema.js";
import { scanUser } from "../db/schema/scanUsers.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
const router = Router();
router.post("/", async (req, res) => {
const body = req.body;
try {
await db
.update(scanUser)
.set({ lastScan: sql`NOW()` })
.where(eq(scanUser.name, body.name));
} catch (error) {
console.log(error);
}
const newLog = await db
.insert(scanLog)
.values({
scannerId: body.scannerId,
message: body.message,
prompt: body.prompt,
commandDescription: body.commandDescription,
status: body.status,
lines: body.lines,
user: body.user,
runningNumber: body.runningNumber,
scannerId: body.scannerId ?? "",
message: body.message ?? "",
prompt: body.prompt ?? "",
commandDescription: body.commandDescription ?? "",
status: body.status ?? "",
lines: body.lines ?? "",
user: body.user ?? "",
runningNumber: body.runningNumber ?? "",
scannerVersion: body.scannerVersion ?? "0",
})
.returning();

View File

@@ -0,0 +1,80 @@
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
// disable the jobs
const jobNames: string[] = [
"monitor_$_lots",
"monitor_$_lots_2",
"monitor$lots",
"Monitor_APO", //listen for people to cry this is no longer a thing
"Monitor_APO2",
"Monitor_AutoConsumeMaterials", // TODO: migrate to lst
"Monitor_AutoConsumeMaterials_iow1",
"Monitor_AutoConsumeMaterials_iow2",
"Monitor_BlockedINV_Loc",
"monitor_inv_cycle",
"monitor_inv_cycle_1",
"monitor_inv_cycle_2",
"monitor_edi_import", // TODO: migrate to lst -- for the query select count(*) from AlplaPROD_test3.dbo.T_EDIDokumente (nolock) where /* IdLieferant > 1 and */ add_date > DATEADD(MINUTE, -30, getdate())
"Monitor_Lot_Progression",
"Monitor_Lots", // TODO: migrate to lst -- this should be the one where we monitor the when a lot is assigned if its missing some data.
"Monitor_MinMax", // TODO:Migrate to lst
"Monitor_MinMax_iow2",
"Monitor_PM",
"Monitor_Purity",
"monitor_wastebookings", // TODO: Migrate
"LastPriceUpdate", // not even sure what this is
"GETLabelsCount", // seems like an old jc job
"jobforpuritycount", // was not even working correctly
"Monitor_EmptyAutoConsumLocations", // not sure who uses this one
"monitor_labelreprint", // Migrated but need to find out who really wants this
"test", // not even sure why this is active
"UpdateLastMoldUsed", // old jc inserts data into a table but not sure what its used for not linked to any other alert
"UpdateWhsePositions3", // old jc inserts data into a table but not sure what its used for not linked to any other alert
"UpdateWhsePositions4",
"delete_print", // i think this was in here for when we was having lag prints in iowa1
"INV_WHSE_1", // something random i wrote long time ago looks like an inv thing to see aged stuff
"INV_WHSE_2",
"laneAgeCheck", // another strange one thats been since moved to lst
"monitor_blocking_2",
"monitor_blocking", // already in lst
"monitor_min_inv", // do we still want this one? it has a description of: this checks m-f the min inventory of materials based on the min level set in stock
"Monitor_MixedLocations",
"Monitor_PM",
"Monitor_PM2",
"wrong_lots_1",
"wrong_lots_2",
"invenotry check", // spelling error one of my stupids
"monitor_hold_monitor",
"Monitor_Silo_adjustments",
"monitor_qualityLocMonitor", // validating with lima this is still needed
"Monitor_Stock_Change",
];
export const sqlJobCleanUp = async () => {
// running a query to disable jobs that are moved to lst to be better maintained
const sqlQuery = sqlQuerySelector("disableJob") as SqlQuery;
if (!sqlQuery.success) {
console.error("Failed to load the query: ", sqlQuery.message);
return;
}
for (const job of jobNames) {
const { error } = await tryCatch(
prodQuery(
sqlQuery.query.replace("[jobName]", `${job}`),
`Disabling job: ${job}`,
),
);
if (error) {
console.error(error);
}
//console.log(data);
}
};

View File

@@ -43,7 +43,7 @@ const parseZebraAlert = (body: any): PrinterEvent => {
};
};
r.post("/printer/listener/:printer", upload.any(), async (req, res) => {
r.post("/:printer", upload.any(), async (req, res) => {
const { printer: printerName } = req.params;
const event: PrinterEvent = parseZebraAlert(req.body);

View File

@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
const r = Router();
r.post("/printer/update", async (_, res) => {
r.post("/update", async (_, res) => {
printerSync();
return apiReturn(res, {
success: true,

View File

@@ -1,4 +1,4 @@
import { type Express, Router } from "express";
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
@@ -6,20 +6,11 @@ import listener from "./ocp.printer.listener.js";
import update from "./ocp.printer.update.js";
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
const router = Router();
// is the feature even on?
router.use(featureCheck("ocp"));
// non auth routes up here
router.use(listener);
// auth routes below here
router.use(requireAuth);
router.use(update);
//router.use("");
app.use(`${baseUrl}/api/ocp`, router);
app.use(`${baseUrl}/api/ocp/printer/listener`, featureCheck("ocp"), listener);
app.use(
`${baseUrl}/api/ocp/printer`,
featureCheck("ocp"),
requireAuth,
update,
);
};

View File

@@ -3,7 +3,7 @@ import { addHours } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import { eq, sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock.schema.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { settings } from "../db/schema/settings.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
@@ -27,6 +27,9 @@ type Releases = {
Quantity: number;
LineItemArticleWeight: number;
CustomerReleaseNumber: string;
DeliveryAddressDescription: string;
DeliveryAddressHumanReadableId: string;
AdditionalInformation1: string;
};
const timeZone = process.env.TIMEZONE as string;
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
@@ -73,6 +76,28 @@ const postRelease = async (release: Releases) => {
log.info({}, "Refreshing Auth Token");
await getToken();
}
// load validation checks
const defaultDock = await db.query.settings.findFirst({
where: (u, { eq }) => eq(u.name, "defaultLoadType"),
});
// check if the release has the data in it
const releaseLoadtypeCheck = (release.AdditionalInformation1 ?? "")
.toLowerCase()
.split(",")
.map((x) => x.trim())
.includes("drop");
const opendDockArticleCheck = await db.query.opendockArticleSetup.findFirst({
where: (table, { and, eq }) =>
and(
eq(table.av, release.LineItemArticleWeight),
eq(table.customer, release.DeliveryAddressHumanReadableId),
),
});
// TODO: add in docks from lst db here to make it more universal for the team
/**
* ReleaseState
* 0 = open
@@ -101,6 +126,7 @@ const postRelease = async (release: Releases) => {
: release.DeliveryState === 4 && "Completed",
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
// TODO: look in the remarks in the release and if its says
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
refNumbers: [release.ReleaseNumber],
//refNumber: release.ReleaseNumber,
@@ -115,6 +141,19 @@ const postRelease = async (release: Releases) => {
},
units: null,
customFields: [
{
name: "strCustomer",
type: "str",
label: "Customer",
value: `${release.DeliveryAddressDescription}`,
description: "Who is the customer ",
placeholder: "",
dropDownValues: [],
minLengthOrValue: 1,
hiddenFromCarrier: false,
requiredForCarrier: false,
requiredForWarehouse: false,
},
{
name: "strArticle",
type: "str",
@@ -190,6 +229,72 @@ const postRelease = async (release: Releases) => {
if (existing) {
const id = existing.openDockAptId;
if (
(releaseLoadtypeCheck ||
opendDockArticleCheck?.loadType === "drop" ||
defaultDock?.value === "drop") &&
(release.DeliveryState === 0 || release.DeliveryState === 1)
) {
const setArrival = { ...newDockApt, status: "Arrived" };
// set to arrived
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
setArrival,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
if (response.status === 400) {
log.error({}, response.data.data.message);
return;
}
// update the release in the db leaving as insert just incase something weird happened
try {
await db
.insert(opendockApt)
.values({
release: release.ReleaseNumber,
openDockAptId: response.data.data.id,
appointment: response.data.data,
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
openDockAptId: response.data.data.id,
appointment: response.data.data,
upd_date: sql`NOW()`,
},
})
.returning();
log.info({}, `${release.ReleaseNumber} was updated`);
} catch (e) {
log.error(
{ stack: e },
`Error updating the release: ${release.ReleaseNumber}`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ stack: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
return;
}
// set to inprogress
await delay(1500);
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
@@ -229,7 +334,7 @@ const postRelease = async (release: Releases) => {
log.info({}, `${release.ReleaseNumber} was updated`);
} catch (e) {
log.error(
{ error: e },
{ stack: e },
`Error updating the release: ${release.ReleaseNumber}`,
);
}
@@ -237,12 +342,67 @@ const postRelease = async (release: Releases) => {
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ error: e.response.data },
{ stack: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
return;
}
} else {
try {
const response = await axios.patch(
`${process.env.OPENDOCK_URL}/appointment/${id}`,
newDockApt,
{
headers: {
"content-type": "application/json; charset=utf-8",
Authorization: `Bearer ${odToken.odToken}`,
},
},
);
if (response.status === 400) {
log.error({}, response.data.data.message);
return;
}
// update the release in the db leaving as insert just incase something weird happened
try {
await db
.insert(opendockApt)
.values({
release: release.ReleaseNumber,
openDockAptId: response.data.data.id,
appointment: response.data.data,
})
.onConflictDoUpdate({
target: opendockApt.release,
set: {
openDockAptId: response.data.data.id,
appointment: response.data.data,
upd_date: sql`NOW()`,
},
})
.returning();
log.info({}, `${release.ReleaseNumber} was updated`);
} catch (e) {
log.error(
{ stack: e },
`Error updating the release: ${release.ReleaseNumber}`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
//console.info(newDockApt);
log.error(
{ stack: e.response.data },
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
);
return;
}
}
} else {
try {
const response = await axios.post(
@@ -287,13 +447,13 @@ const postRelease = async (release: Releases) => {
log.info({}, `${release.ReleaseNumber} was created`);
} catch (e) {
log.error({ error: e }, "Error creating new release");
log.error({ stack: e }, "Error creating new release");
}
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
} catch (e: any) {
log.error(
{ error: e?.response?.data },
"Error posting new release to opendock",
{ stack: e?.response?.data },
`Error posting new release to opendock, ${release.ReleaseNumber}`,
);
return;

View File

@@ -0,0 +1,277 @@
import { desc, eq, sql } from "drizzle-orm";
import { Router } from "express";
import z from "zod";
import { db } from "../db/db.controller.js";
import {
type NewOpendockArticleSetup,
opendockArticleSetup,
} from "../db/schema/opendock_articleSetup.js";
import { opendockDockSetup } from "../db/schema/opendock_docks.js";
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
import {
type SqlQuery,
sqlQuerySelector,
} from "../prodSql/prodSqlQuerySelector.utils.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router();
const newArticleLink = z.object({
av: z.number().int(),
description: z.string(),
customer: z.string().min(1).max(32),
customerDescription: z.string().min(2).max(100),
loadType: z
.enum(["drop", "live"])
.optional()
.describe("What roles are available to use."),
dock: z
//.record(z.string(), z.unknown())
.string()
.optional()
.describe(
"This allows us to add extra fields to the data to parse against",
),
});
const newDockLink = z.object({
name: z.string(),
dockID: z.string(),
});
r.post("/", async (req, res) => {
try {
const validated = newArticleLink.parse(req.body) as NewOpendockArticleSetup;
const newLink = await db
.insert(opendockArticleSetup)
.values({
av: validated.av,
description: validated.description,
customer: validated.customer,
customerDescription: validated.customerDescription,
loadType: validated.loadType,
dock: validated.dock,
add_user: req.user?.username ?? "lst_user",
})
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${validated.av} was just added `,
data: newLink as any,
status: 200,
});
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Internal Server Error adding article link",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
r.patch("/:id", async (req, res) => {
const { id } = req.params;
const updates: Record<string, unknown | null> = {};
if (req.body?.loadType !== undefined) {
updates.loadType = req.body.loadType;
}
if (req.body?.dock !== undefined) {
updates.dock = req.body.dock;
}
updates.upd_user = req.user?.username || "lst_user";
updates.upd_date = sql`NOW()`;
const updatedSetting = await db
.update(opendockArticleSetup)
.set(updates)
.where(eq(opendockArticleSetup.id, id))
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${updatedSetting[0]?.av} was just updated. `,
data: updatedSetting,
status: 200,
});
});
r.delete("/:id", async (req, res) => {
const { id } = req.params;
const removeLink = await db
.delete(opendockArticleSetup)
.where(eq(opendockArticleSetup.id, id))
.returning();
return apiReturn(res, {
success: false,
level: "info", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Article link was deleted",
data: removeLink,
status: 200, //connect.success ? 200 : 400,
});
});
r.get("/", async (_, res) => {
const { data } = await tryCatch(
db
.select()
.from(opendockArticleSetup)
.orderBy(desc(opendockArticleSetup.customer))
.limit(1500),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All links`,
data: data ?? [],
status: 200,
});
});
r.get("/customers/:av", async (req, res) => {
const { av } = req.params;
const avSQLQuery = sqlQuerySelector(`opendock.addressLink`) as SqlQuery;
if (!avSQLQuery.success) {
return apiReturn(res, {
success: true,
level: "error",
module: "opendock",
subModule: "articleCheck",
message: avSQLQuery.message,
data: [],
status: 200,
});
}
const { data } = await tryCatch(
prodQuery(
avSQLQuery.query.replace("[articleCheck]", av),
"openDock addressLink",
),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All customers linked to av: ${av}`,
data: data?.data ?? ([] as any),
status: 200,
});
});
r.post("/dock", async (req, res) => {
try {
const validated = newDockLink.parse(req.body) as any;
const newLink = await db
.insert(opendockDockSetup)
.values({
name: validated.name,
dockID: validated.dockID,
add_user: req.user?.username ?? "lst_user",
})
.returning();
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `${validated.name} was just added `,
data: newLink as any,
status: 200,
});
} catch (err) {
if (err instanceof z.ZodError) {
const flattened = z.flattenError(err);
// return res.status(400).json({
// error: "Validation failed",
// details: flattened,
// });
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Validation failed",
data: [flattened.fieldErrors],
status: 400, //connect.success ? 200 : 400,
});
}
return apiReturn(res, {
success: false,
level: "error", //connect.success ? "info" : "error",
module: "opendock",
subModule: "articleCheck",
message: "Internal Server Error adding dock link",
data: [err],
status: 400, //connect.success ? 200 : 400,
});
}
});
r.get("/dock", async (_, res) => {
const { data } = await tryCatch(
db
.select()
.from(opendockDockSetup)
.orderBy(desc(opendockDockSetup.name))
.limit(1500),
);
return apiReturn(res, {
success: true,
level: "info",
module: "opendock",
subModule: "articleCheck",
message: `All dock links`,
data: data ?? [],
status: 200,
});
});
export default r;

View File

@@ -1,20 +1,24 @@
import { type Express, Router } from "express";
import type { Express } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import articleCheck from "./opendock.articleCheck.route.js";
import getApt from "./opendockGetRelease.route.js";
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
// Apply auth to entire router
const router = Router();
// is the feature even on?
router.use(featureCheck("opendock_sync"));
app.use(
`${baseUrl}/api/opendock`,
featureCheck("opendock_sync"),
requireAuth,
getApt,
);
// we need to make sure we are authenticated to see the releases
router.use(requireAuth);
router.use(getApt);
app.use(`${baseUrl}/api/opendock`, router);
app.use(
`${baseUrl}/api/opendock/articleCheck`,
featureCheck("opendock_sync"),
requireAuth,
articleCheck,
);
};

View File

@@ -1,7 +1,7 @@
import { desc, gte, sql } from "drizzle-orm";
import { Router } from "express";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock.schema.js";
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";

View File

@@ -10,9 +10,7 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
const router = Router();
router.use(requireAuth);
router.use(start);
router.use(stop);
router.use(restart);
app.use(`${baseUrl}/api/system/prodSql`, router);
app.use(`${baseUrl}/api/system/prodSql/start`, requireAuth, start);
app.use(`${baseUrl}/api/system/prodSql/stop`, requireAuth, stop);
app.use(`${baseUrl}/api/system/prodSql/restart`, requireAuth, restart);
};

View File

@@ -4,7 +4,7 @@ import { closePool, connectProdSql } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/restart", async (_, res) => {
r.post("/", async (_, res) => {
await closePool();
await new Promise((r) => setTimeout(r, 2000));

View File

@@ -4,7 +4,7 @@ import { connectProdSql } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/start", async (_, res) => {
r.post("/", async (_, res) => {
const connect = await connectProdSql();
apiReturn(res, {
success: connect.success,

View File

@@ -4,7 +4,7 @@ import { closePool } from "./prodSqlConnection.controller.js";
const r = Router();
r.post("/stop", async (_, res) => {
r.post("/", async (_, res) => {
const connect = await closePool();
apiReturn(res, {
success: connect.success,

View File

@@ -0,0 +1,21 @@
/*
disables sql jobs.
*/
--EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
-- DECLARE @JobName varchar(max) = '[jobName]'
-- UPDATE msdb.dbo.sysjobs
-- SET enabled = 0
-- WHERE name = @JobName;
DECLARE @JobName NVARCHAR(128) = N'[jobName]';
IF EXISTS (
SELECT 1
FROM msdb.dbo.sysjobs
WHERE name = @JobName
)
BEGIN
EXEC msdb.dbo.sp_update_job
@job_name = @JobName,
@enabled = 0;
END

View File

@@ -0,0 +1,34 @@
/*
This will return all address with a sales price.
*/
WITH ranked AS (
SELECT
av.id,
av.humanReadableId as av,
av.Alias as description,
-- CONCAT(ad.HumanReadableId, ' - ',ad.Name) as customer ,
ad.HumanReadableId as customer,
ad.Name as customerDescription,
ROW_NUMBER() OVER (
PARTITION BY AddressId, sp.articleId
ORDER BY ValidAfter DESC
) AS rn
FROM [test1_AlplaPROD2.0_Read].[masterData].[SalesPrice] as sp (nolock)
/* av */
left join
[test1_AlplaPROD2.0_Read].[masterData].[Article] as av (nolock) on
av.id = sp.articleId
/* address */
left join
[test1_AlplaPROD2.0_Read].[masterData].[Address] as ad (nolock) on
ad.id = AddressId
)
SELECT *
FROM ranked
WHERE rn = 1
and ranked.av = '[articleCheck]'
order by customerDescription

View File

@@ -21,7 +21,7 @@ SELECT
,[MainMaterialId]
,[MainMaterialHumanReadableId]
,[MainMaterialDescription]
,[AdditionalInformation1]
,[AdditionalInformation1] -- we will use this to reference as the first check
,[AdditionalInformation2]
,[D365SupplierLot]
,[TradeUnits]
@@ -49,7 +49,7 @@ SELECT
,[PaymentTermsDescription]
,[Remark]
,[DeliveryAddressId]
,[DeliveryAddressHumanReadableId]
,[DeliveryAddressHumanReadableId] --use this to validate with the new drop or live check
,[DeliveryAddressDescription]
,[DeliveryStreetName]
,[DeliveryAddressZip]

View File

@@ -1,16 +1,17 @@
use [test1_AlplaPROD2.0_Read]
SELECT
--JSON_VALUE(content, '$.EntityId') as labelId
JSON_VALUE(content, '$.EntityId') as labelId,
a.id
,ActorName
,FORMAT(PrintDate, 'yyyy-MM-dd HH:mm') as printDate
--,FORMAT(l.PrintDate, 'yyyy-MM-dd HH:mm') as printDate
,Format(COALESCE(l.PrintDate, e.ProductionDate), 'yyyy-MM-dd HH:mm') as printDate
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
,l.ArticleHumanReadableId as av
,l.ArticleDescription as alias
,PrintedCopies
,p.name as printerName
,RunningNumber
,COALESCE(l.ArticleHumanReadableId,e.ArticleHumanReadableId) as av
,COALESCE(l.ArticleDescription, av.Name) as alias
,COALESCE(l.PrintedCopies, 0) as PrintedCopies
,COALESCE(p.name,'External Label not tracked') as printerName
,COALESCE(l.RunningNumber, e.RunningNumber) as runningNumber
--,*
FROM [support].[AuditLog] (nolock) as a
@@ -18,10 +19,20 @@ left join
[labelling].[InternalLabel] (nolock) as l on
l.id = JSON_VALUE(content, '$.EntityId')
OUTER APPLY (
SELECT TOP 1 *
FROM labelling.ExternalLabel e
WHERE e.id = JSON_VALUE(a.content, '$.EntityId')
ORDER BY e.Id DESC
) e
left join
[masterData].[printer] (nolock) as p on
p.id = l.PrinterId
left join
[masterData].[article] (nolock) as av on
av.HumanReadableId = e.ArticleHumanReadableId
where message like '%reprint%'
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
and a.id > [ignoreList]

View File

@@ -16,6 +16,7 @@ import { setupUtilsRoutes } from "./utils/utils.routes.js";
export const setupRoutes = (baseUrl: string, app: Express) => {
//routes that are on by default
setupMobileRoutes(baseUrl, app);
setupSystemRoutes(baseUrl, app);
setupAdminRoutes(baseUrl, app);
setupApiDocsRoutes(baseUrl, app);
@@ -28,5 +29,4 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
setupNotificationRoutes(baseUrl, app);
setupOCPRoutes(baseUrl, app);
setupTCPRoutes(baseUrl, app);
setupMobileRoutes(baseUrl, app);
};

View File

@@ -8,6 +8,7 @@ import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
import { createLogger } from "./logger/logger.controller.js";
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
import { startNotifications } from "./notification/notification.controller.js";
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
import { createNotifications } from "./notification/notifications.master.js";
import { printerSync } from "./ocp/ocp.printer.manage.js";
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
@@ -83,6 +84,9 @@ const start = async () => {
startNotifications();
serversChecks();
aggregateRouteHitsForBusinessDay();
// can be removed at a later date
sqlJobCleanUp();
}, 5 * 1000);
process.on("uncaughtException", async (err) => {

View File

@@ -357,6 +357,17 @@ const newSettings: NewSetting[] = [
roles: ["admin"],
seedVersion: 1,
},
{
name: "defaultLoadType",
value: "drop",
active: false,
description:
"What is the default load type we will use for creating new apt: drop or live are the current options.",
moduleName: "opendock",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
];
export const baseSettingValidationCheck = async () => {

View File

@@ -61,7 +61,7 @@ export async function aggregateRouteHitsForBusinessDay() {
const rows = await db
.select({
businessDate: sql<string>`${businessDate}`,
businessDate: sql<string>`CAST(${businessDate} AS date)`,
method: analytics.method,
routePattern: analytics.routePattern,
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
.insert(analyticsDaily)
.values(rows)
.values(values)
.onConflictDoUpdate({
target: [
analyticsDaily.businessDate,

View File

@@ -1,10 +1,14 @@
import { createAccessControl } from "better-auth/plugins/access";
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
export const statement = {
app: ["read", "create", "share", "update", "delete", "readAll"],
user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
...defaultStatements,
app: ["read", "create", "update", "delete", "readAll"],
quality: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"],
notifications: ["read", "create", "update", "delete", "readAll"],
} as const;
export const ac = createAccessControl(statement);
@@ -12,15 +16,50 @@ export const ac = createAccessControl(statement);
export const user = ac.newRole({
app: ["read", "create"],
notifications: ["read", "create"],
openDock: ["read"],
});
export const manager = ac.newRole({
app: ["read", "create", "update"],
mobile: ["read", "create", "update"],
openDock: ["read", "create", "update"],
});
export const transport = ac.newRole({
app: ["read", "create", "update"],
openDock: ["read", "create", "update"],
});
export const admin = ac.newRole({
app: ["read", "create", "update"],
mobile: ["read", "create", "update"],
user: ["create", "update", "ban"],
openDock: ["read", "create", "update"],
});
export const systemAdmin = ac.newRole({
app: ["read", "create", "share", "update", "delete", "readAll"],
user: ["ban"],
quality: ["read", "create", "share", "update", "delete", "readAll"],
notifications: ["read", "create", "share", "update", "delete", "readAll"],
...adminAc.statements,
app: ["read", "create", "update", "delete", "readAll"],
quality: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"],
notifications: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"],
});
/* example usage
const canCreateProject = await authClient.admin.hasPermission({
permissions: {
project: ["create"],
},
});
// You can also check multiple resource permissions at the same time
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
permissions: {
project: ["create"],
sale: ["create"]
},
});
*/

View File

@@ -13,7 +13,7 @@ import {
//import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import * as rawSchema from "../db/schema/auth.schema.js";
import { ac, admin, systemAdmin, user } from "./auth.permissions.js";
import { ac, admin, manager, systemAdmin, user } from "./auth.permissions.js";
import { allowedOrigins } from "./cors.utils.js";
import { sendEmail } from "./sendEmail.utils.js";
@@ -163,6 +163,7 @@ export const auth = betterAuth({
roles: {
admin,
user,
manager,
systemAdmin,
},
}),

View File

@@ -17,6 +17,7 @@ export const allowedOrigins = [
`http://${process.env.PROD_SERVER}:3100`, // temp
`http://usmcd1olp082:3000`,
`${process.env.EXTERNAL_URL}`, // internal docker
"chrome-extension://mddoackclclnbkmofficmmepfnadolfa",
];
export const lstCors = () => {
return cors({

View File

@@ -7,6 +7,7 @@
<title>Logistics Support Tool</title>
</head>
<body>
<script>
const configScript = document.createElement("script");
configScript.src = `${window.location.origin}/lst/api/lst-config.js`;

View File

@@ -8,6 +8,7 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"@base-ui/react": "^1.5.0",
"@fontsource-variable/inter": "^5.2.8",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-form": "^1.28.5",
@@ -34,6 +35,7 @@
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"tw-animate-css": "^1.4.0",
"vite-imagetools": "^10.0.0",
"zod": "^4.3.6"
},
"devDependencies": {
@@ -439,6 +441,15 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -484,6 +495,66 @@
"node": ">=6.9.0"
}
},
"node_modules/@base-ui/react": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.5.0.tgz",
"integrity": "sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"@base-ui/utils": "0.2.9",
"@floating-ui/react-dom": "^2.1.8",
"@floating-ui/utils": "^0.2.11",
"use-sync-external-store": "^1.6.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@date-fns/tz": "^1.2.0",
"@types/react": "^17 || ^18 || ^19",
"date-fns": "^4.0.0",
"react": "^17 || ^18 || ^19",
"react-dom": "^17 || ^18 || ^19"
},
"peerDependenciesMeta": {
"@date-fns/tz": {
"optional": true
},
"@types/react": {
"optional": true
},
"date-fns": {
"optional": true
}
}
},
"node_modules/@base-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"@floating-ui/utils": "^0.2.11",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"@types/react": "^17 || ^18 || ^19",
"react": "^17 || ^18 || ^19",
"react-dom": "^17 || ^18 || ^19"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@better-auth/utils": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.1.tgz",
@@ -649,6 +720,16 @@
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
@@ -1333,6 +1414,519 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@inquirer/ansi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
@@ -3163,6 +3757,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/pluginutils": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
@@ -6672,6 +7288,12 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -7511,6 +8133,15 @@
"node": ">= 4"
}
},
"node_modules/imagetools-core": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/imagetools-core/-/imagetools-core-9.1.0.tgz",
"integrity": "sha512-xQjs+2vrxLnAjCq+omuNkd5UQTld9/bP8+YT0LyYTlKfuSQtgUBvqhUwGugzSAh6sCdN+LnROMuLswn5hZ9Fhg==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -10460,6 +11091,12 @@
"node": ">=0.10.0"
}
},
"node_modules/reselect": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz",
"integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==",
"license": "MIT"
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -10638,7 +11275,6 @@
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -10804,6 +11440,50 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -11837,6 +12517,23 @@
}
}
},
"node_modules/vite-imagetools": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/vite-imagetools/-/vite-imagetools-10.0.0.tgz",
"integrity": "sha512-+83L32YPU/2BOHWhudO2+9T5HBvb3+0qHoUNN7fb0+XcAoXilx7aE25cDPWU5kBi5Yc750zYCvHxgfyR+tAuMA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.5",
"imagetools-core": "^9.1.0",
"sharp": "^0.34.1"
},
"engines": {
"node": ">=22.0.0"
},
"peerDependencies": {
"vite": ">=7.0.0"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@base-ui/react": "^1.5.0",
"@fontsource-variable/inter": "^5.2.8",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-form": "^1.28.5",
@@ -21,6 +22,8 @@
"better-auth": "^1.5.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"lucide-react": "^0.577.0",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
@@ -34,9 +37,8 @@
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"tw-animate-css": "^1.4.0",
"zod": "^4.3.6",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0"
"vite-imagetools": "^10.0.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^9.36.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -19,11 +19,14 @@ export default function Header() {
const { data: session } = useSession();
const { signOut } = authClient;
const router = useRouterState();
const navigate = useNavigate();
const currentPath = router.location.href;
return (
<header className="sticky top-0 z-50 flex w-full items-center border-b bg-background">
<header
className={`sticky top-0 z-50 flex w-full items-center border-b ${session?.session.impersonatedBy ? "bg-amber-600" : "bg-background"} `}
>
<div className="flex justify-between w-full">
<div className="flex items-center gap-2 px-4">
<div className="flex flex-row">
@@ -48,6 +51,20 @@ export default function Header() {
<span className="font-semibold text-2xl">Logistics Support Tool</span>
</div>
<div className="m-1 flex gap-1">
<div>
{session?.session.impersonatedBy && (
<Button
onClick={async () => {
await authClient.admin.stopImpersonating();
await authClient.getSession();
window.location.assign("/lst/app/admin/users");
}}
>
Stop Impersonating
</Button>
)}
</div>
<div>
<ModeToggle />
</div>

View File

@@ -0,0 +1,51 @@
import { useRouter } from "@tanstack/react-router";
import { Card, CardContent, CardHeader } from "./ui/card";
export default function NotFound() {
const router = useRouter();
let url: string;
if (window.location.origin.includes("localhost")) {
url = `https://www.youtube.com/watch?v=dQw4w9WgXcQ`;
} else if (window.location.origin.includes("vms006")) {
url = `https://${window.location.hostname.replace("vms006", "prod.alpla.net/")}lst/app/old`;
} else {
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
}
return (
<div className="flex items-center justify-center bg-background text-foreground">
<Card>
<CardHeader>
<p className="text-2xl">
Oops, Looks like you hit a link you shouldn't have
</p>
</CardHeader>
<CardContent>
<p className="mt-3 text-muted-foreground">
Your have tried to go to a page that you are not authorized to be
at.
</p>
<div className="flex justify-center">
<div>
<a href={`${url}`} target="_blank" rel="noopener">
<b>
<strong>OLD - LST Home</strong>
</b>
</a>
</div>
<div>
<button
type="button"
className="w-64"
onClick={() => router.navigate({ to: "/", replace: true })}
>
<strong>Home</strong>
</button>
</div>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { Bell, Logs, Server, Settings, UsersRound } from "lucide-react";
import { getSettings } from "../../lib/queries/getSettings";
import {
SidebarGroup,
SidebarGroupContent,
@@ -23,6 +24,7 @@ import {
export default function AdminSidebar({ session }: any) {
const { setOpen } = useSidebar();
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
const items = [
{
title: "Notifications",
@@ -68,9 +70,11 @@ export default function AdminSidebar({ session }: any) {
title: "Scan users",
url: "/admin/scanUsers",
icon: UsersRound,
role: ["systemAdmin", "admin"],
role: ["systemAdmin", "admin", "manager"],
module: "admin",
active: true,
active:
!isLoading &&
settings.filter((n: any) => n.name === "mobile")[0].active,
},
];
return (
@@ -79,9 +83,9 @@ export default function AdminSidebar({ session }: any) {
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<>
{item.role.includes(session.user.role) && (
<SidebarMenuItem key={item.title}>
<div key={item.title}>
{item.role.includes(session.user.role) && item.active && (
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link to={item.url} onClick={() => setOpen(false)}>
<item.icon />
@@ -90,7 +94,7 @@ export default function AdminSidebar({ session }: any) {
</SidebarMenuButton>
</SidebarMenuItem>
)}
</>
</div>
))}
</SidebarMenu>
</SidebarGroupContent>

View File

@@ -1,11 +1,12 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { Link, useRouterState } from "@tanstack/react-router";
import { ChevronRight } from "lucide-react";
import { ChevronRight, ScrollText } from "lucide-react";
import { getSettings } from "../../lib/queries/getSettings";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "../ui/collapsible";
import {
SidebarGroup,
SidebarGroupContent,
@@ -19,43 +20,55 @@ import {
useSidebar,
} from "../ui/sidebar";
const docs = [
{
title: "Notifications",
url: "/intro",
//icon,
isActive: window.location.pathname.includes("notifications") ?? false,
items: [
{
title: "Reprints",
url: "/reprints",
},
{
title: "New Blocking order",
url: "/qualityBlocking",
},
],
},
{
title: "Mobile",
url: "/updateInstructions",
isActive: false,
items: [
{
title: "Settings",
url: "/mobile-settings",
},
],
},
];
export default function DocBar() {
const { setOpen } = useSidebar();
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
const pathname = useRouterState({
select: (s) => s.location.pathname,
});
const isNotifications = pathname.includes("notifications");
const docs = [
{
title: "Notifications",
url: "notifications/intro",
//icon,
isActive: true,
items: [
{
title: "Reprints",
icon: ScrollText,
url: "notifications/reprints",
},
{
title: "New Blocking order",
icon: ScrollText,
url: "notifications/qualityBlocking",
},
],
},
{
title: "Mobile",
url: "mobile/updateInstructions",
isActive:
!isLoading &&
settings.filter((n: any) => n.name === "mobile")[0].active,
items: [
{
title: "Update Instructions",
icon: ScrollText,
url: "mobile/updateInstructions",
},
// {
// title: "Settings",
// icon: ScrollText,
// url: "mobile/mobile-settings",
// },
],
},
];
return (
<SidebarGroup>
<SidebarGroupLabel>Docs</SidebarGroupLabel>
@@ -72,8 +85,9 @@ export default function DocBar() {
</SidebarMenu>
<SidebarMenu>
{docs.map((item) => (
<div key={item.title}>
{item.isActive && (
<Collapsible
key={item.title}
asChild
defaultOpen={isNotifications}
className="group/collapsible"
@@ -81,10 +95,7 @@ export default function DocBar() {
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
<Link
to={"/docs/$"}
params={{ _splat: `notifications${item.url}` }}
>
<Link to={"/docs/$"} params={{ _splat: `${item.url}` }}>
{item.title}
</Link>
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
@@ -97,9 +108,11 @@ export default function DocBar() {
<SidebarMenuSubButton asChild>
<Link
to={"/docs/$"}
params={{ _splat: `notifications${subItem.url}` }}
params={{ _splat: `${subItem.url}` }}
onClick={() => setOpen(false)}
>
{subItem.title}
<subItem.icon />
<span>{subItem.title}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
@@ -108,6 +121,8 @@ export default function DocBar() {
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}
</div>
))}
</SidebarMenu>
</SidebarGroupContent>

View File

@@ -1,5 +1,5 @@
import { Link } from "@tanstack/react-router";
import { ScanText, ScrollText } from "lucide-react";
import { ScanText } from "lucide-react";
import {
SidebarGroup,
SidebarGroupContent,
@@ -10,14 +10,14 @@ import {
useSidebar,
} from "../ui/sidebar";
export default function MobileBar({ session }: any) {
export default function MobileBar() {
const { setOpen } = useSidebar();
const items = [
{
title: "Update Instructions",
url: "/",
icon: ScrollText,
},
// {
// title: "Update Instructions",
// url: "/",
// icon: ScrollText,
// },
{
title: "Scan Log",
url: "/",
@@ -25,8 +25,6 @@ export default function MobileBar({ session }: any) {
},
];
console.log(session);
return (
<SidebarGroup>
<SidebarGroupLabel>Mobile</SidebarGroupLabel>

View File

@@ -0,0 +1,94 @@
import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { ChevronRight, Link as link } from "lucide-react";
import { permissionQuery } from "../../lib/queries/permsCheck";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "../ui/collapsible";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from "../ui/sidebar";
export default function TransportationBar() {
const { data: canCreate = false } = useQuery(
permissionQuery({
openDock: ["create"],
}),
);
const { setOpen } = useSidebar();
const items = [
{
title: "Open Dock",
url: "/transportation",
//icon,
isActive: canCreate,
items: [
{
title: "ArticleLink",
icon: link,
url: "/transportation/opendock",
},
],
},
];
return (
<SidebarGroup>
<SidebarGroupLabel>Transportation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<div key={item.title}>
{item.isActive && (
<Collapsible
asChild
//defaultOpen={isNotifications}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.title}
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<Link
to={subItem.url}
onClick={() => setOpen(false)}
>
<subItem.icon />
<span>{subItem.title}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}
</div>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
);
}

View File

@@ -1,3 +1,4 @@
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
import {
Sidebar,
SidebarContent,
@@ -6,12 +7,21 @@ import {
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { useSession } from "@/lib/auth-client";
import { getSettings } from "../../lib/queries/getSettings";
import { permissionQuery } from "../../lib/queries/permsCheck";
import AdminSidebar from "./AdminBar";
import DocBar from "./DocBar";
import MobileBar from "./MobileBar";
import TransportationBar from "./TransportationBar";
export function AppSidebar() {
const { data: session } = useSession();
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
const { data: canRead = false } = useQuery(
permissionQuery({
openDock: ["read"],
}),
);
return (
<Sidebar
@@ -24,10 +34,20 @@ export function AppSidebar() {
<SidebarMenuItem>
<SidebarContent>
<DocBar />
<MobileBar session={session} />
{!isLoading &&
settings.filter((n: any) => n.name === "mobile")[0].active && (
<MobileBar />
)}
{!isLoading &&
settings.filter((n: any) => n.name === "opendock_sync")[0]
?.active &&
canRead && <TransportationBar />}
{session &&
(session.user.role === "admin" ||
session.user.role === "systemAdmin") && (
session.user.role === "systemAdmin" ||
session.user.role === "manager") && (
<AdminSidebar session={session} />
)}
</SidebarContent>

View File

@@ -1,42 +1,45 @@
import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import type * as React from "react";
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils";
import { cn } from "@/lib/utils"
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: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
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:
"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:
"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",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
default:
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
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",
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",
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
icon: "size-8",
"icon-xs":
"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: {
variant: "default",
size: "default",
},
},
);
}
)
function Button({
className,
@@ -46,9 +49,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button";
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
@@ -58,7 +61,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
)
}
export { Button, buttonVariants };
export { Button, buttonVariants }

View File

@@ -0,0 +1,299 @@
"use client"
import * as React from "react"
import { Combobox as ComboboxPrimitive } from "@base-ui/react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group"
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
const Combobox = ComboboxPrimitive.Root
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
}
function ComboboxTrigger({
className,
children,
...props
}: ComboboxPrimitive.Trigger.Props) {
return (
<ComboboxPrimitive.Trigger
data-slot="combobox-trigger"
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
{...props}
>
{children}
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</ComboboxPrimitive.Trigger>
)
}
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
return (
<ComboboxPrimitive.Clear
data-slot="combobox-clear"
render={<InputGroupButton variant="ghost" size="icon-xs" />}
className={cn(className)}
{...props}
>
<XIcon className="pointer-events-none" />
</ComboboxPrimitive.Clear>
)
}
function ComboboxInput({
className,
children,
disabled = false,
showTrigger = true,
showClear = false,
...props
}: ComboboxPrimitive.Input.Props & {
showTrigger?: boolean
showClear?: boolean
}) {
return (
<InputGroup className={cn("w-auto", className)}>
<ComboboxPrimitive.Input
render={<InputGroupInput disabled={disabled} />}
{...props}
/>
<InputGroupAddon align="inline-end">
{showTrigger && (
<InputGroupButton
size="icon-xs"
variant="ghost"
asChild
data-slot="input-group-button"
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
disabled={disabled}
>
<ComboboxTrigger />
</InputGroupButton>
)}
{showClear && <ComboboxClear disabled={disabled} />}
</InputGroupAddon>
{children}
</InputGroup>
)
}
function ComboboxContent({
className,
side = "bottom",
sideOffset = 6,
align = "start",
alignOffset = 0,
anchor,
...props
}: ComboboxPrimitive.Popup.Props &
Pick<
ComboboxPrimitive.Positioner.Props,
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
>) {
return (
<ComboboxPrimitive.Portal>
<ComboboxPrimitive.Positioner
side={side}
sideOffset={sideOffset}
align={align}
alignOffset={alignOffset}
anchor={anchor}
className="isolate z-50"
>
<ComboboxPrimitive.Popup
data-slot="combobox-content"
data-chips={!!anchor}
className={cn("group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none 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}
/>
</ComboboxPrimitive.Positioner>
</ComboboxPrimitive.Portal>
)
}
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
return (
<ComboboxPrimitive.List
data-slot="combobox-list"
className={cn(
"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
className
)}
{...props}
/>
)
}
function ComboboxItem({
className,
children,
...props
}: ComboboxPrimitive.Item.Props) {
return (
<ComboboxPrimitive.Item
data-slot="combobox-item"
className={cn(
"relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<ComboboxPrimitive.ItemIndicator
render={
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
}
>
<CheckIcon className="pointer-events-none" />
</ComboboxPrimitive.ItemIndicator>
</ComboboxPrimitive.Item>
)
}
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
return (
<ComboboxPrimitive.Group
data-slot="combobox-group"
className={cn(className)}
{...props}
/>
)
}
function ComboboxLabel({
className,
...props
}: ComboboxPrimitive.GroupLabel.Props) {
return (
<ComboboxPrimitive.GroupLabel
data-slot="combobox-label"
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
return (
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
)
}
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
return (
<ComboboxPrimitive.Empty
data-slot="combobox-empty"
className={cn(
"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex",
className
)}
{...props}
/>
)
}
function ComboboxSeparator({
className,
...props
}: ComboboxPrimitive.Separator.Props) {
return (
<ComboboxPrimitive.Separator
data-slot="combobox-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function ComboboxChips({
className,
...props
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
ComboboxPrimitive.Chips.Props) {
return (
<ComboboxPrimitive.Chips
data-slot="combobox-chips"
className={cn(
"flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
)
}
function ComboboxChip({
className,
children,
showRemove = true,
...props
}: ComboboxPrimitive.Chip.Props & {
showRemove?: boolean
}) {
return (
<ComboboxPrimitive.Chip
data-slot="combobox-chip"
className={cn(
"flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
className
)}
{...props}
>
{children}
{showRemove && (
<ComboboxPrimitive.ChipRemove
render={<Button variant="ghost" size="icon-xs" />}
className="-ml-1 opacity-50 hover:opacity-100"
data-slot="combobox-chip-remove"
>
<XIcon className="pointer-events-none" />
</ComboboxPrimitive.ChipRemove>
)}
</ComboboxPrimitive.Chip>
)
}
function ComboboxChipsInput({
className,
...props
}: ComboboxPrimitive.Input.Props) {
return (
<ComboboxPrimitive.Input
data-slot="combobox-chip-input"
className={cn("min-w-16 flex-1 outline-none", className)}
{...props}
/>
)
}
function useComboboxAnchor() {
return React.useRef<HTMLDivElement | null>(null)
}
export {
Combobox,
ComboboxInput,
ComboboxContent,
ComboboxList,
ComboboxItem,
ComboboxGroup,
ComboboxLabel,
ComboboxCollection,
ComboboxEmpty,
ComboboxSeparator,
ComboboxChips,
ComboboxChip,
ComboboxChipsInput,
ComboboxTrigger,
ComboboxValue,
useComboboxAnchor,
}

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,154 @@
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
/* eslint-disable */
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-group"
role="group"
className={cn(
"group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className,
)}
{...props}
/>
);
}
const inputGroupAddonVariants = cva(
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
{
variants: {
align: {
"inline-start":
"order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
"inline-end":
"order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
"block-start":
"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
"block-end":
"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
},
},
defaultVariants: {
align: "inline-start",
},
},
);
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return;
}
e.currentTarget.parentElement?.querySelector("input")?.focus();
}}
{...props}
/>
);
}
const inputGroupButtonVariants = cva(
"flex items-center gap-2 text-sm shadow-none",
{
variants: {
size: {
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
sm: "",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
},
);
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size"> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
);
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
className,
)}
{...props}
/>
);
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
return (
<Textarea
data-slot="input-group-control"
className={cn(
"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent",
className,
)}
{...props}
/>
);
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
};

View File

@@ -1,6 +1,6 @@
import type * as React from "react";
import * as React from "react"
import { cn } from "@/lib/utils";
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
@@ -8,14 +8,12 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
className,
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
);
)
}
export { Input };
export { Input }

View File

@@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
)
}
export { Textarea }

View File

@@ -1,5 +1,4 @@
import { Tooltip as TooltipPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
@@ -40,7 +39,7 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 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}
@@ -52,4 +51,4 @@ function TooltipContent({
);
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };

View File

@@ -0,0 +1,141 @@
//import { useMutation } from "@tanstack/react-query";
//import { Button } from "../../components/ui/button";
import { Separator } from "../../components/ui/separator";
// image imports
const images = import.meta.glob(
"/public/imgs/docs/mobile/*.{png,jpg,jpeg,webp}",
{
eager: true,
query: "w=300;600;1200&format=webp",
import: "default",
},
) as Record<string, string>;
const server = window.LST_CONFIG?.server;
const firstScan = images[`/public/imgs/docs/mobile/${server}-1.png`];
const secondScan = images[`/public/imgs/docs/mobile/${server}-2.png`];
const thirdScan = images[`/public/imgs/docs/mobile/${server}-3.png`];
export default function UpdateInstructions() {
// const getFile = useMutation({
// mutationFn: async () => {
// // 1. Fetch the file from the public folder
// const response = await fetch(
// `/lst/app/stage-now/${window.LST_CONFIG?.server}-stageNow.pdf`,
// );
// if (!response.ok) throw new Error("Network response was not ok");
// // 2. Convert to blob
// return await response.blob();
// },
// onSuccess: (blob) => {
// // 3. Create a temporary anchor element to trigger download
// const url = window.URL.createObjectURL(blob);
// const a = document.createElement("a");
// a.href = url;
// a.download = `${window.LST_CONFIG?.server}-stageNow.pdf`; // Desired filename
// document.body.appendChild(a);
// a.click();
// // 4. Cleanup
// window.URL.revokeObjectURL(url);
// document.body.removeChild(a);
// },
// });
return (
<div className="flex flex-row gap-2">
<div className="w-1/2">
<div className="flex flex-col gap-1 justify-center">
<div>
<p className="text-center text-3xl">
Updating the lst mobile scanner app
</p>
<p className="text-center text-sm">
NOTE: LST Mobile only works on TC8300
</p>
</div>
{/* <div className="flex justify-center">
<Button
onClick={() => getFile.mutate()}
disabled={getFile.isPending}
>
{getFile.isPending ? "Downloading..." : "Get StageNow Codes"}
</Button>
</div> */}
</div>
<Separator className="m-3" />
<div>
<p className="text-2xl text-center">
How to know the scanner has an update?
</p>
<p>
The bottom part of the scanner will show a red or orange bar
indicating there is an update. As shown below
</p>
<div className="flex flex-row gap-2 justify-center">
<div className="w-1/2">
<img
src="/lst/app/imgs/docs/mobile/critical_update.png"
alt="Home"
className="max-w-[50%] h-auto"
/>
</div>
<div className="w-1/2">
<img
src="/lst/app/imgs/docs/mobile/update.png"
alt="Home"
className="max-w-[50%] h-auto"
/>
</div>
</div>
</div>
<Separator className="m-3" />
<div>
<p className="text-2xl text-center">
To update the scanner follow the below steps.
</p>
<p>Step 1) Tap the 3 dots top right of the home screen</p>
<img
src="/lst/app/imgs/docs/mobile/ehs_homeScreen.png"
alt="Home"
className="max-w-[25%] h-auto m-3"
/>
<p>Step 2) Tap tools</p>
<img
src="/lst/app/imgs/docs/mobile/ehs_menu.png"
alt="Home"
className="max-w-[25%] h-auto m-3"
/>
<p>Step 3) Tap Stage Now</p>
<img
src="/lst/app/imgs/docs/mobile/tools.png"
alt="Home"
className="max-w-[25%] h-auto m-3"
/>
<p>
Step 4) Scan the 3 barcode's to the right or from the printed sheet
</p>
<img
src="/lst/app/imgs/docs/mobile/stagenow.png"
alt="Home"
className="max-w-[25%] h-auto m-3"
/>
</div>
</div>
<div className="w-1/2">
<p>Scan Commands</p>
<Separator className="m-3" />
<div className="flex flex-col justify-center">
<img src={firstScan} alt="First Scan" className="m-3" />
<img src={secondScan} alt="Second Scan" className="m-3" />
<img src={thirdScan} alt="Third Scan" className="m-3" />
</div>
</div>
</div>
);
}

View File

@@ -1,3 +0,0 @@
export default function updateInstructions() {
return <div>updateInstructions</div>;
}

View File

@@ -0,0 +1,47 @@
import type { Router } from "@tanstack/react-router";
import axios from "axios";
import { toast } from "sonner";
let appRouter: Router<any, any> | null = null;
export function setApiRouter(router: Router<any, any>) {
appRouter = router;
}
export const api = axios.create({
baseURL: "/lst/api",
withCredentials: true,
timeout: 15000,
});
api.interceptors.response.use(
(response) => response,
(error) => {
const isNetworkError =
error.code === "ERR_NETWORK" ||
error.code === "ECONNABORTED" ||
error.message === "Network Error" ||
error.message === "Failed to fetch" ||
!error.response;
if (error.response?.status === 403) {
// redirect, toast, or show forbidden page
toast.error("Unauthorized to be here");
appRouter?.navigate({ to: "/forbidden", replace: true });
}
if (error.response?.status === 401) {
// redirect, toast, or show forbidden page
toast.error("Unauthorized to be here");
appRouter?.navigate({ to: "/login", replace: true });
}
if (isNetworkError) {
appRouter?.navigate({ to: "/app-down", replace: true });
}
return Promise.reject(error);
},
);

View File

@@ -1,6 +1,12 @@
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
import { redirect } from "@tanstack/react-router";
import {
adminClient,
genericOAuthClient,
usernameClient,
} from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
import { ac, admin, systemAdmin, user } from "./auth-permissions";
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
export const authClient = createAuthClient({
baseURL: `${window.location.origin}/lst/api/auth`,
@@ -10,11 +16,21 @@ export const authClient = createAuthClient({
roles: {
admin,
user,
manager,
systemAdmin,
},
}),
genericOAuthClient(),
usernameClient(),
],
fetchOptions: {
onError() {
redirect({
to: "/app-down",
replace: true,
});
},
},
});
export const { useSession, signUp, signIn, signOut } = authClient;

View File

@@ -1,21 +1,111 @@
import { createAccessControl } from "better-auth/plugins/access";
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
/*
When new perms are added based on there criteria make sure they are added here as well
*/
type SelectableRole = {
label: string;
value: string;
};
export const selectableRoles: SelectableRole[] = [
{ label: "User", value: "user" },
{ label: "Manager", value: "manager" },
{ label: "Transport", value: "transport" },
{ label: "Admin", value: "admin" },
{ label: "System Admin", value: "systemAdmin" },
];
export const statement = {
project: ["create", "share", "update", "delete"],
user: ["ban"],
...defaultStatements,
app: ["read", "create", "update", "delete", "readAll"],
quality: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"],
notifications: ["read", "create", "update", "delete", "readAll"],
} as const;
export const ac = createAccessControl(statement);
export const user = ac.newRole({
project: ["create"],
app: ["read", "create"],
notifications: ["read", "create"],
openDock: ["read"],
});
export const manager = ac.newRole({
app: ["read", "create", "update"],
mobile: ["read", "create", "update"],
openDock: ["read", "create", "update"],
});
export const transport = ac.newRole({
app: ["read", "create", "update"],
openDock: ["read", "create", "update"],
});
export const admin = ac.newRole({
project: ["create", "update"],
app: ["read", "create", "update"],
mobile: ["read", "create", "update"],
user: ["create", "update", "ban"],
openDock: ["read", "create", "update"],
});
export const systemAdmin = ac.newRole({
project: ["create", "update", "delete"],
user: ["ban"],
...adminAc.statements,
app: ["read", "create", "update", "delete", "readAll"],
quality: ["read", "create", "update", "delete", "readAll"],
mobile: ["read", "create", "update", "delete", "readAll"],
logistics: ["read", "create", "update", "delete", "readAll"],
notifications: ["read", "create", "update", "delete", "readAll"],
openDock: ["read", "create", "update", "delete"],
});
/*
inside a component
const { data: canImpersonate = false } = useQuery(
permissionQuery({
user: ["impersonate"],
logistics: ["delete"]
}),
);
on the before load use this example
beforeLoad: async ({ location }) => {
const { data: session } = await authClient.getSession();
//const allowedRole = ["systemAdmin", "admin", "manager"];
const canAccess = await authClient.admin.hasPermission({
permissions: {
opendock: ["create"],
logistics: ["delete"]
},
});
if (!session?.user) {
throw redirect({
to: "/",
search: {
redirect: location.href,
},
});
}
//if (!allowedRole.includes(session.user.role as string)) {
if (!canAccess) {
throw redirect({
to: "/",
});
}
return { user: session.user };
},
*/

View File

@@ -13,9 +13,7 @@ const docsMap: Record<string, ComponentType> = {};
for (const path in modules) {
const mod = modules[path] as DocModule;
const slug = path
.replace("../docs/", "")
.replace(".tsx", "");
const slug = path.replace("../docs/", "").replace(".tsx", "");
// "notifications/intro"
docsMap[slug] = mod.default;

View File

@@ -0,0 +1,84 @@
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "../../components/ui/combobox";
import { Label } from "../../components/ui/label";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
// type SelectOption = {
// value: string;
// label: string;
// };
type ComboBoxFieldProps = {
data: string[];
label: string;
placeholder: string;
};
export const ComboBoxField = ({
data = [],
label,
placeholder = "",
}: ComboBoxFieldProps) => {
const field = useFieldContext<string>();
return (
<div className="grid gap-3">
<div className="grid gap-3">
<Label htmlFor={field.name}>{label}</Label>
{/* <Select
value={field.state.value}
onValueChange={(value) => field.handleChange(value)}
>
<SelectTrigger
id={field.name}
onBlur={field.handleBlur}
className="w-min-2/3 w-max-fit"
>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent position={"popper"}>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select> */}
<Combobox
items={data}
//value={field.state.value ?? ""}
// onValueChange={(value) => {
// console.log(value);
// field.handleChange(value);
// }}
>
<ComboboxInput placeholder={placeholder} />
{/* <ComboboxInput
//showTrigger={false}
placeholder={placeholder}
onBlur={field.handleBlur}
showClear
/> */}
<ComboboxContent className="max-h-72 overflow-y-auto">
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -18,12 +18,14 @@ type SelectFieldProps = {
label: string;
options: SelectOption[];
placeholder?: string;
disabled?: boolean;
};
export const SelectField = ({
label,
options,
placeholder,
disabled = false,
}: SelectFieldProps) => {
const field = useFieldContext<string>();
@@ -34,6 +36,7 @@ export const SelectField = ({
<Select
value={field.state.value}
onValueChange={(value) => field.handleChange(value)}
disabled={disabled}
>
<SelectTrigger
id={field.name}

View File

@@ -1,5 +1,6 @@
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
import { CheckboxField } from "./CheckBox.Field";
import { ComboBoxField } from "./ComboBox.Field";
import { DynamicInputField } from "./DynamicInput.Field";
import { InputField } from "./Input.Field";
import { InputPasswordField } from "./InputPassword.Field";
@@ -21,6 +22,7 @@ export const { useAppForm } = createFormHook({
//Searchable,
SwitchField,
DynamicInputField,
ComboBoxField,
},
formComponents: { SubmitButton },
fieldContext,

View File

@@ -0,0 +1,25 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function getActiveArticle() {
return queryOptions({
queryKey: ["getActiveArticle"],
queryFn: () => dataFetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const dataFetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await api.get("/datamart/activeArticles");
if (!data.success) {
throw new Error(data.message ?? "Failed to load articles");
}
return data.data ?? [];
};

View File

@@ -0,0 +1,25 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function getArticleLinks() {
return queryOptions({
queryKey: ["getArticleLinks"],
queryFn: () => dataFetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const dataFetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await api.get("/opendock/articleCheck");
if (!data.success) {
throw new Error(data.message ?? "Failed to load article links");
}
return data.data ?? [];
};

View File

@@ -0,0 +1,26 @@
import { queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
export function getCustomerByAv(av: string) {
return queryOptions({
queryKey: ["getCustomerByAv", av],
queryFn: () => dataFetch(av),
staleTime: 5000,
enabled: !!av,
refetchOnWindowFocus: true,
//placeholderData: keepPreviousData,
});
}
const dataFetch = async (av: string) => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await api.get(`/opendock/articleCheck/customers/${av}`);
if (!data.success) {
throw new Error(data.message ?? "Failed to load customers");
}
return data.data ?? [];
};

View File

@@ -1,25 +1,25 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
import { api } from "../apiHelper";
export function getScanUsers() {
return queryOptions({
queryKey: ["getScanUsers"],
queryFn: () => fetch(),
queryFn: () => dataFetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
const dataFetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 1500));
}
const { data } = await axios.get("/lst/api/mobile/auth/user", {
withCredentials: true,
timeout: 5000,
});
const { data } = await api.get("/mobile/auth/user");
if (!data.success) {
throw new Error(data.message ?? "Failed to load scan users");
}
return data.data;
return data.data ?? [];
};

View File

@@ -0,0 +1,23 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { api } from "../apiHelper";
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 api.get("/mobile/available");
return data.data;
};

Some files were not shown because too many files have changed in this diff Show More