Compare commits
18 Commits
ebf1060475
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a0c729b9a | |||
| 057a570e43 | |||
| 52974aa0b4 | |||
| ecfbda9036 | |||
| 389211186f | |||
| 3a27fd8542 | |||
| 1f6637c512 | |||
| 1840ac5e58 | |||
| 636daaed0a | |||
| 71c83062cb | |||
| cd67c4de80 | |||
| 36ac1dccb4 | |||
| 514a44b6de | |||
| a7bb364a2f | |||
| 047cc7cdf0 | |||
| 8dc4d70e28 | |||
| c8931c7249 | |||
| 67f36c5499 |
@@ -9,4 +9,4 @@ builds
|
|||||||
testFiles
|
testFiles
|
||||||
nssm.exe
|
nssm.exe
|
||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.msi
|
VSCodeSetup-x64-1.120.0.msi
|
||||||
112
CHANGELOG.md
@@ -1,5 +1,117 @@
|
|||||||
# All Changes to LST can be found below.
|
# 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)
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
|
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
|
||||||
import * as scanUserSchema from "./schema/scanUsers.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}`;
|
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, {
|
export const db = drizzle(queryClient, {
|
||||||
schema: {
|
schema: {
|
||||||
...scanUserSchema,
|
...scanUserSchema,
|
||||||
|
...settingsSchema,
|
||||||
|
...opendockAVCheck,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ export const opendockApt = pgTable(
|
|||||||
"opendock_apt",
|
"opendock_apt",
|
||||||
{
|
{
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
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(),
|
openDockAptId: text("open_dock_apt_id").notNull(),
|
||||||
appointment: jsonb("appointment").notNull().default([]),
|
appointment: jsonb("appointment").notNull().default([]),
|
||||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
},
|
},
|
||||||
(table) => ({
|
(table) => ({
|
||||||
releaseIdx: index("opendock_apt_release_idx").on(table.release),
|
|
||||||
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
||||||
table.openDockAptId,
|
table.openDockAptId,
|
||||||
),
|
),
|
||||||
46
backend/db/schema/opendock_articleSetup.ts
Normal 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
|
||||||
|
>;
|
||||||
21
backend/db/schema/opendock_docks.ts
Normal 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
|
||||||
|
>;
|
||||||
@@ -38,7 +38,7 @@ export const settings = pgTable(
|
|||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||||
uniqueIndex("name").on(table.name),
|
uniqueIndex("settings_name_unique").on(table.name),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,27 @@ router.get("/ehs/xml", (_, res) => {
|
|||||||
return res.sendFile(xmlPath);
|
return res.sendFile(xmlPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/upgrade/android/13", (_, res) => {
|
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(
|
const apkPath = path.join(
|
||||||
downloadDir,
|
downloadDir,
|
||||||
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
|
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
|
||||||
@@ -82,7 +102,7 @@ router.get("/upgrade/android/13", (_, res) => {
|
|||||||
return res.sendFile(apkPath);
|
return res.sendFile(apkPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/upgrade/android/14", (_, res) => {
|
router.get("/android/upgrade/14", (_, res) => {
|
||||||
const apkPath = path.join(
|
const apkPath = path.join(
|
||||||
downloadDir,
|
downloadDir,
|
||||||
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
|
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { addHours } from "date-fns";
|
|||||||
import { formatInTimeZone } from "date-fns-tz";
|
import { formatInTimeZone } from "date-fns-tz";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
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 { settings } from "../db/schema/settings.schema.js";
|
||||||
import { createLogger } from "../logger/logger.controller.js";
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
@@ -27,6 +27,9 @@ type Releases = {
|
|||||||
Quantity: number;
|
Quantity: number;
|
||||||
LineItemArticleWeight: number;
|
LineItemArticleWeight: number;
|
||||||
CustomerReleaseNumber: string;
|
CustomerReleaseNumber: string;
|
||||||
|
DeliveryAddressDescription: string;
|
||||||
|
DeliveryAddressHumanReadableId: string;
|
||||||
|
AdditionalInformation1: string;
|
||||||
};
|
};
|
||||||
const timeZone = process.env.TIMEZONE as string;
|
const timeZone = process.env.TIMEZONE as string;
|
||||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||||
@@ -73,6 +76,28 @@ const postRelease = async (release: Releases) => {
|
|||||||
log.info({}, "Refreshing Auth Token");
|
log.info({}, "Refreshing Auth Token");
|
||||||
await getToken();
|
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
|
* ReleaseState
|
||||||
* 0 = open
|
* 0 = open
|
||||||
@@ -101,6 +126,7 @@ const postRelease = async (release: Releases) => {
|
|||||||
: release.DeliveryState === 4 && "Completed",
|
: release.DeliveryState === 4 && "Completed",
|
||||||
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
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
|
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
|
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||||
refNumbers: [release.ReleaseNumber],
|
refNumbers: [release.ReleaseNumber],
|
||||||
//refNumber: release.ReleaseNumber,
|
//refNumber: release.ReleaseNumber,
|
||||||
@@ -115,6 +141,19 @@ const postRelease = async (release: Releases) => {
|
|||||||
},
|
},
|
||||||
units: null,
|
units: null,
|
||||||
customFields: [
|
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",
|
name: "strArticle",
|
||||||
type: "str",
|
type: "str",
|
||||||
@@ -190,6 +229,72 @@ const postRelease = async (release: Releases) => {
|
|||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const id = existing.openDockAptId;
|
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 {
|
try {
|
||||||
const response = await axios.patch(
|
const response = await axios.patch(
|
||||||
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||||
@@ -229,7 +334,7 @@ const postRelease = async (release: Releases) => {
|
|||||||
log.info({}, `${release.ReleaseNumber} was updated`);
|
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e },
|
{ stack: e },
|
||||||
`Error updating the release: ${release.ReleaseNumber}`,
|
`Error updating the release: ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -237,12 +342,67 @@ const postRelease = async (release: Releases) => {
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
//console.info(newDockApt);
|
//console.info(newDockApt);
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e.response.data },
|
{ stack: e.response.data },
|
||||||
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
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 {
|
} else {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
@@ -287,13 +447,13 @@ const postRelease = async (release: Releases) => {
|
|||||||
|
|
||||||
log.info({}, `${release.ReleaseNumber} was created`);
|
log.info({}, `${release.ReleaseNumber} was created`);
|
||||||
} catch (e) {
|
} 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
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e?.response?.data },
|
{ stack: e?.response?.data },
|
||||||
"Error posting new release to opendock",
|
`Error posting new release to opendock, ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
277
backend/opendock/opendock.articleCheck.route.ts
Normal 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;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
|
import articleCheck from "./opendock.articleCheck.route.js";
|
||||||
|
|
||||||
import getApt from "./opendockGetRelease.route.js";
|
import getApt from "./opendockGetRelease.route.js";
|
||||||
|
|
||||||
@@ -13,4 +14,11 @@ export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
getApt,
|
getApt,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
`${baseUrl}/api/opendock/articleCheck`,
|
||||||
|
featureCheck("opendock_sync"),
|
||||||
|
requireAuth,
|
||||||
|
articleCheck,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { desc, gte, sql } from "drizzle-orm";
|
import { desc, gte, sql } from "drizzle-orm";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { db } from "../db/db.controller.js";
|
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 { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
/*
|
/*
|
||||||
disables sql jobs.
|
disables sql jobs.
|
||||||
*/
|
*/
|
||||||
EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
|
--EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
|
||||||
-- DECLARE @JobName varchar(max) = '[jobName]'
|
-- DECLARE @JobName varchar(max) = '[jobName]'
|
||||||
-- UPDATE msdb.dbo.sysjobs
|
-- UPDATE msdb.dbo.sysjobs
|
||||||
-- SET enabled = 0
|
-- SET enabled = 0
|
||||||
-- WHERE name = @JobName;
|
-- 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
|
||||||
34
backend/prodSql/queries/opendock.addressLink.sql
Normal 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
|
||||||
@@ -21,7 +21,7 @@ SELECT
|
|||||||
,[MainMaterialId]
|
,[MainMaterialId]
|
||||||
,[MainMaterialHumanReadableId]
|
,[MainMaterialHumanReadableId]
|
||||||
,[MainMaterialDescription]
|
,[MainMaterialDescription]
|
||||||
,[AdditionalInformation1]
|
,[AdditionalInformation1] -- we will use this to reference as the first check
|
||||||
,[AdditionalInformation2]
|
,[AdditionalInformation2]
|
||||||
,[D365SupplierLot]
|
,[D365SupplierLot]
|
||||||
,[TradeUnits]
|
,[TradeUnits]
|
||||||
@@ -49,7 +49,7 @@ SELECT
|
|||||||
,[PaymentTermsDescription]
|
,[PaymentTermsDescription]
|
||||||
,[Remark]
|
,[Remark]
|
||||||
,[DeliveryAddressId]
|
,[DeliveryAddressId]
|
||||||
,[DeliveryAddressHumanReadableId]
|
,[DeliveryAddressHumanReadableId] --use this to validate with the new drop or live check
|
||||||
,[DeliveryAddressDescription]
|
,[DeliveryAddressDescription]
|
||||||
,[DeliveryStreetName]
|
,[DeliveryStreetName]
|
||||||
,[DeliveryAddressZip]
|
,[DeliveryAddressZip]
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
use [test1_AlplaPROD2.0_Read]
|
use [test1_AlplaPROD2.0_Read]
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
--JSON_VALUE(content, '$.EntityId') as labelId
|
JSON_VALUE(content, '$.EntityId') as labelId,
|
||||||
a.id
|
a.id
|
||||||
,ActorName
|
,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
|
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
|
||||||
,l.ArticleHumanReadableId as av
|
,COALESCE(l.ArticleHumanReadableId,e.ArticleHumanReadableId) as av
|
||||||
,l.ArticleDescription as alias
|
,COALESCE(l.ArticleDescription, av.Name) as alias
|
||||||
,PrintedCopies
|
,COALESCE(l.PrintedCopies, 0) as PrintedCopies
|
||||||
,p.name as printerName
|
,COALESCE(p.name,'External Label not tracked') as printerName
|
||||||
,RunningNumber
|
,COALESCE(l.RunningNumber, e.RunningNumber) as runningNumber
|
||||||
--,*
|
--,*
|
||||||
FROM [support].[AuditLog] (nolock) as a
|
FROM [support].[AuditLog] (nolock) as a
|
||||||
|
|
||||||
@@ -18,10 +19,20 @@ left join
|
|||||||
[labelling].[InternalLabel] (nolock) as l on
|
[labelling].[InternalLabel] (nolock) as l on
|
||||||
l.id = JSON_VALUE(content, '$.EntityId')
|
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
|
left join
|
||||||
[masterData].[printer] (nolock) as p on
|
[masterData].[printer] (nolock) as p on
|
||||||
p.id = l.PrinterId
|
p.id = l.PrinterId
|
||||||
|
|
||||||
|
left join
|
||||||
|
[masterData].[article] (nolock) as av on
|
||||||
|
av.HumanReadableId = e.ArticleHumanReadableId
|
||||||
where message like '%reprint%'
|
where message like '%reprint%'
|
||||||
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
||||||
and a.id > [ignoreList]
|
and a.id > [ignoreList]
|
||||||
|
|||||||
@@ -357,6 +357,17 @@ const newSettings: NewSetting[] = [
|
|||||||
roles: ["admin"],
|
roles: ["admin"],
|
||||||
seedVersion: 1,
|
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 () => {
|
export const baseSettingValidationCheck = async () => {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
import { adminAc } from "better-auth/plugins/admin/access";
|
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
...defaultStatements,
|
||||||
//user: ["ban"],
|
app: ["read", "create", "update", "delete", "readAll"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "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;
|
} as const;
|
||||||
|
|
||||||
export const ac = createAccessControl(statement);
|
export const ac = createAccessControl(statement);
|
||||||
@@ -13,16 +16,50 @@ export const ac = createAccessControl(statement);
|
|||||||
export const user = ac.newRole({
|
export const user = ac.newRole({
|
||||||
app: ["read", "create"],
|
app: ["read", "create"],
|
||||||
notifications: ["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({
|
export const admin = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
user: ["create", "update", "ban"],
|
||||||
|
openDock: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
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,
|
...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"]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
//import { eq } from "drizzle-orm";
|
//import { eq } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import * as rawSchema from "../db/schema/auth.schema.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 { allowedOrigins } from "./cors.utils.js";
|
||||||
import { sendEmail } from "./sendEmail.utils.js";
|
import { sendEmail } from "./sendEmail.utils.js";
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ export const auth = betterAuth({
|
|||||||
roles: {
|
roles: {
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
|
manager,
|
||||||
systemAdmin,
|
systemAdmin,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const allowedOrigins = [
|
|||||||
`http://${process.env.PROD_SERVER}:3100`, // temp
|
`http://${process.env.PROD_SERVER}:3100`, // temp
|
||||||
`http://usmcd1olp082:3000`,
|
`http://usmcd1olp082:3000`,
|
||||||
`${process.env.EXTERNAL_URL}`, // internal docker
|
`${process.env.EXTERNAL_URL}`, // internal docker
|
||||||
|
"chrome-extension://mddoackclclnbkmofficmmepfnadolfa",
|
||||||
];
|
];
|
||||||
export const lstCors = () => {
|
export const lstCors = () => {
|
||||||
return cors({
|
return cors({
|
||||||
|
|||||||
699
frontend/package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@base-ui/react": "^1.5.0",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tanstack/react-form": "^1.28.5",
|
"@tanstack/react-form": "^1.28.5",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"vite-imagetools": "^10.0.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -439,6 +441,15 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@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": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
@@ -484,6 +495,66 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@better-auth/utils": {
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.1.tgz",
|
"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": "^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": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
||||||
@@ -1333,6 +1414,519 @@
|
|||||||
"url": "https://github.com/sponsors/nzakas"
|
"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": {
|
"node_modules/@inquirer/ansi": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
|
||||||
@@ -3163,6 +3757,28 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.59.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
"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"
|
"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": {
|
"node_modules/esutils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
@@ -7511,6 +8133,15 @@
|
|||||||
"node": ">= 4"
|
"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": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -10460,6 +11091,12 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
@@ -10638,7 +11275,6 @@
|
|||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -10804,6 +11440,50 @@
|
|||||||
"url": "https://github.com/sponsors/colinhacks"
|
"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": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"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": {
|
"node_modules/web-streams-polyfill": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@base-ui/react": "^1.5.0",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tanstack/react-form": "^1.28.5",
|
"@tanstack/react-form": "^1.28.5",
|
||||||
@@ -21,6 +22,8 @@
|
|||||||
"better-auth": "^1.5.5",
|
"better-auth": "^1.5.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-tz": "^3.2.0",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
@@ -34,9 +37,8 @@
|
|||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"zod": "^4.3.6",
|
"vite-imagetools": "^10.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"zod": "^4.3.6"
|
||||||
"date-fns-tz": "^3.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 17 KiB |
BIN
frontend/public/imgs/docs/mobile/usbow1-1.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/public/imgs/docs/mobile/usbow1-2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/public/imgs/docs/mobile/usbow1-3.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
frontend/public/imgs/docs/mobile/usmar1-1.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
frontend/public/imgs/docs/mobile/usmar1-2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/public/imgs/docs/mobile/usmar1-3.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
frontend/public/imgs/docs/mobile/usstp1-1.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/public/imgs/docs/mobile/usstp1-2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/public/imgs/docs/mobile/usstp1-3.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
frontend/public/stage-now/usbow1-stageNow.pdf
Normal file
BIN
frontend/public/stage-now/usmar1-stageNow.pdf
Normal file
BIN
frontend/public/stage-now/usstp1-stageNow.pdf
Normal file
94
frontend/src/components/Sidebar/TransportationBar.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -8,13 +8,20 @@ import {
|
|||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useSession } from "@/lib/auth-client";
|
import { useSession } from "@/lib/auth-client";
|
||||||
import { getSettings } from "../../lib/queries/getSettings";
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
|
import { permissionQuery } from "../../lib/queries/permsCheck";
|
||||||
import AdminSidebar from "./AdminBar";
|
import AdminSidebar from "./AdminBar";
|
||||||
import DocBar from "./DocBar";
|
import DocBar from "./DocBar";
|
||||||
import MobileBar from "./MobileBar";
|
import MobileBar from "./MobileBar";
|
||||||
|
import TransportationBar from "./TransportationBar";
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||||
|
const { data: canRead = false } = useQuery(
|
||||||
|
permissionQuery({
|
||||||
|
openDock: ["read"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@@ -32,6 +39,11 @@ export function AppSidebar() {
|
|||||||
<MobileBar />
|
<MobileBar />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isLoading &&
|
||||||
|
settings.filter((n: any) => n.name === "opendock_sync")[0]
|
||||||
|
?.active &&
|
||||||
|
canRead && <TransportationBar />}
|
||||||
|
|
||||||
{session &&
|
{session &&
|
||||||
(session.user.role === "admin" ||
|
(session.user.role === "admin" ||
|
||||||
session.user.role === "systemAdmin" ||
|
session.user.role === "systemAdmin" ||
|
||||||
|
|||||||
299
frontend/src/components/ui/combobox.tsx
Normal 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,
|
||||||
|
}
|
||||||
154
frontend/src/components/ui/input-group.tsx
Normal 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,
|
||||||
|
};
|
||||||
@@ -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">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
@@ -8,14 +8,12 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
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",
|
"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",
|
||||||
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
className
|
||||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
||||||
className,
|
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input };
|
export { Input }
|
||||||
|
|||||||
18
frontend/src/components/ui/textarea.tsx
Normal 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 }
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
||||||
import type * as React from "react";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -52,4 +51,4 @@ function TooltipContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
||||||
|
|||||||
@@ -1,33 +1,49 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
//import { useMutation } from "@tanstack/react-query";
|
||||||
import { Button } from "../../components/ui/button";
|
//import { Button } from "../../components/ui/button";
|
||||||
import { Separator } from "../../components/ui/separator";
|
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() {
|
export default function UpdateInstructions() {
|
||||||
const getFile = useMutation({
|
// const getFile = useMutation({
|
||||||
mutationFn: async () => {
|
// mutationFn: async () => {
|
||||||
// 1. Fetch the file from the public folder
|
// // 1. Fetch the file from the public folder
|
||||||
const response = await fetch(
|
// const response = await fetch(
|
||||||
`/lst/app/stage-now/${window.LST_CONFIG?.server}-stageNow.pdf`,
|
// `/lst/app/stage-now/${window.LST_CONFIG?.server}-stageNow.pdf`,
|
||||||
);
|
// );
|
||||||
if (!response.ok) throw new Error("Network response was not ok");
|
// if (!response.ok) throw new Error("Network response was not ok");
|
||||||
|
|
||||||
// 2. Convert to blob
|
// // 2. Convert to blob
|
||||||
return await response.blob();
|
// return await response.blob();
|
||||||
},
|
// },
|
||||||
onSuccess: (blob) => {
|
// onSuccess: (blob) => {
|
||||||
// 3. Create a temporary anchor element to trigger download
|
// // 3. Create a temporary anchor element to trigger download
|
||||||
const url = window.URL.createObjectURL(blob);
|
// const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
// const a = document.createElement("a");
|
||||||
a.href = url;
|
// a.href = url;
|
||||||
a.download = `${window.LST_CONFIG?.server}-stageNow.pdf`; // Desired filename
|
// a.download = `${window.LST_CONFIG?.server}-stageNow.pdf`; // Desired filename
|
||||||
document.body.appendChild(a);
|
// document.body.appendChild(a);
|
||||||
a.click();
|
// a.click();
|
||||||
|
|
||||||
// 4. Cleanup
|
// // 4. Cleanup
|
||||||
window.URL.revokeObjectURL(url);
|
// window.URL.revokeObjectURL(url);
|
||||||
document.body.removeChild(a);
|
// document.body.removeChild(a);
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
@@ -41,14 +57,14 @@ export default function UpdateInstructions() {
|
|||||||
NOTE: LST Mobile only works on TC8300
|
NOTE: LST Mobile only works on TC8300
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
{/* <div className="flex justify-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => getFile.mutate()}
|
onClick={() => getFile.mutate()}
|
||||||
disabled={getFile.isPending}
|
disabled={getFile.isPending}
|
||||||
>
|
>
|
||||||
{getFile.isPending ? "Downloading..." : "Get StageNow Codes"}
|
{getFile.isPending ? "Downloading..." : "Get StageNow Codes"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<Separator className="m-3" />
|
<Separator className="m-3" />
|
||||||
<div>
|
<div>
|
||||||
@@ -115,21 +131,9 @@ export default function UpdateInstructions() {
|
|||||||
<p>Scan Commands</p>
|
<p>Scan Commands</p>
|
||||||
<Separator className="m-3" />
|
<Separator className="m-3" />
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
<img
|
<img src={firstScan} alt="First Scan" className="m-3" />
|
||||||
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-1.png`}
|
<img src={secondScan} alt="Second Scan" className="m-3" />
|
||||||
alt="Home"
|
<img src={thirdScan} alt="Third Scan" className="m-3" />
|
||||||
className="m-3"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-2.png`}
|
|
||||||
alt="Home"
|
|
||||||
className="m-3"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-3.png`}
|
|
||||||
alt="Home"
|
|
||||||
className="m-3"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ api.interceptors.response.use(
|
|||||||
appRouter?.navigate({ to: "/forbidden", replace: true });
|
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) {
|
if (isNetworkError) {
|
||||||
appRouter?.navigate({ to: "/app-down", replace: true });
|
appRouter?.navigate({ to: "/app-down", replace: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { redirect } from "@tanstack/react-router";
|
import { redirect } from "@tanstack/react-router";
|
||||||
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
|
import {
|
||||||
|
adminClient,
|
||||||
|
genericOAuthClient,
|
||||||
|
usernameClient,
|
||||||
|
} from "better-auth/client/plugins";
|
||||||
|
|
||||||
import { createAuthClient } from "better-auth/react";
|
import { createAuthClient } from "better-auth/react";
|
||||||
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
|
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
|
||||||
|
|
||||||
@@ -16,6 +21,7 @@ export const authClient = createAuthClient({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
genericOAuthClient(),
|
genericOAuthClient(),
|
||||||
|
usernameClient(),
|
||||||
],
|
],
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
onError() {
|
onError() {
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
import { adminAc } from "better-auth/plugins/admin/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 = {
|
export const statement = {
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
...defaultStatements,
|
||||||
//user: ["ban"],
|
app: ["read", "create", "update", "delete", "readAll"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "update", "delete", "readAll"],
|
||||||
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
logistics: ["read", "create", "update", "delete", "readAll"],
|
||||||
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
mobile: ["read", "create", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
openDock: ["read", "create", "update", "delete"],
|
||||||
|
notifications: ["read", "create", "update", "delete", "readAll"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ac = createAccessControl(statement);
|
export const ac = createAccessControl(statement);
|
||||||
@@ -15,39 +33,79 @@ export const ac = createAccessControl(statement);
|
|||||||
export const user = ac.newRole({
|
export const user = ac.newRole({
|
||||||
app: ["read", "create"],
|
app: ["read", "create"],
|
||||||
notifications: ["read", "create"],
|
notifications: ["read", "create"],
|
||||||
|
openDock: ["read"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const manager = ac.newRole({
|
export const manager = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
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({
|
export const admin = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
user: ["create", "update", "ban"],
|
||||||
|
openDock: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
//user: ["ban"],
|
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
...adminAc.statements,
|
...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({
|
|
||||||
|
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: {
|
permissions: {
|
||||||
project: ["create"],
|
opendock: ["create"],
|
||||||
},
|
logistics: ["delete"]
|
||||||
});
|
|
||||||
// You can also check multiple resource permissions at the same time
|
|
||||||
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
|
|
||||||
permissions: {
|
|
||||||
project: ["create"],
|
|
||||||
sale: ["create"]
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 };
|
||||||
|
},
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|||||||
84
frontend/src/lib/formSutff/ComboBox.Field.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,12 +18,14 @@ type SelectFieldProps = {
|
|||||||
label: string;
|
label: string;
|
||||||
options: SelectOption[];
|
options: SelectOption[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectField = ({
|
export const SelectField = ({
|
||||||
label,
|
label,
|
||||||
options,
|
options,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
disabled = false,
|
||||||
}: SelectFieldProps) => {
|
}: SelectFieldProps) => {
|
||||||
const field = useFieldContext<string>();
|
const field = useFieldContext<string>();
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ export const SelectField = ({
|
|||||||
<Select
|
<Select
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onValueChange={(value) => field.handleChange(value)}
|
onValueChange={(value) => field.handleChange(value)}
|
||||||
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
id={field.name}
|
id={field.name}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
|
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
|
||||||
import { CheckboxField } from "./CheckBox.Field";
|
import { CheckboxField } from "./CheckBox.Field";
|
||||||
|
import { ComboBoxField } from "./ComboBox.Field";
|
||||||
import { DynamicInputField } from "./DynamicInput.Field";
|
import { DynamicInputField } from "./DynamicInput.Field";
|
||||||
import { InputField } from "./Input.Field";
|
import { InputField } from "./Input.Field";
|
||||||
import { InputPasswordField } from "./InputPassword.Field";
|
import { InputPasswordField } from "./InputPassword.Field";
|
||||||
@@ -21,6 +22,7 @@ export const { useAppForm } = createFormHook({
|
|||||||
//Searchable,
|
//Searchable,
|
||||||
SwitchField,
|
SwitchField,
|
||||||
DynamicInputField,
|
DynamicInputField,
|
||||||
|
ComboBoxField,
|
||||||
},
|
},
|
||||||
formComponents: { SubmitButton },
|
formComponents: { SubmitButton },
|
||||||
fieldContext,
|
fieldContext,
|
||||||
|
|||||||
25
frontend/src/lib/queries/getActiveArticles.ts
Normal 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 ?? [];
|
||||||
|
};
|
||||||
25
frontend/src/lib/queries/getArticleLinks.ts
Normal 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 ?? [];
|
||||||
|
};
|
||||||
26
frontend/src/lib/queries/getCustomerByAv.ts
Normal 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 ?? [];
|
||||||
|
};
|
||||||
16
frontend/src/lib/queries/permsCheck.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { queryOptions } from "@tanstack/react-query";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
|
export function permissionQuery(permissions: Record<string, string[]>) {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["permission", permissions],
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await authClient.admin.hasPermission({
|
||||||
|
permissions,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data?.success ?? false;
|
||||||
|
},
|
||||||
|
staleTime: 30_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers'
|
|||||||
import { Route as AdminNotificationsRouteImport } from './routes/admin/notifications'
|
import { Route as AdminNotificationsRouteImport } from './routes/admin/notifications'
|
||||||
import { Route as AdminLogsRouteImport } from './routes/admin/logs'
|
import { Route as AdminLogsRouteImport } from './routes/admin/logs'
|
||||||
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
||||||
|
import { Route as TransportationOpendockIndexRouteImport } from './routes/transportation/opendock/index'
|
||||||
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
||||||
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
|
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
|
||||||
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
|
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
|
||||||
@@ -91,6 +92,12 @@ const authLoginRoute = authLoginRouteImport.update({
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const TransportationOpendockIndexRoute =
|
||||||
|
TransportationOpendockIndexRouteImport.update({
|
||||||
|
id: '/transportation/opendock/',
|
||||||
|
path: '/transportation/opendock/',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const authUserSignupRoute = authUserSignupRouteImport.update({
|
const authUserSignupRoute = authUserSignupRouteImport.update({
|
||||||
id: '/(auth)/user/signup',
|
id: '/(auth)/user/signup',
|
||||||
path: '/user/signup',
|
path: '/user/signup',
|
||||||
@@ -124,6 +131,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/user/signup': typeof authUserSignupRoute
|
'/user/signup': typeof authUserSignupRoute
|
||||||
|
'/transportation/opendock/': typeof TransportationOpendockIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
@@ -142,6 +150,7 @@ export interface FileRoutesByTo {
|
|||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/user/signup': typeof authUserSignupRoute
|
'/user/signup': typeof authUserSignupRoute
|
||||||
|
'/transportation/opendock': typeof TransportationOpendockIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
@@ -161,6 +170,7 @@ export interface FileRoutesById {
|
|||||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||||
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/(auth)/user/signup': typeof authUserSignupRoute
|
'/(auth)/user/signup': typeof authUserSignupRoute
|
||||||
|
'/transportation/opendock/': typeof TransportationOpendockIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
@@ -181,6 +191,7 @@ export interface FileRouteTypes {
|
|||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
|
| '/transportation/opendock/'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
@@ -199,6 +210,7 @@ export interface FileRouteTypes {
|
|||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
|
| '/transportation/opendock'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
@@ -217,6 +229,7 @@ export interface FileRouteTypes {
|
|||||||
| '/(auth)/user/profile'
|
| '/(auth)/user/profile'
|
||||||
| '/(auth)/user/resetpassword'
|
| '/(auth)/user/resetpassword'
|
||||||
| '/(auth)/user/signup'
|
| '/(auth)/user/signup'
|
||||||
|
| '/transportation/opendock/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
@@ -236,6 +249,7 @@ export interface RootRouteChildren {
|
|||||||
authUserProfileRoute: typeof authUserProfileRoute
|
authUserProfileRoute: typeof authUserProfileRoute
|
||||||
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
||||||
authUserSignupRoute: typeof authUserSignupRoute
|
authUserSignupRoute: typeof authUserSignupRoute
|
||||||
|
TransportationOpendockIndexRoute: typeof TransportationOpendockIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
@@ -331,6 +345,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof authLoginRouteImport
|
preLoaderRoute: typeof authLoginRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/transportation/opendock/': {
|
||||||
|
id: '/transportation/opendock/'
|
||||||
|
path: '/transportation/opendock'
|
||||||
|
fullPath: '/transportation/opendock/'
|
||||||
|
preLoaderRoute: typeof TransportationOpendockIndexRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/(auth)/user/signup': {
|
'/(auth)/user/signup': {
|
||||||
id: '/(auth)/user/signup'
|
id: '/(auth)/user/signup'
|
||||||
path: '/user/signup'
|
path: '/user/signup'
|
||||||
@@ -372,6 +393,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
authUserProfileRoute: authUserProfileRoute,
|
authUserProfileRoute: authUserProfileRoute,
|
||||||
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
||||||
authUserSignupRoute: authUserSignupRoute,
|
authUserSignupRoute: authUserSignupRoute,
|
||||||
|
TransportationOpendockIndexRoute: TransportationOpendockIndexRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|||||||
@@ -29,23 +29,36 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
|||||||
|
|
||||||
const form = useAppForm({
|
const form = useAppForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: loginEmail,
|
login: loginEmail,
|
||||||
password: "",
|
password: "",
|
||||||
rememberMe: rememberMe,
|
rememberMe: rememberMe,
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
// set remember me incase we want it later
|
// set remember me incase we want it later
|
||||||
|
const loginValue = value.login.trim();
|
||||||
|
const isEmailLogin = loginValue.includes("@");
|
||||||
|
|
||||||
if (value.rememberMe) {
|
if (value.rememberMe) {
|
||||||
localStorage.setItem("rememberMe", value.rememberMe.toString());
|
localStorage.setItem("rememberMe", value.rememberMe.toString());
|
||||||
localStorage.setItem("loginEmail", value.email.toLocaleLowerCase());
|
localStorage.setItem("loginEmail", loginValue.toLocaleLowerCase());
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("rememberMe");
|
localStorage.removeItem("rememberMe");
|
||||||
localStorage.removeItem("loginEmail");
|
localStorage.removeItem("loginEmail");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const login = await authClient.signIn.email({
|
const login = isEmailLogin
|
||||||
email: value.email,
|
? await authClient.signIn.email({
|
||||||
|
email: loginValue.toLowerCase(),
|
||||||
|
password: value.password,
|
||||||
|
fetchOptions: {
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate({ to: redirectPath ?? "/" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: await authClient.signIn.username({
|
||||||
|
username: loginValue,
|
||||||
password: value.password,
|
password: value.password,
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -95,11 +108,11 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
|||||||
form.handleSubmit();
|
form.handleSubmit();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<form.AppField name="email">
|
<form.AppField name="login">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<field.InputField
|
<field.InputField
|
||||||
label="Email"
|
label="Username or Email Address"
|
||||||
inputType="email"
|
inputType="text"
|
||||||
required={rememberMe}
|
required={rememberMe}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { useAppForm } from "@/lib/formSutff";
|
import { useAppForm } from "@/lib/formSutff";
|
||||||
|
import { Separator } from "../../components/ui/separator";
|
||||||
|
|
||||||
export const Route = createFileRoute("/(auth)/user/signup")({
|
export const Route = createFileRoute("/(auth)/user/signup")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
@@ -22,6 +23,7 @@ function RouteComponent() {
|
|||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
|
username: "",
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
if (value.password !== value.confirmPassword) {
|
if (value.password !== value.confirmPassword) {
|
||||||
@@ -33,6 +35,7 @@ function RouteComponent() {
|
|||||||
name: value.name,
|
name: value.name,
|
||||||
email: value.email,
|
email: value.email,
|
||||||
password: value.password,
|
password: value.password,
|
||||||
|
username: value.username ?? value.name,
|
||||||
callbackURL: `${window.location.origin}/lst/app`,
|
callbackURL: `${window.location.origin}/lst/app`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +74,15 @@ function RouteComponent() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.AppField>
|
</form.AppField>
|
||||||
|
<div className="m-2">
|
||||||
|
<p>Username is option if left blank it will be your name</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<form.AppField name="username">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField label="Username" inputType="text" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
|
||||||
{/* Email */}
|
{/* Email */}
|
||||||
<form.AppField name="email">
|
<form.AppField name="email">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Header from "@/components/Header";
|
|||||||
import { AppSidebar } from "@/components/Sidebar/sidebar";
|
import { AppSidebar } from "@/components/Sidebar/sidebar";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||||
import { ThemeProvider } from "@/lib/theme-provider";
|
import { ThemeProvider } from "@/lib/theme-provider";
|
||||||
|
import { TooltipProvider } from "../components/ui/tooltip";
|
||||||
import { useSession } from "../lib/auth-client";
|
import { useSession } from "../lib/auth-client";
|
||||||
|
|
||||||
const RootLayout = () => {
|
const RootLayout = () => {
|
||||||
@@ -14,7 +15,7 @@ const RootLayout = () => {
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
||||||
<Header />
|
<Header />
|
||||||
|
<TooltipProvider>
|
||||||
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ const RootLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Toaster expand richColors closeButton />
|
<Toaster expand richColors closeButton />
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../../components/ui/button";
|
import { Button } from "../../../components/ui/button";
|
||||||
@@ -10,6 +9,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
|
import { api } from "../../../lib/apiHelper";
|
||||||
import { useAppForm } from "../../../lib/formSutff";
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ export default function NewScanUser({ refetch }: { refetch: any }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await api.post(
|
||||||
"/lst/api/mobile/auth/user",
|
"/mobile/auth/user",
|
||||||
{
|
{
|
||||||
name: value.name,
|
name: value.name,
|
||||||
pinNumber: value.pinNumber,
|
pinNumber: value.pinNumber,
|
||||||
@@ -140,7 +140,7 @@ export default function NewScanUser({ refetch }: { refetch: any }) {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
const { data } = await api.get("/mobile/pin/new");
|
||||||
|
|
||||||
form.setFieldValue("pinNumber", data.data[0].pin);
|
form.setFieldValue("pinNumber", data.data[0].pin);
|
||||||
}}
|
}}
|
||||||
|
|||||||
153
frontend/src/routes/admin/-components/Newuser.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../../components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../../../components/ui/dialog";
|
||||||
|
import { authClient } from "../../../lib/auth-client";
|
||||||
|
import { selectableRoles } from "../../../lib/auth-permissions";
|
||||||
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
|
|
||||||
|
export default function NewUser({ refetch }: { refetch: any }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
role: "",
|
||||||
|
username: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
if (value.name === "" || value.email === "" || value.password === "") {
|
||||||
|
toast.error("Missing Mandatory data please try again ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await authClient.admin.createUser({
|
||||||
|
email: value.email, // required
|
||||||
|
password: value.password, // required
|
||||||
|
name: value.name, // required
|
||||||
|
role: (value.role ?? "user") as any,
|
||||||
|
data: { username: value.username },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.user) {
|
||||||
|
toast.success(`${value.name}, was just created `);
|
||||||
|
form.reset();
|
||||||
|
setOpen(false);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeModel = (e: boolean) => {
|
||||||
|
setOpen(e);
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openForm = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
|
||||||
|
<Button onClick={openForm}>Create new user</Button>
|
||||||
|
|
||||||
|
<DialogContent showCloseButton={false}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Scan user.</DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="name">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Name"
|
||||||
|
inputType="text"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Username can be your windows or anything, if you do not fill this
|
||||||
|
out your name is used as your username
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="username">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField label="Username" inputType="text" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="email">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Email"
|
||||||
|
inputType="email"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="password">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Password"
|
||||||
|
inputType="text"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-32">
|
||||||
|
<form.AppField name="role">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Roles"
|
||||||
|
placeholder="Select role"
|
||||||
|
options={selectableRoles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2 ">
|
||||||
|
<form.AppForm>
|
||||||
|
<form.SubmitButton>Submit</form.SubmitButton>
|
||||||
|
</form.AppForm>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { notificationSubs } from "../../lib/queries/notificationSubs";
|
import { notificationSubs } from "../../lib/queries/notificationSubs";
|
||||||
import { notifications } from "../../lib/queries/notifications";
|
import { notifications } from "../../lib/queries/notifications";
|
||||||
@@ -36,7 +37,7 @@ const updateNotifications = async (
|
|||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(
|
const res = await axios.patch(
|
||||||
`/lst/api/notification/${id}`,
|
`/notification/${id}`,
|
||||||
{ interval: data.interval },
|
{ interval: data.interval },
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@@ -110,7 +111,7 @@ const NotificationTable = () => {
|
|||||||
|
|
||||||
const removeNotification = async (ns: any) => {
|
const removeNotification = async (ns: any) => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.delete(`/lst/api/notification/sub`, {
|
const res = await api.delete(`/notification/sub`, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
data: {
|
data: {
|
||||||
userId: ns.userId,
|
userId: ns.userId,
|
||||||
@@ -168,7 +169,7 @@ const NotificationTable = () => {
|
|||||||
setActiveToggle(e);
|
setActiveToggle(e);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(
|
const res = await api.patch(
|
||||||
`/lst/api/notification/${i.row.original.id}`,
|
`/lst/api/notification/${i.row.original.id}`,
|
||||||
{
|
{
|
||||||
active: !activeToggle,
|
active: !activeToggle,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Suspense, useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Spinner } from "../../components/ui/spinner";
|
import { Spinner } from "../../components/ui/spinner";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
||||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
@@ -19,7 +20,13 @@ import NewScanUser from "./-components/NewScanUser";
|
|||||||
export const Route = createFileRoute("/admin/scanUsers")({
|
export const Route = createFileRoute("/admin/scanUsers")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
const allowedRole = ["systemAdmin", "admin", "manager"];
|
//const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||||
|
|
||||||
|
const canAccess = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
mobile: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -30,7 +37,9 @@ export const Route = createFileRoute("/admin/scanUsers")({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedRole.includes(session.user.role as string)) {
|
//if (!allowedRole.includes(session.user.role as string)) {
|
||||||
|
|
||||||
|
if (!canAccess) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
});
|
});
|
||||||
@@ -47,7 +56,7 @@ const updateSettings = async (
|
|||||||
) => {
|
) => {
|
||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(`/lst/api/mobile/auth/user/${id}`, data, {
|
const res = await axios.patch(`/mobile/auth/user/${id}`, data, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
validateStatus: () => true,
|
validateStatus: () => true,
|
||||||
@@ -123,7 +132,7 @@ const ScanUserTable = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
const { data } = await api.get("/mobile/pin/new");
|
||||||
updateSetting.mutate({
|
updateSetting.mutate({
|
||||||
id: row.original.id,
|
id: row.original.id,
|
||||||
field: "pinNumber",
|
field: "pinNumber",
|
||||||
@@ -171,7 +180,7 @@ const ScanUserTable = () => {
|
|||||||
setActiveToggle(true);
|
setActiveToggle(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.delete(
|
const res = await api.delete(
|
||||||
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||||
import { Suspense, useState } from "react";
|
import { Suspense, useState } from "react";
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { servers } from "../../lib/queries/servers";
|
import { servers } from "../../lib/queries/servers";
|
||||||
import LstTable from "../../lib/tableStuff/LstTable";
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
@@ -111,19 +112,20 @@ const ServerTable = () => {
|
|||||||
const [activeToggle, setActiveToggle] = useState(false);
|
const [activeToggle, setActiveToggle] = useState(false);
|
||||||
|
|
||||||
const onToggle = async () => {
|
const onToggle = async () => {
|
||||||
setActiveToggle(true);
|
|
||||||
toast.success(
|
toast.success(
|
||||||
`${i.row.original.name} just started the upgrade monitor logs for errors.`,
|
`${i.row.original.name} just started the upgrade monitor logs for errors.`,
|
||||||
);
|
);
|
||||||
|
setActiveToggle(activeToggle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await api.post(
|
||||||
`/lst/api/admin/build/updateServer`,
|
`/admin/build/updateServer`,
|
||||||
{
|
{
|
||||||
server: i.row.original.server,
|
server: i.row.original.server,
|
||||||
destination: i.row.original.serverLoc,
|
destination: i.row.original.serverLoc,
|
||||||
token: i.row.original.plantToken,
|
token: i.row.original.plantToken,
|
||||||
},
|
},
|
||||||
{ withCredentials: true },
|
{ withCredentials: true, timeout: 5 * 60 * 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
@@ -218,8 +220,8 @@ function RouteComponent() {
|
|||||||
];
|
];
|
||||||
const triggerBuild = async () => {
|
const triggerBuild = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await api.post(
|
||||||
`/lst/api/admin/build/release`,
|
`/admin/build/release`,
|
||||||
|
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { Suspense, useMemo } from "react";
|
import { Suspense, useMemo } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -24,6 +22,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { getSettings } from "../../lib/queries/getSettings";
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
@@ -48,7 +47,7 @@ const updateSettings = async (
|
|||||||
) => {
|
) => {
|
||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(`/lst/api/settings/${id}`, data, {
|
const res = await api.patch(`/settings/${id}`, data, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
toast.success(`Setting just updated`);
|
toast.success(`Setting just updated`);
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
|
import { KeyRound } from "lucide-react";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { authClient, useSession } from "../../lib/auth-client";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../../components/ui/select";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "../../components/ui/tooltip";
|
||||||
|
import { authClient } from "../../lib/auth-client";
|
||||||
|
import { selectableRoles } from "../../lib/auth-permissions";
|
||||||
import { getUsers } from "../../lib/queries/getUsers";
|
import { getUsers } from "../../lib/queries/getUsers";
|
||||||
|
import { permissionQuery } from "../../lib/queries/permsCheck";
|
||||||
import LstTable from "../../lib/tableStuff/LstTable";
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||||
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
||||||
import { trackLstEvent } from "../../lib/umami.utils";
|
import { trackLstEvent } from "../../lib/umami.utils";
|
||||||
|
import NewUser from "./-components/Newuser";
|
||||||
|
|
||||||
export const Route = createFileRoute("/admin/users")({
|
export const Route = createFileRoute("/admin/users")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
const allowedRole = ["systemAdmin", "admin"];
|
// const allowedRole = ["systemAdmin", "admin"];
|
||||||
|
const canAccess = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
user: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -25,7 +48,8 @@ export const Route = createFileRoute("/admin/users")({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedRole.includes(session.user.role as string)) {
|
//if (!allowedRole.includes(session.user.role as string)) {
|
||||||
|
if (!canAccess) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
});
|
});
|
||||||
@@ -37,8 +61,51 @@ export const Route = createFileRoute("/admin/users")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const UserTable = () => {
|
const UserTable = () => {
|
||||||
const { data } = useSuspenseQuery(getUsers());
|
const { data, refetch } = useSuspenseQuery(getUsers());
|
||||||
const { data: session } = useSession();
|
//const { data: session } = useSession();
|
||||||
|
const { data: canImpersonate = false } = useQuery(
|
||||||
|
permissionQuery({
|
||||||
|
user: ["impersonate"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const { data: canUpdate = false } = useQuery(
|
||||||
|
permissionQuery({
|
||||||
|
user: ["update"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatePassword = useMutation({
|
||||||
|
mutationFn: async ({ user, password }: { user: any; password: string }) => {
|
||||||
|
return authClient.admin.setUserPassword({
|
||||||
|
userId: user.id,
|
||||||
|
newPassword: password,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Password updated");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRoleChange = async (row: any, newRole: string) => {
|
||||||
|
//console.log("update this user", row, newRole);
|
||||||
|
|
||||||
|
const { data, error } = await authClient.admin.updateUser({
|
||||||
|
userId: row.id,
|
||||||
|
data: { role: newRole },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`${data.name}, role was just changed to: ${newRole}`);
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<any>();
|
const columnHelper = createColumnHelper<any>();
|
||||||
|
|
||||||
@@ -50,6 +117,13 @@ const UserTable = () => {
|
|||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: (i) => i.getValue(),
|
||||||
}),
|
}),
|
||||||
|
columnHelper.accessor("username", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Username" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
columnHelper.accessor("email", {
|
columnHelper.accessor("email", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<SearchableHeader column={column} title="Email" searchable={true} />
|
<SearchableHeader column={column} title="Email" searchable={true} />
|
||||||
@@ -57,27 +131,113 @@ const UserTable = () => {
|
|||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: (i) => i.getValue(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// columnHelper.accessor("role", {
|
||||||
|
// header: ({ column }) => (
|
||||||
|
// <SearchableHeader column={column} title="Role" searchable={false} />
|
||||||
|
// ),
|
||||||
|
// filterFn: "includesString",
|
||||||
|
// cell: (i) => i.getValue(),
|
||||||
|
// }),
|
||||||
columnHelper.accessor("role", {
|
columnHelper.accessor("role", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => <SearchableHeader column={column} title="Role" />,
|
||||||
<SearchableHeader column={column} title="Role" searchable={false} />
|
|
||||||
),
|
|
||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: ({ row, getValue }) => {
|
||||||
}),
|
const currentRole = getValue();
|
||||||
columnHelper.accessor("updatedAt", {
|
|
||||||
header: ({ column }) => (
|
return (
|
||||||
<SearchableHeader
|
<Select
|
||||||
column={column}
|
value={currentRole}
|
||||||
title="Updated at"
|
onValueChange={(newRole) => {
|
||||||
searchable={false}
|
handleRoleChange(row.original, newRole);
|
||||||
/>
|
}}
|
||||||
),
|
>
|
||||||
filterFn: "includesString",
|
<SelectTrigger className="w-[180px]">
|
||||||
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
<SelectValue placeholder="Select role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
{selectableRoles.map((role) => (
|
||||||
|
<SelectItem key={role.value} value={role.value}>
|
||||||
|
{role.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (session && session.user.role === "systemAdmin") {
|
if (canUpdate) {
|
||||||
|
columns.push(
|
||||||
|
columnHelper.accessor("changePassword", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Change Password" />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="New password"
|
||||||
|
className="w-[200px]"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key !== "Enter") return;
|
||||||
|
|
||||||
|
const password = e.currentTarget.value.trim();
|
||||||
|
|
||||||
|
if (!password) return;
|
||||||
|
|
||||||
|
updatePassword.mutate({
|
||||||
|
user: row.original,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
e.currentTarget.value = "";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
onClick={(e) => {
|
||||||
|
const input =
|
||||||
|
e.currentTarget.parentElement?.querySelector("input");
|
||||||
|
|
||||||
|
const password = input?.value.trim();
|
||||||
|
|
||||||
|
if (!password) return;
|
||||||
|
|
||||||
|
updatePassword.mutate({
|
||||||
|
user: row.original,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KeyRound className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
Update Password, fill out and press enter or update here
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canImpersonate) {
|
||||||
columns.push(
|
columns.push(
|
||||||
columnHelper.accessor("banned", {
|
columnHelper.accessor("banned", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
@@ -126,7 +286,28 @@ const UserTable = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LstTable data={data} columns={columns} pageSize={50} />;
|
columns.push(
|
||||||
|
columnHelper.accessor("updatedAt", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Updated at"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-end m-2">
|
||||||
|
<NewUser refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
<LstTable data={data} columns={columns} pageSize={50} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { Button } from "../components/ui/button";
|
|
||||||
import { useSession } from "../lib/auth-client";
|
import { useSession } from "../lib/auth-client";
|
||||||
import { runtimeConfig, trackLstEvent } from "../lib/umami.utils";
|
import { trackLstEvent } from "../lib/umami.utils";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
@@ -14,7 +13,7 @@ export const Route = createFileRoute("/")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
const { data: session, isPending } = useSession();
|
const { isPending } = useSession();
|
||||||
|
|
||||||
if (isPending)
|
if (isPending)
|
||||||
return <div className="flex justify-center mt-10">Loading...</div>;
|
return <div className="flex justify-center mt-10">Loading...</div>;
|
||||||
@@ -38,15 +37,15 @@ function Index() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkConfig = () => {
|
// const checkConfig = () => {
|
||||||
console.log(runtimeConfig);
|
// console.log(runtimeConfig);
|
||||||
trackLstEvent("config_click", {
|
// trackLstEvent("config_click", {
|
||||||
module: "app",
|
// module: "app",
|
||||||
action: "click",
|
// action: "click",
|
||||||
label: "configCheck",
|
// label: "configCheck",
|
||||||
page: window.location.pathname,
|
// page: window.location.pathname,
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center m-10 flex-col">
|
<div className="flex justify-center m-10 flex-col">
|
||||||
@@ -77,9 +76,9 @@ function Index() {
|
|||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{session && session.user.role === "systemAdmin" && (
|
{/* {session && session.user.role === "systemAdmin" && (
|
||||||
<Button onClick={checkConfig}>Check config</Button>
|
<Button onClick={checkConfig}>Check config</Button>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../../../components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../../../../components/ui/dialog";
|
||||||
|
import { api } from "../../../../lib/apiHelper";
|
||||||
|
import { useAppForm } from "../../../../lib/formSutff";
|
||||||
|
import { getActiveArticle } from "../../../../lib/queries/getActiveArticles";
|
||||||
|
import { getCustomerByAv } from "../../../../lib/queries/getCustomerByAv";
|
||||||
|
|
||||||
|
export default function NewArticleLink({ refetch }: { refetch: any }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedAv, setSelectedAv] = useState<string>("");
|
||||||
|
const { data: articleData } = useSuspenseQuery(getActiveArticle());
|
||||||
|
const {
|
||||||
|
data: customerData,
|
||||||
|
isPending,
|
||||||
|
isLoading,
|
||||||
|
} = useQuery(getCustomerByAv(selectedAv.split(" - ")[0]));
|
||||||
|
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
av: "",
|
||||||
|
description: "",
|
||||||
|
customer: "",
|
||||||
|
customerDescription: "",
|
||||||
|
loadType: "",
|
||||||
|
dock: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
const corrected = {
|
||||||
|
av: parseInt(value.av.split(" - ")[0], 10),
|
||||||
|
description: value.av.split(" - ")[1],
|
||||||
|
customer: value.customer.split(" - ")[0],
|
||||||
|
customerDescription: value.customer.split(" - ")[1],
|
||||||
|
loadType: value.loadType,
|
||||||
|
dock: value.dock,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.post("/opendock/articleCheck", corrected, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
toast.success(`The link for ${value.av} was just created :D`);
|
||||||
|
refetch();
|
||||||
|
form.reset();
|
||||||
|
setSelectedAv("");
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.data.success) {
|
||||||
|
toast.error(
|
||||||
|
"The article customer combo are not allowed to be created twice please select a different customer.",
|
||||||
|
);
|
||||||
|
|
||||||
|
form.setFieldValue("customer", "");
|
||||||
|
console.log(res.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeModel = (e: boolean) => {
|
||||||
|
setOpen(e);
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
form.reset();
|
||||||
|
setSelectedAv("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openForm = () => {
|
||||||
|
setOpen(true);
|
||||||
|
form.reset;
|
||||||
|
setSelectedAv("");
|
||||||
|
};
|
||||||
|
|
||||||
|
let n: any = [];
|
||||||
|
if (articleData) {
|
||||||
|
n = articleData.map((i: any) => ({
|
||||||
|
label: `${i.article} - ${i.Bezeichnung}`,
|
||||||
|
value: `${i.article} - ${i.Bezeichnung}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let c: any = [];
|
||||||
|
if ((selectedAv && !isPending) || !isLoading) {
|
||||||
|
const cusData = customerData ?? [];
|
||||||
|
c = cusData.map((i: any) => ({
|
||||||
|
label: `${i.customer} - ${i.customerDescription}`,
|
||||||
|
value: `${i.customer} - ${i.customerDescription}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get this from lst as well once we get the actual docks in to link to.
|
||||||
|
// this will be live || drop but also the actaul load types so we can have a little more refined times
|
||||||
|
const loadType = [
|
||||||
|
{
|
||||||
|
label: "Live",
|
||||||
|
value: "live",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Drop",
|
||||||
|
value: "drop",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
//TODO: get the docks from lst to help refine and actually link the dock correctly
|
||||||
|
const dock = [
|
||||||
|
{
|
||||||
|
label: "Cermac",
|
||||||
|
value: "cermac",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Gerber",
|
||||||
|
value: "gerber",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Matrix",
|
||||||
|
value: "matrix",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
|
||||||
|
<Button onClick={openForm}>Create Article link</Button>
|
||||||
|
|
||||||
|
<DialogContent showCloseButton={false} className="min-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Article Link.</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Create the fine tuned per article setup, selecting an av will pull
|
||||||
|
in only the sales prices for the av, After filling in the form all{" "}
|
||||||
|
<p className="underline">NEW</p> release created will use this as
|
||||||
|
the new default settings.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="w-fit">
|
||||||
|
<div className="w-fill">
|
||||||
|
<form.AppField
|
||||||
|
name="av"
|
||||||
|
listeners={{
|
||||||
|
onChange: ({ value }) => {
|
||||||
|
setSelectedAv(value);
|
||||||
|
|
||||||
|
if (form.getFieldValue("customer")) {
|
||||||
|
form.setFieldValue("customer", "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Select Article"
|
||||||
|
placeholder="Select av to link"
|
||||||
|
options={n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-fit">
|
||||||
|
<div className="w-fill">
|
||||||
|
<form.AppField name="customer">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Select Customer"
|
||||||
|
placeholder={
|
||||||
|
!selectedAv
|
||||||
|
? "Select AV first"
|
||||||
|
: isLoading
|
||||||
|
? "Loading customers..."
|
||||||
|
: c.length === 0
|
||||||
|
? "No customers to select"
|
||||||
|
: "Select customer"
|
||||||
|
}
|
||||||
|
options={c}
|
||||||
|
disabled={!selectedAv || (isLoading && c.length > 0)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=" flex flex-row w-fit mt-3">
|
||||||
|
<div className="w-fill">
|
||||||
|
<form.AppField name="loadType">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Select Load Type"
|
||||||
|
placeholder={"Select LoadType"}
|
||||||
|
options={loadType}
|
||||||
|
disabled={!selectedAv}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="w-fill">
|
||||||
|
<form.AppField name="dock">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Select Dock"
|
||||||
|
placeholder={"Select dock"}
|
||||||
|
options={dock}
|
||||||
|
disabled={!selectedAv}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2 ">
|
||||||
|
<form.AppForm>
|
||||||
|
<form.SubmitButton>Submit</form.SubmitButton>
|
||||||
|
</form.AppForm>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
frontend/src/routes/transportation/opendock/index.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { authClient } from "../../../lib/auth-client";
|
||||||
|
import { getArticleLinks } from "../../../lib/queries/getArticleLinks";
|
||||||
|
import LstTable from "../../../lib/tableStuff/LstTable";
|
||||||
|
import SearchableHeader from "../../../lib/tableStuff/SearchableHeader";
|
||||||
|
import SkellyTable from "../../../lib/tableStuff/SkellyTable";
|
||||||
|
import NewArticleLink from "./-components/NewArticleLink";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/transportation/opendock/")({
|
||||||
|
beforeLoad: async ({ location }) => {
|
||||||
|
const { data: session } = await authClient.getSession();
|
||||||
|
//const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||||
|
|
||||||
|
const canAccess = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
openDock: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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 };
|
||||||
|
},
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ArticleLinkTable = () => {
|
||||||
|
const { data, refetch } = useSuspenseQuery(getArticleLinks());
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<any>();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor("av", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Article" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("description", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Description"
|
||||||
|
searchable={true}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("customer", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Customer" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => (
|
||||||
|
<span>
|
||||||
|
{i.row.original.customer} - {i.row.original.customerDescription}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("loadType", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Load Type" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("dock", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Dock" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-end m-2">
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div>
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<NewArticleLink refetch={refetch} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LstTable data={data} columns={columns} pageSize={50} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<SkellyTable />}>
|
||||||
|
<ArticleLinkTable />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { imagetools } from "vite-imagetools";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -14,6 +14,7 @@ export default defineConfig({
|
|||||||
autoCodeSplitting: true,
|
autoCodeSplitting: true,
|
||||||
}),
|
}),
|
||||||
react(),
|
react(),
|
||||||
|
imagetools(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -58,19 +58,25 @@ export default function TabsLayout() {
|
|||||||
// },
|
// },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <Tabs.Screen
|
<Tabs.Screen
|
||||||
name="ppoo"
|
name="ppoo"
|
||||||
options={{
|
options={{
|
||||||
title: "PPOO",
|
title: "PPOO",
|
||||||
href: isNormalScanner ? null : "/(tabs)/ppoo",
|
href:
|
||||||
|
isNormalScanner || !hasRole(["admin", "manager"])
|
||||||
|
? null
|
||||||
|
: "/(tabs)/ppoo",
|
||||||
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
||||||
}}
|
}}
|
||||||
/> */}
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="laneCheck"
|
name="laneCheck"
|
||||||
options={{
|
options={{
|
||||||
title: "Lane Check",
|
title: "Lane Check",
|
||||||
href: isNormalScanner ? null : "/(tabs)/laneCheck",
|
href:
|
||||||
|
isNormalScanner || !hasRole(["admin", "manager"])
|
||||||
|
? null
|
||||||
|
: "/(tabs)/laneCheck",
|
||||||
tabBarIcon: ({ color, size }) => <Rows4 size={size} color={color} />,
|
tabBarIcon: ({ color, size }) => <Rows4 size={size} color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const ERROR_KEYWORDS = [
|
|||||||
"unloaded",
|
"unloaded",
|
||||||
"delivered",
|
"delivered",
|
||||||
"blocked",
|
"blocked",
|
||||||
|
"not possible",
|
||||||
];
|
];
|
||||||
|
|
||||||
// function parseErpResponse(buffer: Buffer) {
|
// function parseErpResponse(buffer: Buffer) {
|
||||||
|
|||||||
3
migrations/0053_petite_thunderbird.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX "opendock_apt_release_idx";--> statement-breakpoint
|
||||||
|
DROP INDEX "name";--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "settings_name_unique" ON "settings" USING btree ("name");
|
||||||
14
migrations/0054_talented_nocturne.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
CREATE TYPE "public"."load_type" AS ENUM('drop', 'live');--> statement-breakpoint
|
||||||
|
CREATE TABLE "opendock_article_setup" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"av" integer NOT NULL,
|
||||||
|
"description" text NOT NULL,
|
||||||
|
"customer" text NOT NULL,
|
||||||
|
"load_type" "load_type" DEFAULT 'drop' NOT NULL,
|
||||||
|
"dock" text NOT NULL,
|
||||||
|
"upd_date" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"upd_user" text DEFAULT 'lst-system' NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"add_user" text DEFAULT 'lst-system' NOT NULL,
|
||||||
|
CONSTRAINT "opendock_article_setup_av_unique" UNIQUE("av")
|
||||||
|
);
|
||||||
1
migrations/0055_nosy_amphibian.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "opendock_article_setup" ADD COLUMN "customer_description" text NOT NULL;
|
||||||
12
migrations/0056_shallow_chimera.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE "opendock_dock_setup" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"dock_id" text NOT NULL,
|
||||||
|
"upd_date" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"upd_user" text DEFAULT 'lst-system' NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"add_user" text DEFAULT 'lst-system' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "opendock_article_setup" DROP CONSTRAINT "opendock_article_setup_av_unique";--> statement-breakpoint
|
||||||
|
ALTER TABLE "opendock_article_setup" ADD CONSTRAINT "uq_opendock_article_setup_av_customer" UNIQUE("av","customer");
|
||||||
2352
migrations/meta/0053_snapshot.json
Normal file
2448
migrations/meta/0054_snapshot.json
Normal file
2454
migrations/meta/0055_snapshot.json
Normal file
2515
migrations/meta/0056_snapshot.json
Normal file
@@ -372,6 +372,34 @@
|
|||||||
"when": 1778533475205,
|
"when": 1778533475205,
|
||||||
"tag": "0052_numerous_wasp",
|
"tag": "0052_numerous_wasp",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 53,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779381841381,
|
||||||
|
"tag": "0053_petite_thunderbird",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 54,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779381875298,
|
||||||
|
"tag": "0054_talented_nocturne",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 55,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779399354404,
|
||||||
|
"tag": "0055_nosy_amphibian",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 56,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1779454561527,
|
||||||
|
"tag": "0056_shallow_chimera",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
79
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.0.2-alpha.10",
|
"version": "0.1.0-alpha.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.0.2-alpha.10",
|
"version": "0.1.0-alpha.2",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dotenvx/dotenvx": "^1.57.0",
|
"@dotenvx/dotenvx": "^1.57.0",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.5.5",
|
"better-auth": "^1.5.5",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
@@ -3801,28 +3802,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"readdirp": "^5.0.0"
|
||||||
"braces": "~3.0.2",
|
|
||||||
"glob-parent": "~5.1.2",
|
|
||||||
"is-binary-path": "~2.1.0",
|
|
||||||
"is-glob": "~4.0.1",
|
|
||||||
"normalize-path": "~3.0.0",
|
|
||||||
"readdirp": "~3.6.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.10.0"
|
"node": ">= 20.19.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "~2.3.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
@@ -10734,16 +10725,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"picomatch": "^2.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.10.0"
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/real-require": {
|
"node_modules/real-require": {
|
||||||
@@ -11978,6 +11969,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-node-dev/node_modules/chokidar": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"anymatch": "~3.1.2",
|
||||||
|
"braces": "~3.0.2",
|
||||||
|
"glob-parent": "~5.1.2",
|
||||||
|
"is-binary-path": "~2.1.0",
|
||||||
|
"is-glob": "~4.0.1",
|
||||||
|
"normalize-path": "~3.0.0",
|
||||||
|
"readdirp": "~3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-node-dev/node_modules/readdirp": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picomatch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsconfig": {
|
"node_modules/tsconfig": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.0.2-alpha.10",
|
"version": "0.1.0-alpha.2",
|
||||||
"description": "The tool that supports us in our everyday alplaprod",
|
"description": "The tool that supports us in our everyday alplaprod",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.5.5",
|
"better-auth": "^1.5.5",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
|
|||||||
@@ -180,6 +180,23 @@ function Update-Server {
|
|||||||
Stop-Service -DisplayName $app_name -Force
|
Stop-Service -DisplayName $app_name -Force
|
||||||
Start-Sleep -Seconds 1
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
|
Write-Host "Removing old server"
|
||||||
|
|
||||||
|
# Remove old dist folder before extracting
|
||||||
|
$DistPath = Join-Path $LocalPath "dist"
|
||||||
|
|
||||||
|
if (Test-Path $DistPath) {
|
||||||
|
Write-Host "Removing old dist folder..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
Remove-Item $DistPath -Recurse -Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Failed to remove dist folder: $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "Unzipping the folder..."
|
Write-Host "Unzipping the folder..."
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||