Compare commits
11 Commits
8fc3129f7d
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| d85f08cb19 | |||
| 15c939ebe8 | |||
| 9ff428f5ea | |||
| 2e460c7f8a | |||
| 7c4c5f980a | |||
| 86e1237509 | |||
| 706ab8b448 | |||
| 9440b44f3b | |||
| a2d9a6c127 | |||
| c0a7d4a125 | |||
| 8fcb2c66ed |
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,5 +1,84 @@
|
||||
# All Changes to LST can be found below.
|
||||
|
||||
## [0.1.0-alpha.3](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) (2026-06-10)
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **eom:** migrated eom endpoints from old version validated working ([e909e8d](https://git.tuffraid.net/cowch/lst_v3/commits/e909e8deecb54a3e4c39789609b0aa7435b9e08a))
|
||||
* **mobile:** added auto download of latest ([6d0fb8a](https://git.tuffraid.net/cowch/lst_v3/commits/6d0fb8aee45c8b5c56ccd7d8a010e1dc803408bf))
|
||||
* **mobile:** dock door scanning backend added ([fe0b157](https://git.tuffraid.net/cowch/lst_v3/commits/fe0b1573f3ba6fd220f181088b994588c52af139)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
|
||||
* **opendock:** added delete button in the article tab ([4464cea](https://git.tuffraid.net/cowch/lst_v3/commits/4464cea022ba48744b884b83fef0fc3f3421dea5)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** added in a new article link system ([bb7931d](https://git.tuffraid.net/cowch/lst_v3/commits/bb7931d6c8d0f5ce4065955491e9ee1247b5e92d))
|
||||
* **warehousing:** ppoo monitoring added ([8b07694](https://git.tuffraid.net/cowch/lst_v3/commits/8b076949a7f8e723bc87619f729082d2c1991b2d)), closes [#13](https://git.tuffraid.net/cowch/lst_v3/issues/13)
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **app:** type in the templates... they all looked the same ([2a648f6](https://git.tuffraid.net/cowch/lst_v3/commits/2a648f63064a300b0a2888bae3322b857af6a238))
|
||||
* **dock door scanning:** correction to how the data is posted ([2f49573](https://git.tuffraid.net/cowch/lst_v3/commits/2f495739e653c462eff7f10ff6343d9572f25563))
|
||||
* **dockscanner:** removed console log ([4249e90](https://git.tuffraid.net/cowch/lst_v3/commits/4249e90307ba1a1992753803e1dc3ab7dd7ac95e))
|
||||
* **eom:** removed un needed imports ([05c553e](https://git.tuffraid.net/cowch/lst_v3/commits/05c553e9279c6e8384d61073781bf915733b0ab5))
|
||||
* **logistics:** historical checks for no data errors when feature is activeed ([4f848bb](https://git.tuffraid.net/cowch/lst_v3/commits/4f848bb649f350c9d370daa09c6fc48f7b76e2e2))
|
||||
* **mobile users:** corrected and endpoint that prevented us from change the pin number ([347edb7](https://git.tuffraid.net/cowch/lst_v3/commits/347edb7078fb4ce959975cd968a6f026bacc98bf))
|
||||
* **mobile:** scan log incorrect user ref ([9c0ef1f](https://git.tuffraid.net/cowch/lst_v3/commits/9c0ef1f5dfa13e8f7e1f72d47d0d5842b3da3c87))
|
||||
* **mobile:** temp removed the autodownload as its causing issues ([3ef0f23](https://git.tuffraid.net/cowch/lst_v3/commits/3ef0f230ddbaef4c3d738cc531e7afee25b210dd))
|
||||
* **mobile:** ui over lapping ([db28635](https://git.tuffraid.net/cowch/lst_v3/commits/db28635c8c260d0f378e109755d32201acdb2328)), closes [#25](https://git.tuffraid.net/cowch/lst_v3/issues/25)
|
||||
* **notifications:** missed api converstion in the front end for updating ([5281118](https://git.tuffraid.net/cowch/lst_v3/commits/52811185965cb0fe4c9b42e73c447b67e917a5ca))
|
||||
* **opendock:** changed the header of delete to be properly named ([865f280](https://git.tuffraid.net/cowch/lst_v3/commits/865f280cfaf82a7126ca4b57d658aed50cf19a73)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** correction to article link success on delete ([a717260](https://git.tuffraid.net/cowch/lst_v3/commits/a717260b8d5cf72534b055721de2b79901506875)), closes [#23](https://git.tuffraid.net/cowch/lst_v3/issues/23)
|
||||
* **opendock:** ref wrong field oops ([f635415](https://git.tuffraid.net/cowch/lst_v3/commits/f635415b751e11d0e7beb557d18b83915155428a))
|
||||
* **scanner:** corrected teh endpoint to delete the user if needed ([6dd1a5b](https://git.tuffraid.net/cowch/lst_v3/commits/6dd1a5b332a853f0e2c7226ac1f64e403ef752d7))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **app:** updated last updated to the readme to show current progress ([78f7b8a](https://git.tuffraid.net/cowch/lst_v3/commits/78f7b8a179d078fc1b2740fd2bf4af71a3df8292))
|
||||
* **app:** updated readme ([1a3d8a7](https://git.tuffraid.net/cowch/lst_v3/commits/1a3d8a7ddcb7a2e51da4b77968ffdefb7644a0b1))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **backend:** dock door scanning socket and perms ([f8335f5](https://git.tuffraid.net/cowch/lst_v3/commits/f8335f5217c339a7dc09883fa8ba9c60aa4c35b1))
|
||||
* **datamart:** if added to query will include plant token now ([7d722c4](https://git.tuffraid.net/cowch/lst_v3/commits/7d722c4aac16bc1d2cfbfbc70539783f34003f77))
|
||||
* **db:** added in notifications vs pulling from the db makes it easier on the system ([706ab8b](https://git.tuffraid.net/cowch/lst_v3/commits/706ab8b448aafb81da94a76fbf8d3d400cba616b))
|
||||
* **db:** added timezone check in so it comes over correct based on the backend timezone ([2558b2e](https://git.tuffraid.net/cowch/lst_v3/commits/2558b2e5bb68be7a3f46de09bb509c99423adeb6))
|
||||
* **dock door scanning:** fixes and final writes for the intial trial went smooth ([86e1237](https://git.tuffraid.net/cowch/lst_v3/commits/86e1237509b81722dee7b42762d0bfced8d26fa3))
|
||||
* **dock scanner:** more work on dock scanner and semi finished ([6eaae0f](https://git.tuffraid.net/cowch/lst_v3/commits/6eaae0f5378e39c4002dadd8325833698dd960e7))
|
||||
* **dockscanning:** more work on the dock door scanning ([7671172](https://git.tuffraid.net/cowch/lst_v3/commits/7671172d975355b5d245a482b481726b49153578)), closes [#12](https://git.tuffraid.net/cowch/lst_v3/issues/12)
|
||||
* **logger:** included error in the stack version so we dont have to remove it all ([da87e2e](https://git.tuffraid.net/cowch/lst_v3/commits/da87e2e1d322a45ca9a7b500d77499fe4c7b999e))
|
||||
* **logs:** refactored to show the submodule and stack as well to make it more easy to watch ([45a0dee](https://git.tuffraid.net/cowch/lst_v3/commits/45a0dee9caae14df639439b905fa81b340791197))
|
||||
* **logs:** socket io setup to be properly logging ([9ff428f](https://git.tuffraid.net/cowch/lst_v3/commits/9ff428f5ea1051e62521632947092f74eb93944d))
|
||||
* **mobile:** intial addin of dockdoor scanning on mobile ([2a35381](https://git.tuffraid.net/cowch/lst_v3/commits/2a35381fe400fc46e6f10c4e72c3ab9ac435e0e5))
|
||||
* **mobile:** moved logout to the tab bar ([bcdf956](https://git.tuffraid.net/cowch/lst_v3/commits/bcdf9566bcf721a085f46ce3befe783eb7b91949))
|
||||
* **mobile:** new error found and added in ([7bbdd4e](https://git.tuffraid.net/cowch/lst_v3/commits/7bbdd4e5552889434aff81ed10ea7c64174f7d34))
|
||||
* **mobile:** setup - added button to go home as it caused confustion ([c15ee07](https://git.tuffraid.net/cowch/lst_v3/commits/c15ee070e7057ad8a5e3d42f51c230d680db9e21))
|
||||
* **new role:** added in warehouse role ([55418e2](https://git.tuffraid.net/cowch/lst_v3/commits/55418e2f098193b0891129a19532608dce2abd9c))
|
||||
* **opend dock:** added in default dock so it uses the default setup as a backup ([8fc3129](https://git.tuffraid.net/cowch/lst_v3/commits/8fc3129f7d687d45dc242aca9a6e71f43d0352ab))
|
||||
* **opendock:** added in check for really using the article link ([cfc497c](https://git.tuffraid.net/cowch/lst_v3/commits/cfc497c1f2254dc12a6e7bb15cfc0dce2a64c1e6))
|
||||
* **opendock:** added in proper complete and ignore of picksheets ([3ad84da](https://git.tuffraid.net/cowch/lst_v3/commits/3ad84dab71ceea081efc824e97d075c6f33807ad))
|
||||
* **opendock:** added some new goodies to the app to help manage releases ([c0a7d4a](https://git.tuffraid.net/cowch/lst_v3/commits/c0a7d4a1252e3f14240d14288a3ac4add44a6463))
|
||||
* **opendock:** changed the article to look at the label to match all plants ([a2d9a6c](https://git.tuffraid.net/cowch/lst_v3/commits/a2d9a6c127cae3b6dc5beb1a996558ab1383fa8c))
|
||||
* **opendock:** changed the subModule for better logging ([7c4c5f9](https://git.tuffraid.net/cowch/lst_v3/commits/7c4c5f980a15e13e2a43a8583a652622370ec1dd))
|
||||
* **opendock:** if load type is drop we will not do anything unless its already scanned ([5a7046b](https://git.tuffraid.net/cowch/lst_v3/commits/5a7046bacd9323627a37c89d7a9a329383e8b1be))
|
||||
* **server builds:** added in proper logging will still need fine tuned though ([15c939e](https://git.tuffraid.net/cowch/lst_v3/commits/15c939ebe8b808b3a9a46a4c1e62fb0488d3649b))
|
||||
* **servers:** all remaining servers added ([5f2148f](https://git.tuffraid.net/cowch/lst_v3/commits/5f2148f5f0f7688cf71e1dad99d4a5f6c34241dd))
|
||||
* **socket.io:** complete rewrite to manage dynamic rooms and seeding better ([9440b44](https://git.tuffraid.net/cowch/lst_v3/commits/9440b44f3bb1842d0e6ffc479d340ae9c5b84656))
|
||||
* **socketio:** changes that if we are in dock room to constant reseed the room ([8fcb2c6](https://git.tuffraid.net/cowch/lst_v3/commits/8fcb2c66ed25c746dc1d63c5290e11fe6a56328e))
|
||||
* **times:** added a better view for times as we save all db as there respective timezone ([40edbc3](https://git.tuffraid.net/cowch/lst_v3/commits/40edbc3eae9000afea447f81ec127884712b38da))
|
||||
* **warehouse:** fixed ppoo check to match the new changes ([2e460c7](https://git.tuffraid.net/cowch/lst_v3/commits/2e460c7f8aeb7de1d641c42357001704a4d8d8b2))
|
||||
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **test:** added in vitest to start doing more testing before deploying ([e6b92ae](https://git.tuffraid.net/cowch/lst_v3/commits/e6b92aeb109394691dd2a7d548ddadd02769a159))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **app:** added more labels to the templates ([69a9a81](https://git.tuffraid.net/cowch/lst_v3/commits/69a9a81a889aca010ec8835ffab7084f7cd4a9b0))
|
||||
* **app:** changed the issues templates to be easier to add to the part of the app needed ([188fdd0](https://git.tuffraid.net/cowch/lst_v3/commits/188fdd0eb7c6bedf52ae1dcc9109a0fdbe969a21))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as dockScans from "./schema/dockdoor.scans.schema.js";
|
||||
import * as logs from "./schema/logs.schema.js";
|
||||
import * as opendockAVCheck from "./schema/opendock_articleSetup.js";
|
||||
import * as scanUserSchema from "./schema/scanUsers.js";
|
||||
import * as settingsSchema from "./schema/settings.schema.js";
|
||||
@@ -23,5 +25,7 @@ export const db = drizzle(queryClient, {
|
||||
...scanUserSchema,
|
||||
...settingsSchema,
|
||||
...opendockAVCheck,
|
||||
...logs,
|
||||
...dockScans,
|
||||
},
|
||||
});
|
||||
|
||||
59
backend/db/db.listener.ts
Normal file
59
backend/db/db.listener.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import postgres from "postgres";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { handleDbNotification } from "./db.router.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications",
|
||||
});
|
||||
|
||||
const CHANNELS = [
|
||||
"logs_inserted",
|
||||
// "labels_inserted",
|
||||
// "dock_scans_inserted",
|
||||
] as const;
|
||||
|
||||
type DbNotificationChannel = (typeof CHANNELS)[number];
|
||||
|
||||
type DbNotificationPayload = {
|
||||
table: string;
|
||||
action: "INSERT" | "UPDATE" | "DELETE";
|
||||
id: string;
|
||||
};
|
||||
|
||||
export async function startDbNotificationListener() {
|
||||
const sql = postgres({
|
||||
host: `${process.env.DATABASE_HOST}`,
|
||||
port: Number(process.env.DATABASE_PORT),
|
||||
database: `${process.env.DATABASE_DB}`,
|
||||
username: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
});
|
||||
|
||||
for (const channel of CHANNELS) {
|
||||
await sql.listen(channel, async (rawPayload) => {
|
||||
await processNotification(channel, rawPayload);
|
||||
});
|
||||
|
||||
log.info({ stack: { channel } }, `Listening for ${channel}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function processNotification(
|
||||
channel: DbNotificationChannel,
|
||||
rawPayload: string,
|
||||
) {
|
||||
try {
|
||||
const payload = JSON.parse(rawPayload) as DbNotificationPayload;
|
||||
|
||||
await handleDbNotification({
|
||||
channel,
|
||||
payload,
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(
|
||||
{ stack: { channel, rawPayload, e }, notify: true },
|
||||
"Failed processing DB notification",
|
||||
);
|
||||
}
|
||||
}
|
||||
41
backend/db/db.router.ts
Normal file
41
backend/db/db.router.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { handleDockScanInsertedNotification } from "../dockdoorScanning/dockdoor.socket.notifications.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { handleLogInsertedNotification } from "../logger/logger.socket.notifications.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications-router",
|
||||
});
|
||||
|
||||
type DbNotification = {
|
||||
channel: string;
|
||||
payload: {
|
||||
table: string;
|
||||
action: "INSERT" | "UPDATE" | "DELETE";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function handleDbNotification(notification: DbNotification) {
|
||||
const { channel, payload } = notification;
|
||||
|
||||
switch (channel) {
|
||||
case "logs_inserted":
|
||||
await handleLogInsertedNotification(payload.id);
|
||||
return;
|
||||
|
||||
// case "labels_inserted":
|
||||
// await handleLabelInsertedNotification(payload.id);
|
||||
// return;
|
||||
|
||||
case "dock_scan_inserted":
|
||||
await handleDockScanInsertedNotification(payload.id);
|
||||
return;
|
||||
|
||||
default:
|
||||
log.warn(
|
||||
{ stack: notification },
|
||||
`Unhandled DB notification channel: ${channel}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
99
backend/db/db.setupNotifications.ts
Normal file
99
backend/db/db.setupNotifications.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
const log = createLogger({
|
||||
module: "db",
|
||||
subModule: "notifications",
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates/updates Postgres notification functions + triggers.
|
||||
*
|
||||
* Safe to run on every app startup.
|
||||
* CREATE OR REPLACE updates the function.
|
||||
* DROP TRIGGER IF EXISTS prevents duplicate triggers.
|
||||
*/
|
||||
export async function setupDbNotifications() {
|
||||
log.info({}, "Setting up DB notifications");
|
||||
|
||||
await setupLogsNotifications();
|
||||
await setupDockScansNotifications();
|
||||
|
||||
log.info({}, "DB notifications setup complete");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs notification setup.
|
||||
*
|
||||
* Flow:
|
||||
* 1. app inserts into logs table
|
||||
* 2. trigger runs after insert
|
||||
* 3. Postgres sends NOTIFY logs_inserted with the new log id
|
||||
* 4. Node listener receives id and fetches/emits full row
|
||||
*/
|
||||
async function setupLogsNotifications() {
|
||||
await db.execute(sql`
|
||||
CREATE OR REPLACE FUNCTION notify_logs_inserted()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify(
|
||||
'logs_inserted',
|
||||
json_build_object(
|
||||
'table', TG_TABLE_NAME,
|
||||
'action', TG_OP,
|
||||
'id', NEW.id
|
||||
)::text
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
DROP TRIGGER IF EXISTS logs_inserted_notify_trigger ON logs;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TRIGGER logs_inserted_notify_trigger
|
||||
AFTER INSERT ON logs
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_logs_inserted();
|
||||
`);
|
||||
|
||||
log.info({}, "Logs DB notification trigger ready");
|
||||
}
|
||||
|
||||
async function setupDockScansNotifications() {
|
||||
await db.execute(sql`
|
||||
CREATE OR REPLACE FUNCTION notify_dock_scan_inserted()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify(
|
||||
'dock_scan_inserted',
|
||||
json_build_object(
|
||||
'table', TG_TABLE_NAME,
|
||||
'action', TG_OP,
|
||||
'id', NEW.id
|
||||
)::text
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
DROP TRIGGER IF EXISTS dock_scan_inserted_notify_trigger ON dock_door_scans;
|
||||
`);
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TRIGGER dock_scan_inserted_notify_trigger
|
||||
AFTER INSERT ON dock_door_scans
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_dock_scan_inserted();
|
||||
`);
|
||||
|
||||
log.info({}, "Dock scan DB notification trigger ready");
|
||||
}
|
||||
21
backend/db/db.socketSeed.ts
Normal file
21
backend/db/db.socketSeed.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { db } from "./db.controller.js";
|
||||
|
||||
export const getRecentLogs = ({
|
||||
module,
|
||||
submodule,
|
||||
limit = 200,
|
||||
}: {
|
||||
module?: string | undefined;
|
||||
submodule?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
}) => {
|
||||
return db.query.logs.findMany({
|
||||
where: (logs, { and, eq }) =>
|
||||
and(
|
||||
module ? eq(logs.module, module) : undefined,
|
||||
submodule ? eq(logs.subModule, submodule) : undefined,
|
||||
),
|
||||
orderBy: (logs, { desc }) => [desc(logs.createdAt)],
|
||||
limit,
|
||||
});
|
||||
};
|
||||
@@ -16,6 +16,7 @@ export const opendockApt = pgTable(
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
release: integer("release").notNull().unique("opendock_apt_release_unique"),
|
||||
openDockAptId: text("open_dock_apt_id").notNull(),
|
||||
status: text("status").default("active"),
|
||||
appointment: jsonb("appointment").notNull().default([]),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true })
|
||||
.notNull()
|
||||
|
||||
@@ -16,7 +16,58 @@ const endLoading = z.object({
|
||||
});
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
// TODO: setup the emitter to just emit the data when we post to the db
|
||||
if (req.body.clear) {
|
||||
// just clear the loading order and clear out all the pallets to keep it clean.
|
||||
await tryCatch(
|
||||
db
|
||||
.update(dockDoorScans)
|
||||
.set({
|
||||
status: "completed",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(
|
||||
req.body.loadingOrder
|
||||
? eq(dockDoorScanners.currentLoadingOrder, req.body.loadingOrder)
|
||||
: undefined,
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: "",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, req.body.dockId))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to updating the dock.`,
|
||||
data: (error as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Loading order: ${req.body.loadingOrder} was just cleared out do to the process being completed in some other means. \nThis includes any scanned pallets as well.`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const validated = endLoading.parse(req.body);
|
||||
@@ -73,7 +124,9 @@ r.post("/", async (req, res) => {
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username ?? "lst-dock-system",
|
||||
})
|
||||
.where(eq(dockDoorScanners.currentLoadingOrder, validated.loadingOrder))
|
||||
.where(
|
||||
eq(dockDoorScans.loadingOrder, validated.loadingOrder.toString()),
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
|
||||
17
backend/dockdoorScanning/dockdoor.socket.notifications.ts
Normal file
17
backend/dockdoorScanning/dockdoor.socket.notifications.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
|
||||
export async function handleDockScanInsertedNotification(id: string) {
|
||||
const row = await db.query.dockDoorScans.findFirst({
|
||||
where: eq(logs.id, id),
|
||||
});
|
||||
|
||||
if (!row) return;
|
||||
|
||||
// send only to the current dock door
|
||||
if (row.dockId) {
|
||||
emitToRoom(`dockDoorLoading:${row.dockId}`, row);
|
||||
}
|
||||
}
|
||||
20
backend/dockdoorScanning/dockdoor.socket.seed.ts
Normal file
20
backend/dockdoorScanning/dockdoor.socket.seed.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { db } from "../db/db.controller.js";
|
||||
|
||||
export const getRecentDockScans = ({
|
||||
loadingOrder,
|
||||
limit = 200,
|
||||
}: {
|
||||
loadingOrder: string;
|
||||
limit?: number | undefined;
|
||||
}) => {
|
||||
return db.query.dockDoorScans.findMany({
|
||||
//where: (scans, { eq }) => eq(scans.status, "active"),
|
||||
where: (scans, { and, eq }) =>
|
||||
and(
|
||||
eq(scans.status, "active"),
|
||||
loadingOrder ? eq(scans.loadingOrder, loadingOrder) : undefined,
|
||||
),
|
||||
orderBy: (scans, { desc }) => [desc(scans.upd_date)],
|
||||
limit,
|
||||
});
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { Writable } from "node:stream";
|
||||
import pino, { type Logger } from "pino";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { notifySystemIssue } from "./logger.notify.js";
|
||||
//import build from "pino-abstract-transport";
|
||||
@@ -50,10 +49,10 @@ const dbStream = new Writable({
|
||||
notifySystemIssue(obj);
|
||||
}
|
||||
|
||||
if (obj.room) {
|
||||
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
}
|
||||
emitToRoom("logs", res.data ? res.data[0] : obj);
|
||||
// if (obj.room) {
|
||||
// emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
// }
|
||||
// emitToRoom("logs", res.data ? res.data[0] : obj);
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error("DB log insert error:", err);
|
||||
|
||||
24
backend/logger/logger.socket.notifications.ts
Normal file
24
backend/logger/logger.socket.notifications.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
|
||||
export async function handleLogInsertedNotification(id: string) {
|
||||
const row = await db.query.logs.findFirst({
|
||||
where: eq(logs.id, id),
|
||||
});
|
||||
|
||||
if (!row) return;
|
||||
|
||||
// More targeted rooms.
|
||||
if (row.module) {
|
||||
emitToRoom(`logs:${row.module}`, row);
|
||||
}
|
||||
|
||||
if (row.subModule) {
|
||||
emitToRoom(`logs:${row.subModule}`, row);
|
||||
}
|
||||
|
||||
// Everyone listening to all logs.
|
||||
emitToRoom("logs", row);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ type Releases = {
|
||||
ReleaseNumber: number;
|
||||
DeliveryState: number;
|
||||
DeliveryDate: Date;
|
||||
ReleaseState: number;
|
||||
LineItemHumanReadableId: number;
|
||||
ArticleAlias: string;
|
||||
LoadingUnits: string;
|
||||
@@ -38,6 +39,8 @@ const actaulDocks = [
|
||||
{ name: "matrix", dockId: "3e32cbfc-49f4-4138-b491-9d5df9c94754" },
|
||||
{ name: "gerber", dockId: "9109e789-6c15-4cd9-87cb-de1b18627b6d" },
|
||||
{ name: "rb", dockId: "6be02526-6183-4789-a73f-e0aa155e6d1e" },
|
||||
//test server dock
|
||||
{ name: "second", dockId: "e87c92bd-13b4-4f7e-bf5e-b0182884c47a" },
|
||||
];
|
||||
const timeZone = process.env.TIMEZONE as string;
|
||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||
@@ -273,11 +276,65 @@ const postRelease = async (release: Releases) => {
|
||||
if (existing) {
|
||||
const id = existing.openDockAptId;
|
||||
|
||||
// deal with canceled stuff as we want this gone off od
|
||||
if (release.ReleaseState === 2 || release.ReleaseState === 4) {
|
||||
// delete the order in od and change the state to canceled in lst
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
Authorization: `Bearer ${odToken.odToken}`,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// hardDelete: true,
|
||||
// },
|
||||
);
|
||||
|
||||
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
|
||||
.update(opendockApt)
|
||||
.set({
|
||||
status: "canceled",
|
||||
upd_date: sql`Now()`,
|
||||
})
|
||||
.where(eq(opendockApt.release, release.ReleaseNumber))
|
||||
.returning();
|
||||
|
||||
log.info({}, `${release.ReleaseNumber} was canceled`);
|
||||
} catch (e) {
|
||||
log.error(
|
||||
{ stack: e },
|
||||
`Error canceling 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 canceling of the release: ${release.ReleaseNumber}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(releaseLoadtypeCheck ||
|
||||
opendDockArticleCheck?.loadType === "drop" ||
|
||||
defaultDock?.value === "drop") &&
|
||||
(release.DeliveryState === 0 || release.DeliveryState === 2)
|
||||
release.DeliveryState === 2
|
||||
) {
|
||||
const setArrival = { ...newDockApt, status: "Arrived" };
|
||||
|
||||
@@ -392,7 +449,11 @@ const postRelease = async (release: Releases) => {
|
||||
return;
|
||||
}
|
||||
// changing to only trigger the change if the state is 2 meaning it has a scan to it and already in progress of being loaded.
|
||||
} else if (release.DeliveryState === 0 || release.DeliveryState === 2) {
|
||||
} else if (
|
||||
release.DeliveryState === 0 ||
|
||||
release.DeliveryState === 1 ||
|
||||
release.DeliveryState === 2
|
||||
) {
|
||||
try {
|
||||
const response = await axios.patch(
|
||||
`${process.env.OPENDOCK_URL}/appointment/${id}`,
|
||||
@@ -479,6 +540,7 @@ const postRelease = async (release: Releases) => {
|
||||
set: {
|
||||
openDockAptId: response.data.data.id,
|
||||
appointment: response.data.data,
|
||||
status: "completed",
|
||||
upd_date: sql`NOW()`,
|
||||
},
|
||||
})
|
||||
|
||||
73
backend/opendock/openDockUndoLastStatus.ts
Normal file
73
backend/opendock/openDockUndoLastStatus.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import axios from "axios";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { odToken } from "./opendock.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.patch("/:id", async (req, res) => {
|
||||
//const limit
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const response = await axios.patch(
|
||||
`${process.env.OPENDOCK_URL}/appointment/${id}/undo-latest-status`,
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
Authorization: `Bearer ${odToken.odToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status === 400) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: response.data.data.message,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// update the release in the db
|
||||
const { data } = await tryCatch(
|
||||
db
|
||||
.update(opendockApt)
|
||||
.set({
|
||||
appointment: response.data.data,
|
||||
upd_date: sql`NOW()`,
|
||||
})
|
||||
.where(eq(opendockApt.openDockAptId, id)),
|
||||
);
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `The release was reverted to the last state.`,
|
||||
data: data as any,
|
||||
status: 200,
|
||||
});
|
||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
} catch (e: any) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `An error updating the release in opendock`,
|
||||
data: e.response.data,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Express } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import undo from "./openDockUndoLastStatus.js";
|
||||
import articleCheck from "./opendock.articleCheck.route.js";
|
||||
|
||||
import getApt from "./opendockGetRelease.route.js";
|
||||
import getApt from "./opendockRelease.route.js";
|
||||
|
||||
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
@@ -21,4 +21,11 @@ export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||
requireAuth,
|
||||
articleCheck,
|
||||
);
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/opendock/undo-latest-status`,
|
||||
featureCheck("opendock_sync"),
|
||||
requireAuth,
|
||||
undo,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export let odToken: ODToken = {
|
||||
};
|
||||
|
||||
export const getToken = async () => {
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
const log = createLogger({ module: "opendock", subModule: "auth" });
|
||||
try {
|
||||
const { status, data } = await axios.post(
|
||||
`${process.env.OPENDOCK_URL}/auth/login`,
|
||||
@@ -29,7 +29,8 @@ export const getToken = async () => {
|
||||
|
||||
odToken = { odToken: data.access_token, tokenDate: new Date() };
|
||||
log.info({ odToken }, "Token added");
|
||||
return;
|
||||
} catch (e) {
|
||||
log.error({ error: e }, "Error getting/refreshing token");
|
||||
log.error({ stack: e }, "Error getting/refreshing token");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { desc, gte, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (_, res) => {
|
||||
//const limit
|
||||
|
||||
const daysCreated = 30;
|
||||
|
||||
const { data } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(opendockApt)
|
||||
.where(
|
||||
gte(
|
||||
opendockApt.createdAt,
|
||||
sql.raw(`NOW() - INTERVAL '${daysCreated} days'`),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(opendockApt.createdAt))
|
||||
.limit(500),
|
||||
);
|
||||
|
||||
apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `The first ${data?.length} Apt(s) that were created in the last ${daysCreated} `,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
124
backend/opendock/opendockRelease.route.ts
Normal file
124
backend/opendock/opendockRelease.route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import axios from "axios";
|
||||
import { and, desc, eq, gte, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { opendockApt } from "../db/schema/opendock_apt.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { odToken } from "./opendock.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res) => {
|
||||
//const limit
|
||||
|
||||
const daysCreated = req.query.daysCreated ?? 30;
|
||||
|
||||
const { data } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(opendockApt)
|
||||
.where(
|
||||
and(
|
||||
gte(
|
||||
opendockApt.upd_date,
|
||||
sql.raw(`NOW() - INTERVAL '${daysCreated} days'`),
|
||||
),
|
||||
eq(opendockApt.status, "active"),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(opendockApt.upd_date))
|
||||
.limit(500),
|
||||
);
|
||||
|
||||
apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `The first ${data?.length} Apt(s) that were created in the last ${daysCreated} `,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
r.delete("/:id", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const { data: releaseInfo } = (await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(opendockApt)
|
||||
.where(eq(opendockApt.id, id as string)),
|
||||
)) as any;
|
||||
|
||||
if (releaseInfo.length === 0) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: "Invalid release id passed over please try again",
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
`${process.env.OPENDOCK_URL}/appointment/${releaseInfo[0].appointment.id}`,
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
Authorization: `Bearer ${odToken.odToken}`,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// hardDelete: true,
|
||||
// },
|
||||
);
|
||||
|
||||
if (response.status === 400) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: response.data.data.message,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// update the release in the db leaving as insert just in-case something weird happened
|
||||
const { data } = await tryCatch(
|
||||
db
|
||||
.delete(opendockApt)
|
||||
.where(eq(opendockApt.id, id as string))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `The release was deleted, this is un unrecoverable`,
|
||||
data: data as any,
|
||||
status: 200,
|
||||
});
|
||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
} catch (e: any) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `An error deleting the release in opendock`,
|
||||
data: e.response.data,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -2,6 +2,8 @@ import { createServer } from "node:http";
|
||||
import os from "node:os";
|
||||
import createApp from "./app.js";
|
||||
import { db } from "./db/db.controller.js";
|
||||
import { startDbNotificationListener } from "./db/db.listener.js";
|
||||
import { setupDbNotifications } from "./db/db.setupNotifications.js";
|
||||
import { dbCleanup } from "./db/dbCleanup.controller.js";
|
||||
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
||||
import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
|
||||
@@ -43,12 +45,13 @@ const start = async () => {
|
||||
startTCPServer();
|
||||
connectProdSql();
|
||||
connectGPSql();
|
||||
startDbNotificationListener();
|
||||
|
||||
// trigger startup processes these must run before anything else can run
|
||||
await baseSettingValidationCheck();
|
||||
systemSettings = await db.select().from(settings);
|
||||
|
||||
//when starting up long lived features the name must match the setting name.
|
||||
// when starting up long lived features the name must match the setting name.
|
||||
// also we always want to have long lived processes inside a setting check.
|
||||
setTimeout(() => {
|
||||
if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
|
||||
@@ -89,6 +92,7 @@ const start = async () => {
|
||||
startNotifications();
|
||||
serversChecks();
|
||||
aggregateRouteHitsForBusinessDay();
|
||||
setupDbNotifications();
|
||||
|
||||
// can be removed at a later date
|
||||
sqlJobCleanUp();
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { RoomId } from "./roomDefinitions.socket.js";
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
||||
|
||||
export const roomHistory = new Map<RoomId, unknown[]>();
|
||||
export const roomBuffers = new Map<RoomId, any[]>();
|
||||
export const roomFlushTimers = new Map<RoomId, NodeJS.Timeout>();
|
||||
@@ -1,123 +0,0 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScans } from "../db/schema/dockdoor.scans.schema.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { ppoRun } from "../warehousing/warehousing.ppooMonitor.js";
|
||||
|
||||
type RoomDefinition<T = unknown> = {
|
||||
seed: (limit: number) => Promise<T[]>;
|
||||
};
|
||||
|
||||
export type StaticRoomId =
|
||||
| "logs"
|
||||
| "labels"
|
||||
| "admin"
|
||||
| "admin:build"
|
||||
| "ppoo"
|
||||
| "dockDoorLoading:2";
|
||||
export type DynamicRoomId = `dockDoorLoading:${string}`;
|
||||
export type RoomId = StaticRoomId | DynamicRoomId;
|
||||
|
||||
export type RoomConfig = {
|
||||
requiresAuth?: boolean;
|
||||
role?: string[];
|
||||
seed?: (limit: number, roomId: RoomId) => Promise<unknown[]>;
|
||||
};
|
||||
|
||||
export const protectedRooms: Record<StaticRoomId, RoomConfig> = {
|
||||
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
|
||||
labels: {},
|
||||
admin: {},
|
||||
"admin:build": {},
|
||||
ppoo: {},
|
||||
"dockDoorLoading:2": {},
|
||||
};
|
||||
|
||||
export function getRoomConfig(roomId: string): RoomConfig | null {
|
||||
if (roomId in protectedRooms) {
|
||||
return protectedRooms[roomId as StaticRoomId];
|
||||
}
|
||||
|
||||
if (roomId.startsWith("dockDoorLoading:")) {
|
||||
const dockId = roomId.split(":")[1];
|
||||
|
||||
if (!dockId) return null;
|
||||
|
||||
return {
|
||||
requiresAuth: true,
|
||||
role: ["admin", "systemAdmin", "dockDoor"],
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
logs: {
|
||||
seed: async (limit) => {
|
||||
try {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(logs)
|
||||
.orderBy(desc(logs.createdAt))
|
||||
.limit(limit);
|
||||
|
||||
return rows; //.reverse();
|
||||
} catch (e) {
|
||||
console.error("Failed to seed logs:", e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
labels: {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
"admin:build": {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
ppoo: {
|
||||
seed: async (limit) => {
|
||||
console.log(limit);
|
||||
return {
|
||||
type: "snapshot",
|
||||
items: await ppoRun(),
|
||||
createdAt: new Date().toISOString(),
|
||||
} as any;
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: add in dynamic room seeding
|
||||
"dockDoorLoading:2": {
|
||||
seed: async (limit) => {
|
||||
console.log(limit);
|
||||
console.log("reseeiding");
|
||||
try {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(dockDoorScans)
|
||||
.where(eq(dockDoorScans.status, "active"))
|
||||
.orderBy(desc(dockDoorScans.upd_date))
|
||||
.limit(limit);
|
||||
|
||||
return rows; //.reverse();
|
||||
} catch (e) {
|
||||
console.error("Failed to seed logs:", e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,27 +1,73 @@
|
||||
// the emitter setup
|
||||
// TODO: validate if we want to add event back in later..
|
||||
// let emitFn: ((roomId: string, event: string, payload: unknown) => void) | null =
|
||||
// null;
|
||||
|
||||
import type { RoomId } from "./roomDefinitions.socket.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;
|
||||
type QueuedPayload = unknown;
|
||||
|
||||
let emitFn: ((roomId: string, payload: QueuedPayload[]) => void) | null = null;
|
||||
|
||||
const queues = new Map<string, QueuedPayload[]>();
|
||||
const timers = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
const FLUSH_MS = 500;
|
||||
const MAX_QUEUE_SIZE = 200;
|
||||
|
||||
export const registerEmitter = (
|
||||
fn: (roomId: RoomId, payload: unknown[]) => void,
|
||||
fn: (roomId: string, payload: QueuedPayload) => void,
|
||||
) => {
|
||||
addDataToRoom = fn;
|
||||
emitFn = fn;
|
||||
};
|
||||
|
||||
export const emitToRoom = (roomId: RoomId, payload: unknown[]) => {
|
||||
if (!addDataToRoom) {
|
||||
export const emitToRoom = (roomId: string, payload: QueuedPayload) => {
|
||||
const log = createLogger({ module: "socket.io", subModule: "emitter" });
|
||||
if (!emitFn) {
|
||||
console.error("Socket emitter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
addDataToRoom(roomId, payload);
|
||||
const queue = queues.get(roomId) ?? [];
|
||||
|
||||
if (queue.length > MAX_QUEUE_SIZE) {
|
||||
log.error(
|
||||
{ stack: { roomId, size: queue.length }, notify: true },
|
||||
`Socket queue exceeded max size for ${roomId}`,
|
||||
);
|
||||
}
|
||||
queue.push(payload);
|
||||
queues.set(roomId, queue);
|
||||
|
||||
if (timers.has(roomId)) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
const payloads = queues.get(roomId) ?? [];
|
||||
|
||||
if (payloads.length === 0) return;
|
||||
emitFn?.(roomId, payloads);
|
||||
|
||||
queues.delete(roomId);
|
||||
} catch (e) {
|
||||
console.error("Socket emit failed", { roomId, e });
|
||||
} finally {
|
||||
timers.delete(roomId);
|
||||
}
|
||||
}, FLUSH_MS);
|
||||
|
||||
timers.set(roomId, timer);
|
||||
};
|
||||
|
||||
/*
|
||||
import { emitToRoom } from "../socket/socketEmitter.js";
|
||||
// room name
|
||||
// its payload
|
||||
emitToRoom("logs", newLogRow);
|
||||
example emitToRoom(room, payload)
|
||||
|
||||
payload can be anything json serilized example below.
|
||||
|
||||
emitToRoom("inventory:ppoo", {
|
||||
type: "snapshot",
|
||||
location: "ppoo",
|
||||
items,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { Server } from "socket.io";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import {
|
||||
FLUSH_INTERVAL,
|
||||
MAX_HISTORY,
|
||||
roomBuffers,
|
||||
roomFlushTimers,
|
||||
roomHistory,
|
||||
} from "./roomCache.socket.js";
|
||||
import { type RoomId, roomDefinition } from "./roomDefinitions.socket.js";
|
||||
|
||||
// get the db data if not exiting already
|
||||
const log = createLogger({ module: "socket.io", subModule: "roomService" });
|
||||
let ioRef: Server | null = null;
|
||||
|
||||
export const registerRoomService = (io: Server) => {
|
||||
ioRef = io;
|
||||
};
|
||||
|
||||
export const hasRoomMembers = (roomId: string): boolean => {
|
||||
if (!ioRef) return false;
|
||||
|
||||
return (ioRef.sockets.adapter.rooms.get(roomId)?.size ?? 0) > 0;
|
||||
};
|
||||
|
||||
export const getRoomMemberCount = (roomId: string): number => {
|
||||
if (!ioRef) return 0;
|
||||
|
||||
return ioRef.sockets.adapter.rooms.get(roomId)?.size ?? 0;
|
||||
};
|
||||
export const preseedRoom = async (roomId: RoomId) => {
|
||||
if (roomHistory.has(roomId)) {
|
||||
return roomHistory.get(roomId);
|
||||
}
|
||||
|
||||
const roomDef = roomDefinition[roomId] as any;
|
||||
|
||||
if (!roomDef) {
|
||||
log.error({}, `Room ${roomId} is not defined`);
|
||||
}
|
||||
|
||||
const latestData = await roomDef.seed(MAX_HISTORY);
|
||||
|
||||
roomHistory.set(roomId, latestData);
|
||||
|
||||
return latestData;
|
||||
};
|
||||
|
||||
export const createRoomEmitter = (io: Server) => {
|
||||
const addDataToRoom = <T>(roomId: RoomId, payload: T[]) => {
|
||||
if (!roomHistory.has(roomId)) {
|
||||
roomHistory.set(roomId, []);
|
||||
}
|
||||
|
||||
const history = roomHistory.get(roomId)!;
|
||||
history?.push(payload);
|
||||
|
||||
if (history?.length > MAX_HISTORY) {
|
||||
history?.shift();
|
||||
}
|
||||
|
||||
if (!roomBuffers.has(roomId)) {
|
||||
roomBuffers.set(roomId, []);
|
||||
}
|
||||
|
||||
roomBuffers.get(roomId)!.push(payload);
|
||||
|
||||
if (!roomFlushTimers.has(roomId)) {
|
||||
const timer = setTimeout(() => {
|
||||
const buffered = roomBuffers.get(roomId) || [];
|
||||
|
||||
if (buffered.length > 0) {
|
||||
io.to(roomId).emit("room-update", {
|
||||
roomId,
|
||||
payloads: buffered, // ✅ array now
|
||||
});
|
||||
}
|
||||
|
||||
roomBuffers.set(roomId, []);
|
||||
roomFlushTimers.delete(roomId);
|
||||
}, FLUSH_INTERVAL);
|
||||
|
||||
roomFlushTimers.set(roomId, timer);
|
||||
}
|
||||
};
|
||||
|
||||
return { addDataToRoom };
|
||||
};
|
||||
@@ -1,33 +1,16 @@
|
||||
import type { Server as HttpServer } from "node:http";
|
||||
//import { dirname, join } from "node:path";
|
||||
//import { fileURLToPath } from "node:url";
|
||||
import { instrument } from "@socket.io/admin-ui";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
import { allowedOrigins } from "../utils/cors.utils.js";
|
||||
import { registerEmitter } from "./roomEmitter.socket.js";
|
||||
import {
|
||||
createRoomEmitter,
|
||||
preseedRoom,
|
||||
registerRoomService,
|
||||
} from "./roomService.socket.js";
|
||||
import { registerHasRoomMembers } from "./socket.manager.js";
|
||||
import { isRoomKey, roomConfigs } from "./socket.roomConfig.js";
|
||||
|
||||
//const __filename = fileURLToPath(import.meta.url);
|
||||
//const __dirname = dirname(__filename);
|
||||
const log = createLogger({ module: "socket.io", subModule: "setup" });
|
||||
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
//import type { Session, User } from "better-auth"; // adjust if needed
|
||||
import { getRoomConfig } from "./roomDefinitions.socket.js";
|
||||
|
||||
// declare module "socket.io" {
|
||||
// interface Socket {
|
||||
// user?: User | any;
|
||||
// session?: Session;
|
||||
// }
|
||||
// }
|
||||
|
||||
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
const io = new Server(server, {
|
||||
path: `${baseUrl}/api/socket.io`,
|
||||
@@ -37,12 +20,16 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
},
|
||||
});
|
||||
|
||||
// manage members of the rooms.
|
||||
registerRoomService(io);
|
||||
registerHasRoomMembers((roomId) => {
|
||||
return (io.sockets.adapter.rooms.get(roomId)?.size ?? 0) > 0;
|
||||
});
|
||||
|
||||
// ✅ Create emitter instance
|
||||
const { addDataToRoom } = createRoomEmitter(io);
|
||||
registerEmitter(addDataToRoom);
|
||||
registerEmitter((roomId, payloads) => {
|
||||
io.to(roomId).emit("room-update", {
|
||||
roomId,
|
||||
payloads,
|
||||
});
|
||||
});
|
||||
|
||||
io.use(async (socket, next) => {
|
||||
try {
|
||||
@@ -85,79 +72,95 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// s.on("join-room", async (rn) => {
|
||||
// const config = protectedRooms[rn];
|
||||
s.on("join-room", async ({ room, params }) => {
|
||||
if (!isRoomKey(room)) return;
|
||||
|
||||
// if (config?.requiresAuth && !s.user) {
|
||||
// return s.emit("room-error", {
|
||||
// room: rn,
|
||||
// message: "Authentication required",
|
||||
// });
|
||||
// }
|
||||
|
||||
// const roles = Array.isArray(config?.role) ? config?.role : [config?.role];
|
||||
|
||||
// //if (config?.role && s.user?.role !== config.role) {
|
||||
// if (config?.role && !roles.includes(s.user?.role)) {
|
||||
// return s.emit("room-error", {
|
||||
// roomId: rn,
|
||||
// message: `Not authorized to be in room: ${rn}`,
|
||||
// });
|
||||
// }
|
||||
// s.join(rn);
|
||||
|
||||
// // get room seeded
|
||||
// const history = await preseedRoom(rn);
|
||||
// log.info({}, `User joined ${rn}: ${s.id}`);
|
||||
// // send the intial data
|
||||
// s.emit("room-update", {
|
||||
// roomId: rn,
|
||||
// payloads: history,
|
||||
// initial: true,
|
||||
// });
|
||||
// });
|
||||
|
||||
s.on("join-room", async (rn: string) => {
|
||||
const config = getRoomConfig(rn);
|
||||
const config = roomConfigs[room];
|
||||
|
||||
if (!config) {
|
||||
return s.emit("room-error", {
|
||||
roomId: rn,
|
||||
message: `Unknown room: ${rn}`,
|
||||
roomId: room,
|
||||
message: `Unknown room: ${room}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.requiresAuth && !s.user) {
|
||||
const actualRoom = config.buildRoom
|
||||
? config.buildRoom(params)
|
||||
: (room as any);
|
||||
|
||||
const allowed = config.canJoin
|
||||
? await config.canJoin({
|
||||
socket: s,
|
||||
user: s.user,
|
||||
room,
|
||||
actualRoom,
|
||||
params,
|
||||
})
|
||||
: true;
|
||||
|
||||
if (!allowed) {
|
||||
return s.emit("room-error", {
|
||||
roomId: rn,
|
||||
message: "Authentication required",
|
||||
roomId: room,
|
||||
message: `Not authorized to be in room: ${room}`,
|
||||
});
|
||||
}
|
||||
|
||||
const roles = Array.isArray(config.role) ? config.role : [];
|
||||
await s.join(actualRoom);
|
||||
|
||||
if (roles.length > 0 && !roles.includes(s.user?.role)) {
|
||||
return s.emit("room-error", {
|
||||
roomId: rn,
|
||||
message: `Not authorized to be in room: ${rn}`,
|
||||
s.emit("room-joined", {
|
||||
room,
|
||||
roomId: actualRoom,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
s.join(rn);
|
||||
|
||||
const history = await preseedRoom(rn as any);
|
||||
|
||||
log.info({}, `User joined ${rn}: ${s.id}`);
|
||||
if (config.seed) {
|
||||
const payloads = await config.seed({
|
||||
room,
|
||||
actualRoom,
|
||||
params,
|
||||
user: s.user,
|
||||
});
|
||||
|
||||
s.emit("room-update", {
|
||||
roomId: rn,
|
||||
payloads: history,
|
||||
initial: true,
|
||||
room,
|
||||
roomId: actualRoom,
|
||||
type: "snapshot",
|
||||
payloads,
|
||||
});
|
||||
}
|
||||
|
||||
log.info(
|
||||
{ room, actualRoom, params },
|
||||
`User joined ${actualRoom}: ${s.id}`,
|
||||
);
|
||||
});
|
||||
s.on("leave-room", (room) => {
|
||||
s.leave(room);
|
||||
log.info({}, `${s.id} left room: ${room}`);
|
||||
// s.on("leave-room", (room) => {
|
||||
// s.leave(room);
|
||||
// log.info({}, `${s.id} left room: ${JSON.stringify(room)}`);
|
||||
// });
|
||||
s.on("leave-room", async ({ room, params }) => {
|
||||
if (!isRoomKey(room)) return;
|
||||
|
||||
const config = roomConfigs[room];
|
||||
|
||||
if (!config) return;
|
||||
|
||||
const actualRoom = config.buildRoom
|
||||
? config.buildRoom(params)
|
||||
: (room as any);
|
||||
|
||||
await s.leave(actualRoom);
|
||||
|
||||
s.emit("room-left", {
|
||||
room,
|
||||
roomId: actualRoom,
|
||||
params,
|
||||
});
|
||||
|
||||
log.info(
|
||||
{ room, actualRoom, params },
|
||||
`${s.id} left room: ${actualRoom}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
10
backend/socket.io/socket.manager.ts
Normal file
10
backend/socket.io/socket.manager.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
let hasMembersFn: ((roomId: string) => boolean) | null = null;
|
||||
|
||||
export const registerHasRoomMembers = (fn: (roomId: string) => boolean) => {
|
||||
hasMembersFn = fn;
|
||||
};
|
||||
|
||||
export const hasRoomMembers = (roomId: string) => {
|
||||
if (!hasMembersFn) return false;
|
||||
return hasMembersFn(roomId);
|
||||
};
|
||||
117
backend/socket.io/socket.roomConfig.ts
Normal file
117
backend/socket.io/socket.roomConfig.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { getRecentLogs } from "../db/db.socketSeed.js";
|
||||
import { getRecentDockScans } from "../dockdoorScanning/dockdoor.socket.seed.js";
|
||||
|
||||
export type RoomKey =
|
||||
| "logs"
|
||||
| "labels"
|
||||
| "admin"
|
||||
| "inventory"
|
||||
| "dockDoorLoading";
|
||||
|
||||
export type SocketUser = {
|
||||
id: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
export type CanJoinArgs = {
|
||||
socket: any;
|
||||
user?: SocketUser;
|
||||
room: string;
|
||||
actualRoom: string;
|
||||
params?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type RoomConfig = {
|
||||
//requiresAuth?: boolean;
|
||||
//roles?: string[];
|
||||
canJoin?: (args: CanJoinArgs) => boolean | Promise<boolean>;
|
||||
buildRoom?: (params?: Record<string, unknown>) => string | null;
|
||||
seed?: (args: {
|
||||
room: string;
|
||||
actualRoom: string;
|
||||
params?: Record<string, unknown>;
|
||||
user?: SocketUser;
|
||||
}) => Promise<unknown[]>;
|
||||
};
|
||||
|
||||
export function isRoomKey(room: string): room is RoomKey {
|
||||
return room in roomConfigs;
|
||||
}
|
||||
|
||||
export const roomConfigs: Record<RoomKey, RoomConfig> = {
|
||||
logs: {
|
||||
canJoin: ({ user, params }) => {
|
||||
if (!params?.submodule && !params?.module) {
|
||||
return user?.role === "systemAdmin";
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
buildRoom: (params) => {
|
||||
const module = String(params?.module ?? "").toLowerCase();
|
||||
const submodule = String(params?.submodule ?? "").toLowerCase();
|
||||
|
||||
if (module && submodule) return `logs:${module}:${submodule}`;
|
||||
if (submodule) return `logs:${submodule}`;
|
||||
if (module) return `logs:${module}`;
|
||||
|
||||
return "logs";
|
||||
},
|
||||
seed: async ({ params }) => {
|
||||
const module = params?.module ? String(params.module) : undefined;
|
||||
const submodule = params?.submodule
|
||||
? String(params.submodule)
|
||||
: undefined;
|
||||
|
||||
return await getRecentLogs({
|
||||
module,
|
||||
submodule,
|
||||
limit: 200,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
labels: {
|
||||
canJoin: () => true,
|
||||
buildRoom: () => "labels",
|
||||
},
|
||||
|
||||
admin: {
|
||||
canJoin: ({ user, params }) => {
|
||||
if (params?.section === "system") {
|
||||
return user?.role === "systemAdmin";
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
buildRoom: (params) =>
|
||||
params?.section ? `admin:${params.section}` : "admin",
|
||||
},
|
||||
inventory: {
|
||||
canJoin: () => true,
|
||||
buildRoom: (params) =>
|
||||
params?.location ? `inventory:${params.location}` : null,
|
||||
},
|
||||
|
||||
dockDoorLoading: {
|
||||
canJoin: () => true,
|
||||
buildRoom: (params) =>
|
||||
params?.dockId ? `dockDoorLoading:${params.dockId}` : null,
|
||||
seed: async ({ params }) => {
|
||||
return await getRecentDockScans({
|
||||
loadingOrder: params?.loadingOrder as string,
|
||||
limit: 200,
|
||||
});
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, RoomConfig>;
|
||||
|
||||
/*
|
||||
|
||||
socket.emit("join-room", {
|
||||
room: "dockDoorLoading",
|
||||
params: { dockId: "2" },
|
||||
});
|
||||
|
||||
*/
|
||||
@@ -1,5 +1,5 @@
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { hasRoomMembers } from "../socket.io/roomService.socket.js";
|
||||
import { hasRoomMembers } from "../socket.io/socket.manager.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
|
||||
export const ppoRun = async () => {
|
||||
@@ -17,11 +17,13 @@ export const ppoRun = async () => {
|
||||
};
|
||||
|
||||
export const ppooMonitoring = async () => {
|
||||
if (!hasRoomMembers(`ppoo`)) {
|
||||
const roomId = "inventory:ppoo";
|
||||
|
||||
if (!hasRoomMembers(roomId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitToRoom("ppoo", {
|
||||
emitToRoom(roomId, {
|
||||
type: "snapshot",
|
||||
items: await ppoRun(),
|
||||
createdAt: new Date().toISOString(),
|
||||
|
||||
@@ -40,6 +40,11 @@ export default function TransportationBar() {
|
||||
icon: link,
|
||||
url: "/transportation/opendock",
|
||||
},
|
||||
{
|
||||
title: "Active releases",
|
||||
icon: link,
|
||||
url: "/transportation/opendock/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
//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 { permissionQuery } from "../../lib/queries/permsCheck";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
} from "../ui/sidebar";
|
||||
|
||||
export default function WarehouseBar() {
|
||||
const { data: canCreate = false } = useQuery(
|
||||
permissionQuery({
|
||||
warehouse: ["read"],
|
||||
}),
|
||||
);
|
||||
// const { data: canCreate = false } = useQuery(
|
||||
// permissionQuery({
|
||||
// warehouse: ["read"],
|
||||
// }),
|
||||
// );
|
||||
|
||||
const { setOpen } = useSidebar();
|
||||
const items = [
|
||||
@@ -33,7 +33,7 @@ export default function WarehouseBar() {
|
||||
title: "Dock Door Scanning",
|
||||
url: "/warehouse",
|
||||
//icon,
|
||||
isActive: canCreate,
|
||||
isActive: true,
|
||||
items: [
|
||||
{
|
||||
title: "DockDoorScanning",
|
||||
|
||||
@@ -24,11 +24,11 @@ export function AppSidebar() {
|
||||
}),
|
||||
);
|
||||
|
||||
const { data: canReadWarehouse = false } = useQuery(
|
||||
permissionQuery({
|
||||
warehouse: ["read"],
|
||||
}),
|
||||
);
|
||||
// const { data: canReadWarehouse = false } = useQuery(
|
||||
// permissionQuery({
|
||||
// warehouse: ["read"],
|
||||
// }),
|
||||
// );
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
@@ -53,8 +53,7 @@ export function AppSidebar() {
|
||||
|
||||
{!isLoading &&
|
||||
settings.filter((n: any) => n.name === "dockDoorScanning")[0]
|
||||
?.active &&
|
||||
canReadWarehouse && <WarehouseBar />}
|
||||
?.active && <WarehouseBar />}
|
||||
|
||||
{session &&
|
||||
(session.user.role === "admin" ||
|
||||
|
||||
@@ -1,68 +1,110 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import socket from "@/lib/socket.io";
|
||||
|
||||
type RoomParams = Record<string, unknown>;
|
||||
|
||||
type JoinRoomPayload = {
|
||||
room: string;
|
||||
params?: RoomParams;
|
||||
};
|
||||
|
||||
type RoomUpdatePayload<T> = {
|
||||
roomId: string;
|
||||
payloads: T[];
|
||||
type: string;
|
||||
};
|
||||
|
||||
type RoomJoinedPayload = {
|
||||
room: string;
|
||||
roomId: string;
|
||||
};
|
||||
|
||||
type RoomErrorPayload = {
|
||||
room?: string;
|
||||
roomId?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type UpdateMode = "append" | "replace";
|
||||
|
||||
export function useSocketRoom<T>(
|
||||
roomId: string,
|
||||
getKey?: (item: T) => string | number,
|
||||
updateMode: UpdateMode = "append",
|
||||
) {
|
||||
export function useSocketRoom<T>(room: string, params?: RoomParams) {
|
||||
const [actualRoomId, setActualRoomId] = useState<string | null>(null);
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const [info, setInfo] = useState(
|
||||
"No data yet — join the room to start receiving",
|
||||
);
|
||||
|
||||
const clearRoom = useCallback(
|
||||
(id?: string | number) => {
|
||||
if (id !== undefined && getKey) {
|
||||
setData((prev) => prev.filter((item) => getKey(item) !== id));
|
||||
setInfo(`Removed item ${id}`);
|
||||
return;
|
||||
}
|
||||
console.log("cleared data from the room");
|
||||
setData([]);
|
||||
setInfo("Room data cleared");
|
||||
},
|
||||
[getKey],
|
||||
// This is the payload we send to the server.
|
||||
// Example:
|
||||
// { room: "inventory", params: { location: "ppoo" } }
|
||||
const joinPayload = useMemo<JoinRoomPayload>(
|
||||
() => ({
|
||||
room,
|
||||
params,
|
||||
}),
|
||||
[room, params],
|
||||
);
|
||||
|
||||
const clearRoom = useCallback((filterFn?: (item: T) => boolean) => {
|
||||
if (filterFn) {
|
||||
setData((prev) => prev.filter((item) => !filterFn(item)));
|
||||
return;
|
||||
}
|
||||
|
||||
setData([]);
|
||||
setInfo("Room data cleared");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function handleConnect() {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
// Join the logical room.
|
||||
// The server decides the real Socket.IO roomId.
|
||||
// Example:
|
||||
// client sends: { room: "inventory", params: { location: "ppoo" } }
|
||||
// server joins: "inventory:ppoo"
|
||||
function joinRoom() {
|
||||
socket.emit("join-room", joinPayload);
|
||||
setInfo(`Joining room: ${room}`);
|
||||
}
|
||||
|
||||
// Server should emit this after socket.join(actualRoom).
|
||||
// This lets the client know the final roomId to filter updates by.
|
||||
function handleJoined(payload: RoomJoinedPayload) {
|
||||
//if (payload.room !== room) return;
|
||||
|
||||
setActualRoomId(payload.roomId);
|
||||
setInfo(`Joined room: ${payload.roomId}`);
|
||||
}
|
||||
|
||||
function handleUpdate(payload: RoomUpdatePayload<T>) {
|
||||
// protects against other room updates hitting this hook
|
||||
if (payload.roomId !== roomId) return;
|
||||
// If we know the actual roomId, only accept updates for that room.
|
||||
// This protects against other pages/rooms also listening to "room-update".
|
||||
|
||||
// resetting room data for rooms that just need updated data.
|
||||
if (updateMode === "replace") {
|
||||
if (!actualRoomId) return;
|
||||
|
||||
if (payload.roomId !== actualRoomId) return;
|
||||
|
||||
if (payload.type === "snapshot") {
|
||||
setData(payload.payloads);
|
||||
} else {
|
||||
setData((prev) => [...payload.payloads, ...prev]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Append mode is good for logs/scans/events.
|
||||
setData((prev) => [...payload.payloads, ...prev]);
|
||||
setInfo("");
|
||||
}
|
||||
|
||||
function handleError(err: RoomErrorPayload) {
|
||||
if (err.roomId && err.roomId !== roomId) return;
|
||||
// Ignore errors for other logical rooms.
|
||||
if (err.room && err.room !== room) return;
|
||||
|
||||
// Ignore errors for other actual rooms.
|
||||
if (err.roomId && room && err.roomId !== room) return;
|
||||
|
||||
toast.error(err.message);
|
||||
setInfo(err.message ?? "Room error");
|
||||
}
|
||||
|
||||
socket.on("connect", handleConnect);
|
||||
socket.on("connect", joinRoom);
|
||||
socket.on("room-joined", handleJoined);
|
||||
socket.on("room-update", handleUpdate);
|
||||
socket.on("room-error", handleError);
|
||||
|
||||
@@ -70,31 +112,26 @@ export function useSocketRoom<T>(
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
// If already connected, join immediately
|
||||
// If socket is already connected, join immediately.
|
||||
if (socket.connected) {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
joinRoom();
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket.emit("leave-room", roomId);
|
||||
// Leave using the same logical room payload.
|
||||
// Server should rebuild the actual room and call socket.leave(actualRoom).
|
||||
socket.emit("leave-room", joinPayload);
|
||||
|
||||
socket.off("connect", handleConnect);
|
||||
socket.off("connect", joinRoom);
|
||||
socket.off("room-joined", handleJoined);
|
||||
socket.off("room-update", handleUpdate);
|
||||
socket.off("room-error", handleError);
|
||||
};
|
||||
}, [roomId, updateMode]);
|
||||
}, [room, joinPayload, actualRoomId]);
|
||||
|
||||
return { data, info, clearRoom };
|
||||
return {
|
||||
data,
|
||||
info,
|
||||
clearRoom,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const isDockDoorPage = location.pathname.startsWith("/dockdoor");
|
||||
|
||||
useSocketRoom(
|
||||
dockId ? `dockdoor:${dockId}` : null,
|
||||
isDockDoorPage,
|
||||
);
|
||||
|
||||
*/
|
||||
|
||||
23
frontend/src/lib/queries/openDockApt.ts
Normal file
23
frontend/src/lib/queries/openDockApt.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function opendockApt() {
|
||||
return queryOptions({
|
||||
queryKey: ["opendockApt"],
|
||||
queryFn: () => fetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const fetch = async () => {
|
||||
if (window.location.hostname === "localhost") {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await api.get("/opendock?daysCreated=90");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
@@ -25,6 +25,7 @@ import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
||||
import { Route as WarehouseDockdoorscanningIndexRouteImport } from './routes/warehouse/dockdoorscanning/index'
|
||||
import { Route as TransportationOpendockIndexRouteImport } from './routes/transportation/opendock/index'
|
||||
import { Route as WarehouseDockdoorscanningDockRouteImport } from './routes/warehouse/dockdoorscanning/$dock'
|
||||
import { Route as TransportationOpendockReleasesRouteImport } from './routes/transportation/opendock/releases'
|
||||
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
||||
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
|
||||
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
|
||||
@@ -113,6 +114,12 @@ const WarehouseDockdoorscanningDockRoute =
|
||||
path: '/warehouse/dockdoorscanning/$dock',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const TransportationOpendockReleasesRoute =
|
||||
TransportationOpendockReleasesRouteImport.update({
|
||||
id: '/transportation/opendock/releases',
|
||||
path: '/transportation/opendock/releases',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const authUserSignupRoute = authUserSignupRouteImport.update({
|
||||
id: '/(auth)/user/signup',
|
||||
path: '/user/signup',
|
||||
@@ -152,6 +159,7 @@ export interface FileRoutesByFullPath {
|
||||
'/user/profile': typeof authUserProfileRoute
|
||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
'/user/signup': typeof authUserSignupRoute
|
||||
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
|
||||
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
|
||||
'/transportation/opendock/': typeof TransportationOpendockIndexRoute
|
||||
'/warehouse/dockdoorscanning/': typeof WarehouseDockdoorscanningIndexRoute
|
||||
@@ -174,6 +182,7 @@ export interface FileRoutesByTo {
|
||||
'/user/profile': typeof authUserProfileRoute
|
||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
'/user/signup': typeof authUserSignupRoute
|
||||
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
|
||||
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
|
||||
'/transportation/opendock': typeof TransportationOpendockIndexRoute
|
||||
'/warehouse/dockdoorscanning': typeof WarehouseDockdoorscanningIndexRoute
|
||||
@@ -197,6 +206,7 @@ export interface FileRoutesById {
|
||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
'/(auth)/user/signup': typeof authUserSignupRoute
|
||||
'/transportation/opendock/releases': typeof TransportationOpendockReleasesRoute
|
||||
'/warehouse/dockdoorscanning/$dock': typeof WarehouseDockdoorscanningDockRoute
|
||||
'/transportation/opendock/': typeof TransportationOpendockIndexRoute
|
||||
'/warehouse/dockdoorscanning/': typeof WarehouseDockdoorscanningIndexRoute
|
||||
@@ -221,6 +231,7 @@ export interface FileRouteTypes {
|
||||
| '/user/profile'
|
||||
| '/user/resetpassword'
|
||||
| '/user/signup'
|
||||
| '/transportation/opendock/releases'
|
||||
| '/warehouse/dockdoorscanning/$dock'
|
||||
| '/transportation/opendock/'
|
||||
| '/warehouse/dockdoorscanning/'
|
||||
@@ -243,6 +254,7 @@ export interface FileRouteTypes {
|
||||
| '/user/profile'
|
||||
| '/user/resetpassword'
|
||||
| '/user/signup'
|
||||
| '/transportation/opendock/releases'
|
||||
| '/warehouse/dockdoorscanning/$dock'
|
||||
| '/transportation/opendock'
|
||||
| '/warehouse/dockdoorscanning'
|
||||
@@ -265,6 +277,7 @@ export interface FileRouteTypes {
|
||||
| '/(auth)/user/profile'
|
||||
| '/(auth)/user/resetpassword'
|
||||
| '/(auth)/user/signup'
|
||||
| '/transportation/opendock/releases'
|
||||
| '/warehouse/dockdoorscanning/$dock'
|
||||
| '/transportation/opendock/'
|
||||
| '/warehouse/dockdoorscanning/'
|
||||
@@ -288,6 +301,7 @@ export interface RootRouteChildren {
|
||||
authUserProfileRoute: typeof authUserProfileRoute
|
||||
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
||||
authUserSignupRoute: typeof authUserSignupRoute
|
||||
TransportationOpendockReleasesRoute: typeof TransportationOpendockReleasesRoute
|
||||
WarehouseDockdoorscanningDockRoute: typeof WarehouseDockdoorscanningDockRoute
|
||||
TransportationOpendockIndexRoute: typeof TransportationOpendockIndexRoute
|
||||
WarehouseDockdoorscanningIndexRoute: typeof WarehouseDockdoorscanningIndexRoute
|
||||
@@ -408,6 +422,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof WarehouseDockdoorscanningDockRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/transportation/opendock/releases': {
|
||||
id: '/transportation/opendock/releases'
|
||||
path: '/transportation/opendock/releases'
|
||||
fullPath: '/transportation/opendock/releases'
|
||||
preLoaderRoute: typeof TransportationOpendockReleasesRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/(auth)/user/signup': {
|
||||
id: '/(auth)/user/signup'
|
||||
path: '/user/signup'
|
||||
@@ -456,6 +477,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
authUserProfileRoute: authUserProfileRoute,
|
||||
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
||||
authUserSignupRoute: authUserSignupRoute,
|
||||
TransportationOpendockReleasesRoute: TransportationOpendockReleasesRoute,
|
||||
WarehouseDockdoorscanningDockRoute: WarehouseDockdoorscanningDockRoute,
|
||||
TransportationOpendockIndexRoute: TransportationOpendockIndexRoute,
|
||||
WarehouseDockdoorscanningIndexRoute: WarehouseDockdoorscanningIndexRoute,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
//import { useMemo } from "react";
|
||||
import { useSocketRoom } from "@/hooks/socket.io.hook";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
@@ -71,9 +72,10 @@ function LevelBadge({ level }: { level: string }) {
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
// const logParams = useMemo(() => ({ subModule: "query" }), []);
|
||||
// const { data: logs } = useSocketRoom<LogEntry>("logs", logParams);
|
||||
const { data: logs } = useSocketRoom<LogEntry>("logs");
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
const column = [
|
||||
columnHelper.accessor("createdAt", {
|
||||
header: ({ column }) => <SearchableHeader column={column} title="Time" />,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { format } from "date-fns-tz";
|
||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
import { Suspense, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Spinner } from "../../components/ui/spinner";
|
||||
@@ -171,7 +171,13 @@ const ServerTable = () => {
|
||||
};
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: logs = [], clearRoom } = useSocketRoom<any>("admin:build");
|
||||
const params = useMemo(
|
||||
() => ({
|
||||
submodule: "builds",
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { data: logs = [], clearRoom } = useSocketRoom<any>("logs", params);
|
||||
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
@@ -181,7 +187,7 @@ function RouteComponent() {
|
||||
<SearchableHeader column={column} title="Time" searchable={false} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
||||
cell: (i) => i.getValue(), //format(i.getValue(), "M/d/yyyy HH:mm") ,
|
||||
}),
|
||||
columnHelper.accessor("message", {
|
||||
header: ({ column }) => (
|
||||
@@ -210,7 +216,7 @@ function RouteComponent() {
|
||||
<Button
|
||||
size="icon"
|
||||
variant={"destructive"}
|
||||
onClick={() => clearRoom(x.timestamp)}
|
||||
onClick={() => clearRoom((item) => item.timestamp === x.timestamp)}
|
||||
>
|
||||
<Trash />
|
||||
</Button>
|
||||
|
||||
221
frontend/src/routes/transportation/opendock/releases.tsx
Normal file
221
frontend/src/routes/transportation/opendock/releases.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { Trash } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Spinner } from "../../../components/ui/spinner";
|
||||
import { api } from "../../../lib/apiHelper";
|
||||
import { authClient } from "../../../lib/auth-client";
|
||||
import { opendockApt } from "../../../lib/queries/openDockApt";
|
||||
import { permissionQuery } from "../../../lib/queries/permsCheck";
|
||||
import LstTable from "../../../lib/tableStuff/LstTable";
|
||||
import SearchableHeader from "../../../lib/tableStuff/SearchableHeader";
|
||||
import SkellyTable from "../../../lib/tableStuff/SkellyTable";
|
||||
|
||||
export const Route = createFileRoute("/transportation/opendock/releases")({
|
||||
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 OpendockApts = () => {
|
||||
const { data, refetch } = useSuspenseQuery(opendockApt());
|
||||
const { data: canReadOpenDock = false } = useQuery(
|
||||
permissionQuery({
|
||||
openDock: ["update"],
|
||||
}),
|
||||
);
|
||||
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("release", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Release" searchable={true} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row.appointment.customFields.find((x: any) => x.label === "Article")
|
||||
?.value ?? "",
|
||||
{
|
||||
id: "article",
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Article" searchable={true} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
},
|
||||
),
|
||||
columnHelper.accessor("appointment.status", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Current Opendock status"
|
||||
searchable={true}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("upd_date", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Last Updated" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => {
|
||||
function isDateValid(date: any) {
|
||||
return date instanceof Date && !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
const trueDate = isDateValid(new Date(i.getValue()))
|
||||
? formatInTimeZone(
|
||||
i.getValue(),
|
||||
`${window.LST_CONFIG?.timezone}`,
|
||||
"MM/dd/yyyy HH:mm:ss",
|
||||
)
|
||||
: "invalid time";
|
||||
|
||||
return <span>{trueDate}</span>;
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
if (canReadOpenDock) {
|
||||
columns.push(
|
||||
columnHelper.accessor("deleteRelease", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Delete Link"
|
||||
searchable={false}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => {
|
||||
// biome-ignore lint: just removing the lint for now to get this going will maybe fix later
|
||||
const [activeToggle, setActiveToggle] = useState(false);
|
||||
|
||||
const onTrigger = async () => {
|
||||
setActiveToggle(true);
|
||||
|
||||
try {
|
||||
const res = await api.delete(
|
||||
`/opendock/${i.row.original.id}`,
|
||||
|
||||
{
|
||||
withCredentials: true,
|
||||
timeout: 5000,
|
||||
validateStatus: () => true,
|
||||
},
|
||||
);
|
||||
|
||||
if (res.data.success) {
|
||||
toast.success(
|
||||
`Release: ${i.row.original.release} was deleted.`,
|
||||
);
|
||||
refetch();
|
||||
setActiveToggle(false);
|
||||
}
|
||||
|
||||
if (!res.data.success) {
|
||||
toast.error(
|
||||
`Release: ${i.row.original.release} encountered an error when trying to delete: ${res.data.message}`,
|
||||
);
|
||||
refetch();
|
||||
setActiveToggle(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setActiveToggle(false);
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={
|
||||
activeToggle ||
|
||||
i.row.original.appointment.status !== "Scheduled"
|
||||
}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
{activeToggle ? (
|
||||
<span>
|
||||
<Spinner />
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<Trash />
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
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 />}>
|
||||
<OpendockApts />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -68,9 +68,9 @@ function RouteComponent() {
|
||||
<div className="flex justify-center">
|
||||
<p>Please select an active loading order for {dockData[0].name}</p>
|
||||
</div>
|
||||
<div className="flex justify-center mt-5 gap-2">
|
||||
<div className="flex flex-row justify-center mt-5 gap-2">
|
||||
{loadingPlans && loadingPlans.length > 0 ? (
|
||||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
{loadingPlans.map((i: any) => {
|
||||
return (
|
||||
<Card key={i.id} className="max-w-96">
|
||||
@@ -140,7 +140,8 @@ function RouteComponent() {
|
||||
)
|
||||
}
|
||||
disabled={
|
||||
dockData[0].currentLoadingOrder === "" ? true : false
|
||||
dockData[0].currentLoadingOrder === "" ||
|
||||
dockData[0].currentLoadingOrder !== i.id.toString()
|
||||
}
|
||||
>
|
||||
Finish Loading
|
||||
|
||||
@@ -21,6 +21,7 @@ export const finishLoadingOrder = async (
|
||||
dockId: string,
|
||||
refetch: any,
|
||||
refetchActiveLoading: any,
|
||||
clear?: boolean,
|
||||
) => {
|
||||
try {
|
||||
const res = await api.post(
|
||||
@@ -28,6 +29,7 @@ export const finishLoadingOrder = async (
|
||||
{
|
||||
loadingOrder: loadingOrder,
|
||||
dockId: dockId,
|
||||
clear,
|
||||
},
|
||||
{ validateStatus: (status) => status < 500 },
|
||||
);
|
||||
@@ -80,8 +82,8 @@ function RouteComponent() {
|
||||
(x: any) => x.id === Number(i.currentLoadingOrder),
|
||||
)
|
||||
: [];
|
||||
console.log(loadingPlan);
|
||||
console.log(loadingPlanItems);
|
||||
// console.log(loadingPlan);
|
||||
// console.log(loadingPlanItems);
|
||||
return (
|
||||
<Card
|
||||
key={i.id}
|
||||
@@ -150,6 +152,7 @@ function RouteComponent() {
|
||||
i.dockId,
|
||||
refetch,
|
||||
refetchActiveLoading,
|
||||
true,
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { useSocketRoom } from "../../../../hooks/socket.io.hook";
|
||||
@@ -9,6 +10,7 @@ import { api } from "../../../../lib/apiHelper";
|
||||
import { useAppForm } from "../../../../lib/formSutff";
|
||||
import { getActiveLoadingOrders } from "../../../../lib/queries/getActiveDockScanners";
|
||||
import { getActiveDockScanners } from "../../../../lib/queries/getActiveLoadingOrders";
|
||||
import { permissionQuery } from "../../../../lib/queries/permsCheck";
|
||||
import LstTable from "../../../../lib/tableStuff/LstTable";
|
||||
import SearchableHeader from "../../../../lib/tableStuff/SearchableHeader";
|
||||
import { finishLoadingOrder } from "..";
|
||||
@@ -20,15 +22,53 @@ export const Route = createFileRoute(
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { dockScans } = Route.useParams();
|
||||
const { data: logs, clearRoom } = useSocketRoom<any>(
|
||||
`dockDoorLoading:${dockScans}`,
|
||||
const { data: canSee = false } = useQuery(
|
||||
permissionQuery({
|
||||
warehouse: ["update"],
|
||||
}),
|
||||
);
|
||||
const { data, refetch } = useSuspenseQuery(getActiveDockScanners());
|
||||
const { dockScans } = Route.useParams();
|
||||
const params = useMemo(
|
||||
() => ({
|
||||
dockId: dockScans,
|
||||
loadingOrder: data[0].currentLoadingOrder ?? undefined,
|
||||
}),
|
||||
[dockScans, data],
|
||||
);
|
||||
const { data: logs, clearRoom } = useSocketRoom<any>(
|
||||
`dockDoorLoading`,
|
||||
params,
|
||||
);
|
||||
|
||||
const { data: loadingPlanItems, refetch: refetchActiveLoading } =
|
||||
useSuspenseQuery(getActiveLoadingOrders());
|
||||
|
||||
const { data: canReadWarehouse = false } = useQuery(
|
||||
permissionQuery({
|
||||
warehouse: ["delete"],
|
||||
}),
|
||||
);
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
const logCount = logs.length;
|
||||
|
||||
// TODO: move this to an onMessage: handFunction
|
||||
/*
|
||||
const handleLogMessage = useCallback(() => {
|
||||
refetchActiveLoading();
|
||||
}, [refetchActiveLoading]);
|
||||
|
||||
const { data: logs } = useSocketRoom<LogEntry>("logs", {
|
||||
onMessage: handleLogMessage,
|
||||
});
|
||||
*/
|
||||
|
||||
// biome-ignore lint: false
|
||||
useEffect(() => {
|
||||
refetchActiveLoading();
|
||||
}, [logCount, refetchActiveLoading]);
|
||||
|
||||
const column = [
|
||||
columnHelper.accessor("loadingOrder", {
|
||||
header: ({ column }) => (
|
||||
@@ -119,6 +159,7 @@ function RouteComponent() {
|
||||
(x: any) => x.id === Number(data[0].currentLoadingOrder),
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
@@ -138,7 +179,8 @@ function RouteComponent() {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-1/2 flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
{canSee && (
|
||||
<div>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
@@ -166,8 +208,10 @@ function RouteComponent() {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mr-3 ">
|
||||
{loadingPlan && loadingPlan.length > 0 && (
|
||||
<div className="flex mb-2 gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -180,15 +224,18 @@ function RouteComponent() {
|
||||
clearRoom();
|
||||
}}
|
||||
disabled={
|
||||
loadingPlan ||
|
||||
(loadingPlan && loadingPlan.length < 0) ||
|
||||
loadingPlan[0].loadingPlanItems[0].loadedQuantityLUs !==
|
||||
loadingPlan[0].loadingPlanItems[0].plannedQuantityLUs
|
||||
}
|
||||
>
|
||||
Finish Loading order
|
||||
</Button>
|
||||
{canReadWarehouse && (
|
||||
<Button onClick={() => clearRoom()}>Clear Table</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<LstTable data={logs} columns={column} pageSize={50} />
|
||||
|
||||
1
migrations/0063_illegal_mauler.sql
Normal file
1
migrations/0063_illegal_mauler.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "opendock_apt" ADD COLUMN "status" text DEFAULT 'active';
|
||||
2696
migrations/meta/0063_snapshot.json
Normal file
2696
migrations/meta/0063_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -442,6 +442,13 @@
|
||||
"when": 1781009330205,
|
||||
"tag": "0062_square_harrier",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 63,
|
||||
"version": "7",
|
||||
"when": 1781045714275,
|
||||
"tag": "0063_illegal_mauler",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lst_v3",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"version": "0.1.0-alpha.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lst_v3",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"version": "0.1.0-alpha.3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@dotenvx/dotenvx": "^1.57.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lst_v3",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"version": "0.1.0-alpha.3",
|
||||
"description": "The tool that supports us in our everyday alplaprod",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user