Compare commits
11 Commits
v0.1.0-alp
...
2a648f6306
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a648f6306 | |||
| 69a9a81a88 | |||
| 188fdd0eb7 | |||
| db28635c8c | |||
| bcdf9566bc | |||
| c15ee070e7 | |||
| 347edb7078 | |||
| fe0b1573f3 | |||
| 9c0ef1f5df | |||
| 8b076949a7 | |||
| 6d0fb8aee4 |
66
.gitea/ISSUE_TEMPLATE/bug_report_frontend.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report_frontend.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: Bug Report - Frontend
|
||||
about: Report something that is broken or not working correctly
|
||||
title: "[BUG - Frontend] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- bug
|
||||
- frontend
|
||||
---
|
||||
|
||||
# Summary
|
||||
|
||||
Briefly explain the issue.
|
||||
|
||||
---
|
||||
|
||||
# Steps To Reproduce
|
||||
|
||||
1. Go to ...
|
||||
2. Click ...
|
||||
3. Scan ...
|
||||
4. Error occurs ...
|
||||
|
||||
---
|
||||
|
||||
# Expected Behavior
|
||||
|
||||
What should have happened?
|
||||
|
||||
---
|
||||
|
||||
# Actual Behavior
|
||||
|
||||
What actually happened instead?
|
||||
|
||||
---
|
||||
|
||||
# Severity
|
||||
|
||||
- [ ] Low
|
||||
- [ ] Medium
|
||||
- [ ] High
|
||||
- [ ] Critical
|
||||
|
||||
---
|
||||
|
||||
# Environment
|
||||
|
||||
Example:
|
||||
|
||||
- Production
|
||||
- Development
|
||||
- Zebra Scanner
|
||||
- Mobile Device
|
||||
- Windows Server
|
||||
- Docker
|
||||
|
||||
---
|
||||
|
||||
# Logs / Screenshots
|
||||
|
||||
Paste logs or upload screenshots here.
|
||||
|
||||
```txt
|
||||
Paste logs here
|
||||
66
.gitea/ISSUE_TEMPLATE/bug_report_mobile.md
Normal file
66
.gitea/ISSUE_TEMPLATE/bug_report_mobile.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: Bug Report - Mobile
|
||||
about: Report something that is broken or not working correctly
|
||||
title: "[BUG - Mobile] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- bug
|
||||
- mobile
|
||||
---
|
||||
|
||||
# Summary
|
||||
|
||||
Briefly explain the issue.
|
||||
|
||||
---
|
||||
|
||||
# Steps To Reproduce
|
||||
|
||||
1. Go to ...
|
||||
2. Click ...
|
||||
3. Scan ...
|
||||
4. Error occurs ...
|
||||
|
||||
---
|
||||
|
||||
# Expected Behavior
|
||||
|
||||
What should have happened?
|
||||
|
||||
---
|
||||
|
||||
# Actual Behavior
|
||||
|
||||
What actually happened instead?
|
||||
|
||||
---
|
||||
|
||||
# Severity
|
||||
|
||||
- [ ] Low
|
||||
- [ ] Medium
|
||||
- [ ] High
|
||||
- [ ] Critical
|
||||
|
||||
---
|
||||
|
||||
# Environment
|
||||
|
||||
Example:
|
||||
|
||||
- Production
|
||||
- Development
|
||||
- Zebra Scanner
|
||||
- Mobile Device
|
||||
- Windows Server
|
||||
- Docker
|
||||
|
||||
---
|
||||
|
||||
# Logs / Screenshots
|
||||
|
||||
Paste logs or upload screenshots here.
|
||||
|
||||
```txt
|
||||
Paste logs here
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
name: Bug Report
|
||||
name: Bug Report - Server
|
||||
about: Report something that is broken or not working correctly
|
||||
title: "[BUG] "
|
||||
title: "[BUG - Server] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- bug
|
||||
- server
|
||||
|
||||
---
|
||||
|
||||
48
.gitea/ISSUE_TEMPLATE/enhancement_frontend.md
Normal file
48
.gitea/ISSUE_TEMPLATE/enhancement_frontend.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Enhancement - Frontend
|
||||
about: Improve or refine an existing feature
|
||||
title: "[ENHANCEMENT - Frontend] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- enhancement
|
||||
- frontend
|
||||
---
|
||||
|
||||
# Existing Feature
|
||||
|
||||
What current feature or workflow is being improved?
|
||||
|
||||
Example:
|
||||
|
||||
- Notifications
|
||||
- Scanner Login
|
||||
- Release Monitor
|
||||
- Printing
|
||||
- Auth
|
||||
|
||||
---
|
||||
|
||||
# Proposed Improvement
|
||||
|
||||
Describe the improvement.
|
||||
|
||||
---
|
||||
|
||||
# Expected Benefit
|
||||
|
||||
Why would this improvement help?
|
||||
|
||||
---
|
||||
|
||||
# Impact
|
||||
|
||||
- [ ] Small
|
||||
- [ ] Medium
|
||||
- [ ] Large
|
||||
|
||||
---
|
||||
|
||||
# Additional Notes
|
||||
|
||||
Anything else worth mentioning.
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
name: Enhancement
|
||||
name: Enhancement - Mobile
|
||||
about: Improve or refine an existing feature
|
||||
title: "[ENHANCEMENT] "
|
||||
title: "[ENHANCEMENT - Mobile] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- enhancement
|
||||
- mobile
|
||||
---
|
||||
|
||||
# Existing Feature
|
||||
48
.gitea/ISSUE_TEMPLATE/enhancement_server.md
Normal file
48
.gitea/ISSUE_TEMPLATE/enhancement_server.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Enhancement - Server
|
||||
about: Improve or refine an existing feature
|
||||
title: "[ENHANCEMENT - Server] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- enhancement
|
||||
- server
|
||||
---
|
||||
|
||||
# Existing Feature
|
||||
|
||||
What current feature or workflow is being improved?
|
||||
|
||||
Example:
|
||||
|
||||
- Notifications
|
||||
- Scanner Login
|
||||
- Release Monitor
|
||||
- Printing
|
||||
- Auth
|
||||
|
||||
---
|
||||
|
||||
# Proposed Improvement
|
||||
|
||||
Describe the improvement.
|
||||
|
||||
---
|
||||
|
||||
# Expected Benefit
|
||||
|
||||
Why would this improvement help?
|
||||
|
||||
---
|
||||
|
||||
# Impact
|
||||
|
||||
- [ ] Small
|
||||
- [ ] Medium
|
||||
- [ ] Large
|
||||
|
||||
---
|
||||
|
||||
# Additional Notes
|
||||
|
||||
Anything else worth mentioning.
|
||||
41
.gitea/ISSUE_TEMPLATE/feature_request_frontend.md
Normal file
41
.gitea/ISSUE_TEMPLATE/feature_request_frontend.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Feature Request - Frontend
|
||||
about: Suggest a brand new feature or module
|
||||
title: "[FEATURE - Frontend] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- feature
|
||||
- frontend
|
||||
---
|
||||
|
||||
# Problem Statement
|
||||
|
||||
What problem are you trying to solve?
|
||||
|
||||
---
|
||||
|
||||
# Proposed Solution
|
||||
|
||||
Describe the feature you would like added.
|
||||
|
||||
---
|
||||
|
||||
# Alternatives Considered
|
||||
|
||||
Any other ideas, workarounds, or approaches?
|
||||
|
||||
---
|
||||
|
||||
# Priority
|
||||
|
||||
- [ ] Nice to Have
|
||||
- [ ] Medium Priority
|
||||
- [ ] High Priority
|
||||
- [ ] Critical
|
||||
|
||||
---
|
||||
|
||||
# Additional Context
|
||||
|
||||
Add mockups, screenshots, examples, or notes here.
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
name: Feature Request
|
||||
name: Feature Request - Mobile
|
||||
about: Suggest a brand new feature or module
|
||||
title: "[FEATURE] "
|
||||
title: "[FEATURE - Mobile] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- feature
|
||||
- mobile
|
||||
---
|
||||
|
||||
# Problem Statement
|
||||
41
.gitea/ISSUE_TEMPLATE/feature_request_server.md
Normal file
41
.gitea/ISSUE_TEMPLATE/feature_request_server.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Feature Request - Server
|
||||
about: Suggest a brand new feature or module
|
||||
title: "[FEATURE - Server] "
|
||||
ref: "main"
|
||||
labels:
|
||||
|
||||
- feature
|
||||
- server
|
||||
---
|
||||
|
||||
# Problem Statement
|
||||
|
||||
What problem are you trying to solve?
|
||||
|
||||
---
|
||||
|
||||
# Proposed Solution
|
||||
|
||||
Describe the feature you would like added.
|
||||
|
||||
---
|
||||
|
||||
# Alternatives Considered
|
||||
|
||||
Any other ideas, workarounds, or approaches?
|
||||
|
||||
---
|
||||
|
||||
# Priority
|
||||
|
||||
- [ ] Nice to Have
|
||||
- [ ] Medium Priority
|
||||
- [ ] High Priority
|
||||
- [ ] Critical
|
||||
|
||||
---
|
||||
|
||||
# Additional Context
|
||||
|
||||
Add mockups, screenshots, examples, or notes here.
|
||||
22
backend/db/schema/dockdoor.schema.ts
Normal file
22
backend/db/schema/dockdoor.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const dockDoorScanners = pgTable("dock_door_scanners", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
ip: text("ip").notNull(),
|
||||
name: text("name").unique(),
|
||||
dockId: text("dock_id"),
|
||||
active: boolean("active").default(true),
|
||||
currentLoadingOrder: text("current_loading_order").default(""),
|
||||
add_date: timestamp("add_date").defaultNow(),
|
||||
add_user: text("add_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
});
|
||||
|
||||
export const dockDoorScannersSchema = createSelectSchema(dockDoorScanners);
|
||||
export const newDockDoorScannersSchema = createInsertSchema(dockDoorScanners);
|
||||
|
||||
export type DockDoorScanners = z.infer<typeof dockDoorScannersSchema>;
|
||||
export type NewDockDoorScanners = z.infer<typeof newDockDoorScannersSchema>;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { addDays, subDays } from "date-fns";
|
||||
import { format } from "date-fns-tz";
|
||||
import { Router } from "express";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (_, res) => {
|
||||
const orders = await runProdApi({
|
||||
method: "post",
|
||||
endpoint: "/public/v1.0/OutboundDeliveries/LoadingOrders/Search",
|
||||
data: [
|
||||
{
|
||||
loadingDateFrom: format(subDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
|
||||
loadingDateTo: format(addDays(new Date(Date.now()), 3), "yyyy-MM-dd"),
|
||||
states: [
|
||||
1, // planned
|
||||
],
|
||||
//isCommissioned: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "current Active loading orders",
|
||||
message: `Current active loading loaders.`,
|
||||
data: orders?.data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
18
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
18
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Router } from "express";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "lane check",
|
||||
message: `Release x is being closed now. the bol should come out at the default printer.`,
|
||||
data: req.body ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
89
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
89
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// sends the units from the dock door scanner here.
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
|
||||
// validate we are active
|
||||
|
||||
type Data = {
|
||||
dockId?: string;
|
||||
sscc?: string;
|
||||
runningNr?: string;
|
||||
};
|
||||
|
||||
export const loadUnit = async (data: Data) => {
|
||||
// are we even active at this time?
|
||||
const dockDoorActive = await db.query.settings.findFirst({
|
||||
where: (u, { eq }) => eq(u.name, "dockDoorScanning"),
|
||||
});
|
||||
|
||||
if (!dockDoorActive?.active) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadunit",
|
||||
message: "Dock door scanning feature is not active.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: "",
|
||||
});
|
||||
}
|
||||
// check if its a valids an sscc
|
||||
|
||||
if (data.sscc === "noread") {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message:
|
||||
"Failed to load the unit to the truck, there was no pallet read.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: `dockDoorLoading${data.dockId}`,
|
||||
});
|
||||
}
|
||||
|
||||
// check if we currently have a loading order attached to the dock door.
|
||||
const dock = await db
|
||||
.select()
|
||||
.from(dockDoorScanners)
|
||||
.where(eq(dockDoorScanners.dockId, data.dockId as string));
|
||||
|
||||
if (dock[0]?.currentLoadingOrder === "") {
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrders",
|
||||
message:
|
||||
"There are know current active loading orders please start one and try again.",
|
||||
data: [],
|
||||
notify: false,
|
||||
room: `dockDoorLoading${data.dockId}`,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: pallet validation, check if we are on hold, then check if we have been in the staging warehouse for more than x time.
|
||||
|
||||
// if on hold stop the scan and send a bad read with the reason its on hold and what its on hold for, including coa.
|
||||
|
||||
// if precheck is active then check if we have a warehouse, then check if the pallet was in the warehouse for greater than the define min, all fails send a warning and still do the scan
|
||||
|
||||
// add the loading units
|
||||
try {
|
||||
const prod = await runProdApi({
|
||||
method: "post",
|
||||
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
|
||||
data: [{ sscc: data.sscc?.slice(2) }],
|
||||
});
|
||||
|
||||
console.log(prod?.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
43
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
43
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Express } from "express";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import activeLoadingOrders from "./dockdoor.activeLoadingOrders.route.js";
|
||||
import closeLoadingOrder from "./dockdoor.closeLoadingOrder.route.js";
|
||||
import startLoad from "./dockdoor.startLoad.route.js";
|
||||
import prodDocks from "./dockdoors.docks.route.js";
|
||||
import docks from "./dockdoors.route.js";
|
||||
|
||||
export const setupDockDoorRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/scanners`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
|
||||
docks,
|
||||
);
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/closeLoadingOrder`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
closeLoadingOrder,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/activeLoadingOrders`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
activeLoadingOrders,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/startLoad`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
startLoad,
|
||||
);
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/docks`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
prodDocks,
|
||||
);
|
||||
|
||||
// TODO : add manual way to add pallets
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
65
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
65
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
const startLoading = z.object({
|
||||
loadingOrder: z.string(),
|
||||
dockId: z.string(),
|
||||
});
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
try {
|
||||
const validated = startLoading.parse(req.body);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: validated.loadingOrder,
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
})
|
||||
.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 ${validated.loadingOrder} was just added to dockId ${validated.dockId}.`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingOrder",
|
||||
message: `Failed to start loading order.`,
|
||||
data: (error as any) ?? [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default r;
|
||||
54
backend/dockdoorScanning/dockdoors.docks.route.ts
Normal file
54
backend/dockdoorScanning/dockdoors.docks.route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Router } from "express";
|
||||
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();
|
||||
|
||||
r.get("/", async (_, res) => {
|
||||
const activeDocks = sqlQuerySelector(`outbound.docks`) as SqlQuery;
|
||||
|
||||
if (!activeDocks.success) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "docks",
|
||||
message: `There was an error getting the docks query.`,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
prodQuery(activeDocks.query, "Current Active Docks"),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "newDock",
|
||||
message: `There was an error getting the docks.`,
|
||||
data: (error as any) ?? ([] as any),
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "docks",
|
||||
message: `Current active docks.`,
|
||||
data: (data.data as any) ?? ([] as any),
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
76
backend/dockdoorScanning/dockdoors.route.ts
Normal file
76
backend/dockdoorScanning/dockdoors.route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
const newDockScanner = z.object({
|
||||
ip: z.string(),
|
||||
name: z.string(),
|
||||
dockId: z.string(),
|
||||
});
|
||||
|
||||
r.get("/", async (_, res) => {
|
||||
try {
|
||||
const docks = await db
|
||||
.select()
|
||||
.from(dockDoorScanners)
|
||||
.orderBy(dockDoorScanners.name);
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "lane check",
|
||||
message: `All dock Doors.`,
|
||||
data: docks ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "newDock",
|
||||
message: `There was an error adding in the new dock.`,
|
||||
data: error ?? ([] as any),
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
r.post("/", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const validated = newDockScanner.parse(req.body);
|
||||
|
||||
const newDock = await db
|
||||
.insert(dockDoorScanners)
|
||||
.values(validated)
|
||||
.returning();
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "lane check",
|
||||
message: `${validated.name} was just added.`,
|
||||
data: newDock ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "newDock",
|
||||
message: `There was an error adding in the new dock.`,
|
||||
data: error ?? ([] as any),
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -13,7 +13,7 @@ router.post("/", async (req, res) => {
|
||||
await db
|
||||
.update(scanUser)
|
||||
.set({ lastScan: sql`NOW()` })
|
||||
.where(eq(scanUser.name, body.name));
|
||||
.where(eq(scanUser.name, body.user));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
6
backend/prodSql/queries/outbound.docks.sql
Normal file
6
backend/prodSql/queries/outbound.docks.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
USE [test1_AlplaPROD2.0_Read]
|
||||
|
||||
SELECT *
|
||||
FROM [masterData].[Dock] (nolock)
|
||||
where active = 1
|
||||
order by Description desc
|
||||
@@ -4,6 +4,7 @@ import { setupAuthRoutes } from "./auth/auth.routes.js";
|
||||
// import the routes and route setups
|
||||
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
|
||||
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
|
||||
import { setupDockDoorRoutes } from "./dockdoorScanning/dockdoor.routes.js";
|
||||
import { setupGPSqlRoutes } from "./gpSql/gpSql.routes.js";
|
||||
import { setupMobileRoutes } from "./mobile/mobile.routes.js";
|
||||
import { setupNotificationRoutes } from "./notification/notification.routes.js";
|
||||
@@ -29,4 +30,5 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||
setupNotificationRoutes(baseUrl, app);
|
||||
setupOCPRoutes(baseUrl, app);
|
||||
setupTCPRoutes(baseUrl, app);
|
||||
setupDockDoorRoutes(baseUrl, app);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "./utils/analyticRouteHits.utils.js";
|
||||
import { createCronJob } from "./utils/croner.utils.js";
|
||||
import { sendEmail } from "./utils/sendEmail.utils.js";
|
||||
import { ppooMonitoring } from "./warehousing/warehousing.ppooMonitor.js";
|
||||
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
export let systemSettings: Setting[] = [];
|
||||
@@ -78,6 +79,10 @@ const start = async () => {
|
||||
runRouteHitAnalyticsCron(),
|
||||
);
|
||||
|
||||
createCronJob("ppooMonitor", "*/45 * * * * *", async () =>
|
||||
ppooMonitoring(),
|
||||
);
|
||||
|
||||
createCronJob("cleanHitsUp", "0 0 7 * * *", () => cleanupOldRouteHits());
|
||||
// one shots only needed to run on server startups
|
||||
createNotifications();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
import type { RoomId } from "./roomDefinitions.socket.js";
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
||||
|
||||
@@ -1,17 +1,50 @@
|
||||
import { desc } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
import { ppoRun } from "../warehousing/warehousing.ppooMonitor.js";
|
||||
|
||||
type RoomDefinition<T = unknown> = {
|
||||
seed: (limit: number) => Promise<T[]>;
|
||||
};
|
||||
|
||||
export const protectedRooms: any = {
|
||||
export type StaticRoomId = "logs" | "labels" | "admin" | "admin:build" | "ppoo";
|
||||
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: {},
|
||||
};
|
||||
|
||||
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) => {
|
||||
@@ -48,4 +81,14 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
ppoo: {
|
||||
seed: async (limit) => {
|
||||
console.log(limit);
|
||||
return {
|
||||
type: "snapshot",
|
||||
items: await ppoRun(),
|
||||
createdAt: new Date().toISOString(),
|
||||
} as any;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// the emitter setup
|
||||
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
import type { RoomId } from "./roomDefinitions.socket.js";
|
||||
|
||||
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;
|
||||
|
||||
|
||||
@@ -7,18 +7,33 @@ import {
|
||||
roomFlushTimers,
|
||||
roomHistory,
|
||||
} from "./roomCache.socket.js";
|
||||
import { roomDefinition } from "./roomDefinitions.socket.js";
|
||||
import type { RoomId } from "./types.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];
|
||||
const roomDef = roomDefinition[roomId] as any;
|
||||
|
||||
if (!roomDef) {
|
||||
log.error({}, `Room ${roomId} is not defined`);
|
||||
@@ -32,7 +47,7 @@ export const preseedRoom = async (roomId: RoomId) => {
|
||||
};
|
||||
|
||||
export const createRoomEmitter = (io: Server) => {
|
||||
const addDataToRoom = <T>(roomId: RoomId, payload: T) => {
|
||||
const addDataToRoom = <T>(roomId: RoomId, payload: T[]) => {
|
||||
if (!roomHistory.has(roomId)) {
|
||||
roomHistory.set(roomId, []);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import { Server } from "socket.io";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { allowedOrigins } from "../utils/cors.utils.js";
|
||||
import { registerEmitter } from "./roomEmitter.socket.js";
|
||||
import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
|
||||
import {
|
||||
createRoomEmitter,
|
||||
preseedRoom,
|
||||
registerRoomService,
|
||||
} from "./roomService.socket.js";
|
||||
|
||||
//const __filename = fileURLToPath(import.meta.url);
|
||||
//const __dirname = dirname(__filename);
|
||||
@@ -15,7 +19,7 @@ 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 { protectedRooms } from "./roomDefinitions.socket.js";
|
||||
import { getRoomConfig } from "./roomDefinitions.socket.js";
|
||||
|
||||
// declare module "socket.io" {
|
||||
// interface Socket {
|
||||
@@ -33,6 +37,9 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
},
|
||||
});
|
||||
|
||||
// manage members of the rooms.
|
||||
registerRoomService(io);
|
||||
|
||||
// ✅ Create emitter instance
|
||||
const { addDataToRoom } = createRoomEmitter(io);
|
||||
registerEmitter(addDataToRoom);
|
||||
@@ -78,38 +85,76 @@ 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 (rn) => {
|
||||
// const config = protectedRooms[rn];
|
||||
|
||||
if (config?.requiresAuth && !s.user) {
|
||||
// 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);
|
||||
|
||||
if (!config) {
|
||||
return s.emit("room-error", {
|
||||
room: rn,
|
||||
roomId: rn,
|
||||
message: `Unknown room: ${rn}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.requiresAuth && !s.user) {
|
||||
return s.emit("room-error", {
|
||||
roomId: rn,
|
||||
message: "Authentication required",
|
||||
});
|
||||
}
|
||||
|
||||
const roles = Array.isArray(config?.role) ? config?.role : [config?.role];
|
||||
const roles = Array.isArray(config.role) ? config.role : [];
|
||||
|
||||
//if (config?.role && s.user?.role !== config.role) {
|
||||
if (config?.role && !roles.includes(s.user?.role)) {
|
||||
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.join(rn);
|
||||
|
||||
// get room seeded
|
||||
const history = await preseedRoom(rn);
|
||||
const history = await preseedRoom(rn as any);
|
||||
|
||||
log.info({}, `User joined ${rn}: ${s.id}`);
|
||||
// send the intial data
|
||||
|
||||
s.emit("room-update", {
|
||||
roomId: rn,
|
||||
payloads: history,
|
||||
initial: true,
|
||||
});
|
||||
});
|
||||
|
||||
s.on("leave-room", (room) => {
|
||||
s.leave(room);
|
||||
log.info({}, `${s.id} left room: ${room}`);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type RoomId = "logs" | "labels" | "admin" | "admin:build"; //| "alerts" | "metrics";
|
||||
@@ -162,6 +162,17 @@ const servers: NewServerData[] = [
|
||||
serverLoc: "D$\\LST_V3",
|
||||
buildNumber: 1,
|
||||
},
|
||||
{
|
||||
name: "Salt Lake City",
|
||||
server: "USSLC1VMS006",
|
||||
plantToken: "usslc1",
|
||||
idAddress: "10.202.0.26",
|
||||
greatPlainsPlantCode: "70",
|
||||
contactEmail: "",
|
||||
contactPhone: "",
|
||||
serverLoc: "D$\\LST_V3",
|
||||
buildNumber: 1,
|
||||
},
|
||||
];
|
||||
|
||||
// notes here for when it comes time to pull in all the server address info [test1_AlplaPROD2.0_Read].[masterData].[Plant] has everything from every server :D
|
||||
|
||||
@@ -86,8 +86,40 @@ const newSettings: NewSetting[] = [
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "dockDoorScanning",
|
||||
value: "0",
|
||||
active: false,
|
||||
description: "dock door scanning",
|
||||
moduleName: "dockDoorScanning",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
|
||||
// standard settings
|
||||
{
|
||||
name: "stagingWarehouse",
|
||||
value: "30218",
|
||||
active: true,
|
||||
description:
|
||||
"The warehouse we will use for staging, validation that we did our prechecks if required",
|
||||
moduleName: "dockDoorScanning",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "precheck",
|
||||
value: "5",
|
||||
active: false,
|
||||
description:
|
||||
"Precheck is required, the value is in minute, 5 min should be 5",
|
||||
moduleName: "dockDoorScanning",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "prolinkCheck",
|
||||
value: "1",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import net from "node:net";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { dockDoorScanners } from "../db/schema/dockdoor.schema.js";
|
||||
import { printerData } from "../db/schema/printers.schema.js";
|
||||
import { loadUnit } from "../dockdoorScanning/dockdoor.loadUnits.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { delay } from "../utils/delay.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
@@ -14,6 +16,7 @@ export let isServerRunning = false;
|
||||
|
||||
const port = parseInt(process.env.TCP_PORT ?? "2222", 10);
|
||||
|
||||
// This is the parser for zebra scanners
|
||||
const parseTcpAlert = (input: string) => {
|
||||
// guard
|
||||
const colonIndex = input.indexOf(":");
|
||||
@@ -74,6 +77,24 @@ export const startTCPServer = async () => {
|
||||
|
||||
printerListen(printerData as PrinterData);
|
||||
}
|
||||
|
||||
// check if its a dock door scanner
|
||||
// TODO: move to the db and get real info lol
|
||||
const dockdoorScanners = await db.select().from(dockDoorScanners);
|
||||
|
||||
if (dockdoorScanners.some((s) => s.ip === ip.replace("::ffff:", ""))) {
|
||||
console.log("dock door logic");
|
||||
|
||||
const currentDock = dockdoorScanners.filter(
|
||||
(s) => s.ip === ip.replace("::ffff:", ""),
|
||||
);
|
||||
|
||||
// send the data + dock scan over
|
||||
loadUnit({
|
||||
dockId: currentDock[0]?.dockId ?? "0",
|
||||
sscc: data.toString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("end", () => {
|
||||
|
||||
@@ -16,7 +16,8 @@ export interface ReturnHelper<T = unknown[]> {
|
||||
| "tcp"
|
||||
| "logistics"
|
||||
| "admin"
|
||||
| "mobile";
|
||||
| "mobile"
|
||||
| "dockdoor";
|
||||
subModule: string;
|
||||
|
||||
level: "info" | "error" | "debug" | "fatal" | "warn";
|
||||
|
||||
29
backend/warehousing/warehousing.ppooMonitor.ts
Normal file
29
backend/warehousing/warehousing.ppooMonitor.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { hasRoomMembers } from "../socket.io/roomService.socket.js";
|
||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||
|
||||
export const ppoRun = async () => {
|
||||
const laneData = await runProdApi({
|
||||
method: "post",
|
||||
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
||||
data: [
|
||||
{
|
||||
laneIds: ["0"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return laneData?.data ?? [];
|
||||
};
|
||||
|
||||
export const ppooMonitoring = async () => {
|
||||
if (!hasRoomMembers(`ppoo`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitToRoom("ppoo", {
|
||||
type: "snapshot",
|
||||
items: await ppoRun(),
|
||||
createdAt: new Date().toISOString(),
|
||||
} as any);
|
||||
};
|
||||
@@ -13,6 +13,7 @@ type RoomErrorPayload = {
|
||||
|
||||
export function useSocketRoom<T>(
|
||||
roomId: string,
|
||||
enabled = true,
|
||||
getKey?: (item: T) => string | number,
|
||||
) {
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
@@ -35,6 +36,7 @@ export function useSocketRoom<T>(
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!roomId || !enabled) return;
|
||||
function handleConnect() {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
@@ -74,7 +76,18 @@ export function useSocketRoom<T>(
|
||||
socket.off("room-update", handleUpdate);
|
||||
socket.off("room-error", handleError);
|
||||
};
|
||||
}, [roomId]);
|
||||
}, [roomId, enabled]);
|
||||
|
||||
return { data, info, clearRoom };
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const isDockDoorPage = location.pathname.startsWith("/dockdoor");
|
||||
|
||||
useSocketRoom(
|
||||
dockId ? `dockdoor:${dockId}` : null,
|
||||
isDockDoorPage,
|
||||
);
|
||||
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns-tz";
|
||||
import { Trash } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
@@ -56,7 +55,7 @@ const updateSettings = async (
|
||||
) => {
|
||||
//console.log(id, data);
|
||||
try {
|
||||
const res = await axios.patch(`/mobile/auth/user/${id}`, data, {
|
||||
const res = await api.patch(`/mobile/auth/user/${id}`, data, {
|
||||
withCredentials: true,
|
||||
timeout: 15000,
|
||||
validateStatus: () => true,
|
||||
@@ -121,7 +120,7 @@ const ScanUserTable = () => {
|
||||
<EditableCellInput
|
||||
value={getValue()}
|
||||
id={row.original.id}
|
||||
field="value"
|
||||
field="pinNumber"
|
||||
onSubmit={({ id, field, value }) => {
|
||||
updateSetting.mutate({ id, field, value });
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "LST mobile",
|
||||
"slug": "lst-mobile",
|
||||
"version": "0.11.1-alpha",
|
||||
"orientation": "portrait",
|
||||
"orientation": "default",
|
||||
"icon": "./assets/icon_white.png",
|
||||
"scheme": "lstmobile",
|
||||
"userInterfaceStyle": "automatic",
|
||||
@@ -15,10 +15,14 @@
|
||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"versionCode": 37,
|
||||
"versionCode": 39,
|
||||
"minSupportedVersionCode": 33,
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "net.alpla.lst.mobile"
|
||||
"package": "net.alpla.lst.mobile",
|
||||
"permissions": [
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||
"android.permission.READ_EXTERNAL_STORAGE"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
|
||||
88
lstMobile/package-lock.json
generated
88
lstMobile/package-lock.json
generated
@@ -35,6 +35,7 @@
|
||||
"expo-image": "~55.0.8",
|
||||
"expo-linking": "~55.0.13",
|
||||
"expo-router": "~55.0.12",
|
||||
"expo-screen-orientation": "~55.0.16",
|
||||
"expo-splash-screen": "~55.0.18",
|
||||
"expo-status-bar": "~55.0.5",
|
||||
"expo-symbols": "~55.0.7",
|
||||
@@ -46,6 +47,7 @@
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.4",
|
||||
"react-native-blob-util": "^0.24.9",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
@@ -6132,6 +6134,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/base-64": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -8171,6 +8178,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-screen-orientation": {
|
||||
"version": "55.0.16",
|
||||
"resolved": "https://registry.npmjs.org/expo-screen-orientation/-/expo-screen-orientation-55.0.16.tgz",
|
||||
"integrity": "sha512-I9NIqb2zAkHsK/CxdmMdmgSFP7E1v++8z/Mj2X9j1AuK6l55yOma/JHo905KU3x2zPm9/l1BTzmMA320tiBebg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-server": {
|
||||
"version": "55.0.9",
|
||||
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.9.tgz",
|
||||
@@ -12821,6 +12838,77 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-blob-util": {
|
||||
"version": "0.24.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native-blob-util/-/react-native-blob-util-0.24.9.tgz",
|
||||
"integrity": "sha512-tG3+m0WhVdBGifvxSFxZDVqtr85D0fGBJU6E4UxmK3tU+RabJZTumXEn8k7jn5/NFe8OhQhPjtBEZ11ZJ6L7Vw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-64": "0.1.0",
|
||||
"glob": "13.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ronradtke"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-blob-util/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-blob-util/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-blob-util/node_modules/glob": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz",
|
||||
"integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^10.1.2",
|
||||
"minipass": "^7.1.2",
|
||||
"path-scurry": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-blob-util/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-css-interop": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.2.3.tgz",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"expo-image": "~55.0.8",
|
||||
"expo-linking": "~55.0.13",
|
||||
"expo-router": "~55.0.12",
|
||||
"expo-screen-orientation": "~55.0.16",
|
||||
"expo-splash-screen": "~55.0.18",
|
||||
"expo-status-bar": "~55.0.5",
|
||||
"expo-symbols": "~55.0.7",
|
||||
@@ -56,6 +57,7 @@
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.4",
|
||||
"react-native-blob-util": "^0.24.9",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Redirect, Tabs } from "expo-router";
|
||||
import { Redirect, Tabs, useRouter } from "expo-router";
|
||||
import {
|
||||
Boxes,
|
||||
Container,
|
||||
Home,
|
||||
LogOut,
|
||||
Logs,
|
||||
Rows4,
|
||||
Settings,
|
||||
} from "lucide-react-native";
|
||||
import { Alert } from "react-native";
|
||||
import { useAppStore } from "../../hooks/useAppStore";
|
||||
import { useMobileAuthStore } from "../../hooks/useMobileAuth";
|
||||
|
||||
@@ -20,6 +22,8 @@ export default function TabsLayout() {
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
const user = useMobileAuthStore((s) => s.user);
|
||||
const isUnlocked = useMobileAuthStore((s) => s.isUnlocked);
|
||||
const logoutScanner = useMobileAuthStore((s) => s.logout);
|
||||
const router = useRouter();
|
||||
|
||||
const port = parseInt(serverPort || "0", 10) >= 50000;
|
||||
|
||||
@@ -36,6 +40,32 @@ export default function TabsLayout() {
|
||||
return role ? allowed.includes(role) : false;
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
// optional confirm
|
||||
Alert.alert("Logout", "Are you sure?", [
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Logout",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
// clear auth/session
|
||||
logoutScanner();
|
||||
router.replace("/(tabs)/scanner");
|
||||
|
||||
// clear zustand/session stuff
|
||||
//useAuthStore.getState().reset();
|
||||
|
||||
// maybe clear async storage too
|
||||
// await AsyncStorage.clear();
|
||||
},
|
||||
},
|
||||
]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
@@ -62,10 +92,10 @@ export default function TabsLayout() {
|
||||
name="ppoo"
|
||||
options={{
|
||||
title: "PPOO",
|
||||
href:
|
||||
isNormalScanner || !hasRole(["admin", "manager"])
|
||||
? null
|
||||
: "/(tabs)/ppoo",
|
||||
// href:
|
||||
// isNormalScanner || !hasRole(["admin", "manager"])
|
||||
// ? null
|
||||
// : "/(tabs)/ppoo",
|
||||
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
@@ -73,10 +103,10 @@ export default function TabsLayout() {
|
||||
name="laneCheck"
|
||||
options={{
|
||||
title: "Lane Check",
|
||||
href:
|
||||
isNormalScanner || !hasRole(["admin", "manager"])
|
||||
? null
|
||||
: "/(tabs)/laneCheck",
|
||||
// href:
|
||||
// isNormalScanner || !hasRole(["admin", "manager"])
|
||||
// ? null
|
||||
// : "/(tabs)/laneCheck",
|
||||
tabBarIcon: ({ color, size }) => <Rows4 size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
@@ -112,6 +142,7 @@ export default function TabsLayout() {
|
||||
parseInt(serverPort || "0", 10) >= 50000 ? null : "/(tabs)/logs",
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<Tabs.Screen
|
||||
name="config"
|
||||
options={{
|
||||
@@ -121,6 +152,22 @@ export default function TabsLayout() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="logout"
|
||||
options={{
|
||||
title: "Logout",
|
||||
tabBarIcon: ({ color, size }) => <LogOut color={color} size={size} />,
|
||||
}}
|
||||
listeners={{
|
||||
tabPress: (e) => {
|
||||
// stop navigation
|
||||
e.preventDefault();
|
||||
|
||||
// run logout logic
|
||||
logout();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
3
lstMobile/src/app/(tabs)/logout.tsx
Normal file
3
lstMobile/src/app/(tabs)/logout.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function LogoutScreen() {
|
||||
return null;
|
||||
}
|
||||
@@ -1,210 +1,105 @@
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns-tz";
|
||||
import * as Device from "expo-device";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import type React from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { GlobalFooter } from "../../components/UpdateFooter";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Card, CardContent, CardHeader } from "../../components/ui/card";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { useAppStore } from "../../hooks/useAppStore";
|
||||
import { scannerFeedback } from "../../lib/feedbackScan";
|
||||
import { type ZebraScanResult, zebraScanner } from "../../lib/ZebraScanner";
|
||||
Button,
|
||||
ScrollView,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
||||
|
||||
const InfoRow = ({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<View className="flex-row justify-between gap-4 py-2 border-b border-gray-200">
|
||||
<Text className="text-sm text-gray-500">{label}</Text>
|
||||
<Text className="text-sm font-medium text-gray-900 text-right flex-1">
|
||||
{value}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
type PPOO = {
|
||||
type: string;
|
||||
items: any;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
export default function PPOO() {
|
||||
const [units, setUnits] = useState<any>(null);
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
const { data } = useSocketRoom<any>("ppoo", undefined, "replace") as any;
|
||||
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
||||
|
||||
const handleScan = useCallback(
|
||||
async (scan: ZebraScanResult) => {
|
||||
setUnits(null);
|
||||
await scannerFeedback({
|
||||
type: "scan",
|
||||
sound: true,
|
||||
vibrate: true,
|
||||
led: true,
|
||||
});
|
||||
if (!scan.data.startsWith("loc")) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Scan error",
|
||||
text2: "The last scan was not a lane please try again",
|
||||
});
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet =
|
||||
Device.modelName?.toLowerCase().includes("et40") ||
|
||||
Device.modelName?.toLowerCase().includes("et45");
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`http://${serverIp.trim()}:3000/lst/api/mobile/lanecheck`,
|
||||
{
|
||||
lane: "loc#1#0<",
|
||||
},
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
if (res.status === 200) {
|
||||
setUnits(res.data);
|
||||
Toast.show({
|
||||
type: "info",
|
||||
text1: "Lane Data",
|
||||
text2: "All Loading Units from this lane will be listed below",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Lane Data",
|
||||
text2: "Error getting lane data please try again",
|
||||
});
|
||||
}
|
||||
},
|
||||
[serverIp.trim],
|
||||
);
|
||||
const columns = isTablet ? 3 : 1;
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
zebraScanner.startListening();
|
||||
const gap = 8;
|
||||
const cardWidth =
|
||||
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||
|
||||
const sub = zebraScanner.addScanListener((scan) => {
|
||||
//console.log("SCAN:", scan);
|
||||
handleScan(scan);
|
||||
});
|
||||
const items = data?.items ?? [];
|
||||
const sortedItems = useMemo(() => {
|
||||
return [...items].sort((a, b) => {
|
||||
const aDate = new Date(a.lastMovingDate).getTime();
|
||||
const bDate = new Date(b.lastMovingDate).getTime();
|
||||
|
||||
return () => {
|
||||
sub.remove();
|
||||
zebraScanner.stopListening();
|
||||
//setUnits(null);
|
||||
};
|
||||
}, [handleScan]),
|
||||
);
|
||||
return sortDir === "asc" ? aDate - bDate : bDate - aDate;
|
||||
});
|
||||
}, [items, sortDir]);
|
||||
|
||||
//console.log(logsInfo);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
{units ? (
|
||||
// <SafeAreaView className={`flex-1 w-full items-center`}>
|
||||
// <ScrollView className="w-full flex-1">
|
||||
// <View className="flex items-center gap-2 w-full">
|
||||
// {units.data?.map((i: any, index: any) => (
|
||||
// <View key={`${i.runningNumber}-${index}`}>
|
||||
// <Text>example</Text>
|
||||
// </View>
|
||||
// ))}
|
||||
// </View>
|
||||
// </ScrollView>
|
||||
// </SafeAreaView>
|
||||
<SafeAreaView className={`w-full items-center`}>
|
||||
<View style={{ padding: 2 }}>
|
||||
<Text>There Are {units.data.length} units in PPOO</Text>
|
||||
</View>
|
||||
<ScrollView className="w-full" style={{ marginBottom: 20 }}>
|
||||
<View>
|
||||
{units.data.map((i, index) => (
|
||||
<View
|
||||
key={`${i.runningNumber}-${index}`}
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
margin: 2,
|
||||
}}
|
||||
>
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Card
|
||||
className="w-full"
|
||||
style={{
|
||||
borderColor:
|
||||
i.state === "QualityBlocked" ? "red" : undefined,
|
||||
borderWidth: 4,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Text>
|
||||
{i.articleId} - {i.articleName}
|
||||
</Text>
|
||||
<View className="flex items-center mt-2">
|
||||
<View className="flex m-2">
|
||||
<Button
|
||||
onPress={() =>
|
||||
setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))
|
||||
}
|
||||
title={`Sort: ${sortDir}`}
|
||||
/>
|
||||
</View>
|
||||
{sortedItems.length === 0 ? (
|
||||
<View className="flex items-center">
|
||||
<Text>Loading PPOO...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<SafeAreaView className="flex-1">
|
||||
<ScrollView className="w-full">
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{sortedItems.map((i: any) => {
|
||||
return (
|
||||
<View key={i.runningNumber}>
|
||||
<Card
|
||||
//className={isTablet ? "w-[32%]" : "w-full"}
|
||||
style={{
|
||||
borderColor:
|
||||
i.mainDefectId === 864
|
||||
? "blue"
|
||||
: i.state === "QualityBlocked"
|
||||
? "red"
|
||||
: undefined,
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Text>
|
||||
{i.articleId} - {i.articleName}
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
Running Number: {i.runningNumber ?? "Non barcoded"}
|
||||
</Text>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Details for Article {i.articleId}, Rn:
|
||||
{i.runningNumber ?? "Non barcoded"}{" "}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<InfoRow
|
||||
label="Production Date"
|
||||
value={format(i.productionDate, "MM/dd/yyyy HH:mm")}
|
||||
/>
|
||||
<InfoRow label="Quantity" value={i.quantity} />
|
||||
{i.state === "QualityBlocked" && (
|
||||
<InfoRow
|
||||
label="Defect"
|
||||
value={i.mainDefectGroupDescription}
|
||||
/>
|
||||
)}
|
||||
{i.state === "QualityBlocked" && (
|
||||
<InfoRow
|
||||
label="Description"
|
||||
value={i.mainDefectDescription}
|
||||
/>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</View>
|
||||
))}
|
||||
<Text>
|
||||
Running Number: {i.runningNumber ?? "Non barcoded"}
|
||||
</Text>
|
||||
<Text>
|
||||
Date: {format(i.lastMovingDate, "M/d/yyyy HH:mm")}
|
||||
</Text>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
) : (
|
||||
<View className="mt-50">
|
||||
<Text className="text-2xl text-center">
|
||||
Please scan a lane to see all Units that are in the lane.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View>
|
||||
<GlobalFooter />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import "../../global.css";
|
||||
import { useEffect } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import useDeviceLock from "../hooks/useDeviceCheck";
|
||||
import { connectSocket } from "../lib/socket.io";
|
||||
import { zebraScanner } from "../lib/ZebraScanner";
|
||||
|
||||
export default function RootLayout() {
|
||||
useDeviceLock();
|
||||
|
||||
useEffect(() => {
|
||||
zebraScanner.ensureProfile();
|
||||
connectSocket();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -99,9 +99,16 @@ export default function Setup() {
|
||||
justifyContent: "center",
|
||||
padding: 3,
|
||||
borderRadius: 8,
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<Button title="Submit" onPress={authCheck} />
|
||||
<Button
|
||||
title="Home"
|
||||
onPress={() => {
|
||||
router.push("/(tabs)/scanner");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
@@ -145,14 +152,7 @@ export default function Setup() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
padding: 3,
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
<View className="flex gap-2 flex-row">
|
||||
<Button title="Save Config" onPress={handleSave} />
|
||||
<Button
|
||||
title="Home"
|
||||
|
||||
@@ -182,14 +182,17 @@ export default function LSTScanner() {
|
||||
}, [handleScan]),
|
||||
);
|
||||
return (
|
||||
<View className={`${bgColor ?? ""} flex-1 w-screen`}>
|
||||
<View style={{ alignItems: "center", margin: 5 }}>
|
||||
<Text style={{ fontSize: 14, fontWeight: "600" }}>
|
||||
User: {formatName(user?.name ?? "")}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 18, fontWeight: "600" }}>
|
||||
LST Scanner id: {user?.scannerId}
|
||||
</Text>
|
||||
<View className={`${bgColor ?? ""} flex-1 w-full`}>
|
||||
<View className="flex gap-2 items-center">
|
||||
<View className="flex flex-col gap-2 items-center">
|
||||
<Text style={{ fontSize: 14, fontWeight: "600" }}>
|
||||
Lst user: {formatName(user?.name ?? "")}
|
||||
</Text>
|
||||
{/* <Text style={{ fontSize: 14, fontWeight: "600" }}>
|
||||
LST Scanner id: {user?.scannerId}
|
||||
</Text> */}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginTop: 5,
|
||||
@@ -197,8 +200,8 @@ export default function LSTScanner() {
|
||||
}}
|
||||
>
|
||||
{!lastScan ? (
|
||||
<View style={{ marginTop: 10, alignItems: "center" }}>
|
||||
<Text className="text-xl font-bold">Ready to scan</Text>
|
||||
<View style={{ marginTop: 2, alignItems: "center" }}>
|
||||
<Text className="text-lg font-bold">Ready to scan</Text>
|
||||
<Text>Please Scan a command to start scanning...</Text>
|
||||
<Text className="text-sm">
|
||||
Scanning a label could cause errors due to incorrect previous
|
||||
@@ -208,7 +211,7 @@ export default function LSTScanner() {
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
marginTop: 10,
|
||||
marginTop: 2,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
@@ -217,10 +220,10 @@ export default function LSTScanner() {
|
||||
.map((i) => {
|
||||
return (
|
||||
<View
|
||||
style={{ marginTop: 10, alignItems: "center" }}
|
||||
style={{ marginTop: 2, alignItems: "center" }}
|
||||
key={i}
|
||||
>
|
||||
<Text style={{ fontSize: 18, fontWeight: "600" }}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "600" }}>
|
||||
{i}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -237,18 +240,17 @@ export default function LSTScanner() {
|
||||
color={bgColor}
|
||||
clearScan={clearScans}
|
||||
/>
|
||||
<GlobalFooter />
|
||||
</View>
|
||||
|
||||
<View className="m-2">
|
||||
{/* <View className="m-2">
|
||||
{user && (
|
||||
<View className="items-center">
|
||||
<Button title="Logout" onPress={logoutScanner} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
<GlobalFooter />
|
||||
</View>
|
||||
</View> */}
|
||||
{/* <View style={{ maxHeight: 75 }} className="flex-1 bg-slate-500"></View> */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,28 +13,30 @@ export function GlobalFooter() {
|
||||
if (serverVersion && serverVersion?.versionCode <= build) return;
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
{hasUpdate && (
|
||||
<View className="items-center h-[75px] bg-[#EB091A]">
|
||||
<Link href={"/updateScreen"}>
|
||||
<Text className="h-[75px] font-medium text-base text-wrap text-center">
|
||||
Critical updates pending, once you are completed with your task
|
||||
please click me for instructions to update
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
)}
|
||||
{(hasUpdate || shouldUpdate) && (
|
||||
<View className="bg-slate-500">
|
||||
{hasUpdate && (
|
||||
<View className="items-center h-[75px] bg-[#EB091A] justify-center">
|
||||
<Link href="/updateScreen">
|
||||
<Text className="font-medium text-base text-center">
|
||||
Critical updates pending, once you are completed with your
|
||||
task please click me for instructions to update
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!hasUpdate && shouldUpdate && (
|
||||
<View className="bg-[#FDBA74]">
|
||||
<Link href={"/updateScreen"}>
|
||||
<Text className="h-[16] font-medium text-base text-wrap text-center">
|
||||
There is an update click me for instructions
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{!hasUpdate && shouldUpdate && (
|
||||
<View className="bg-[#FDBA74] py-2 items-center">
|
||||
<Link href="/updateScreen">
|
||||
<Text className="font-medium text-base text-center">
|
||||
There is an update click me for instructions
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
136
lstMobile/src/hooks/socket.io.hook.ts
Normal file
136
lstMobile/src/hooks/socket.io.hook.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { getSocket } from "../lib/socket.io";
|
||||
|
||||
type RoomUpdatePayload<T> = {
|
||||
roomId: string;
|
||||
payloads: T[];
|
||||
};
|
||||
|
||||
type RoomErrorPayload = {
|
||||
roomId?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type UpdateMode = "append" | "replace";
|
||||
|
||||
export function useSocketRoom<T>(
|
||||
roomId: string,
|
||||
getKey?: (item: T) => string | number,
|
||||
updateMode: UpdateMode = "append",
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
setData([]);
|
||||
setInfo("Room data cleared");
|
||||
},
|
||||
[getKey],
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const socket = getSocket();
|
||||
function handleConnect() {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
}
|
||||
|
||||
function handleUpdate(payload: RoomUpdatePayload<T>) {
|
||||
// protects against other room updates hitting this hook
|
||||
if (payload.roomId !== roomId) return;
|
||||
|
||||
// resetting room data for rooms that just need updated data.
|
||||
if (updateMode === "replace") {
|
||||
setData(payload.payloads);
|
||||
} else {
|
||||
setData((prev) => [...payload.payloads, ...prev]);
|
||||
}
|
||||
|
||||
setInfo("");
|
||||
}
|
||||
|
||||
function handleError(err: RoomErrorPayload) {
|
||||
if (err.roomId && err.roomId !== roomId) return;
|
||||
setInfo(err.message ?? "Room error");
|
||||
}
|
||||
|
||||
socket.on("connect", handleConnect);
|
||||
socket.on("room-update", handleUpdate);
|
||||
socket.on("room-error", handleError);
|
||||
|
||||
if (!socket.connected && socket.disconnected) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
// If already connected, join immediately
|
||||
if (socket.connected) {
|
||||
socket.emit("join-room", roomId);
|
||||
setInfo(`Joined room: ${roomId}`);
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket.emit("leave-room", roomId);
|
||||
|
||||
socket.off("connect", handleConnect);
|
||||
socket.off("room-update", handleUpdate);
|
||||
socket.off("room-error", handleError);
|
||||
};
|
||||
}, [roomId, updateMode]),
|
||||
);
|
||||
|
||||
// useEffect(() => {
|
||||
// const socket = getSocket();
|
||||
// function handleConnect() {
|
||||
// socket.emit("join-room", roomId);
|
||||
// setInfo(`Joined room: ${roomId}`);
|
||||
// }
|
||||
|
||||
// function handleUpdate(payload: RoomUpdatePayload<T>) {
|
||||
// // protects against other room updates hitting this hook
|
||||
// if (payload.roomId !== roomId) return;
|
||||
|
||||
// setData((prev) => [...payload.payloads, ...prev]);
|
||||
// setInfo("");
|
||||
// }
|
||||
|
||||
// function handleError(err: RoomErrorPayload) {
|
||||
// if (err.roomId && err.roomId !== roomId) return;
|
||||
// setInfo(err.message ?? "Room error");
|
||||
// }
|
||||
|
||||
// if (!socket.connected && socket.disconnected) {
|
||||
// socket.connect();
|
||||
// }
|
||||
|
||||
// // If already connected, join immediately
|
||||
// if (socket.connected) {
|
||||
// socket.emit("join-room", roomId);
|
||||
// setInfo(`Joined room: ${roomId}`);
|
||||
// }
|
||||
|
||||
// socket.on("connect", handleConnect);
|
||||
// socket.on("room-update", handleUpdate);
|
||||
// socket.on("room-error", handleError);
|
||||
|
||||
// return () => {
|
||||
// socket.emit("leave-room", roomId);
|
||||
// console.log("leaving Room");
|
||||
// socket.off("connect", handleConnect);
|
||||
// socket.off("room-update", handleUpdate);
|
||||
// socket.off("room-error", handleError);
|
||||
// };
|
||||
// }, [roomId]);
|
||||
|
||||
return { data, info, clearRoom };
|
||||
}
|
||||
50
lstMobile/src/lib/lastestVersion.ts
Normal file
50
lstMobile/src/lib/lastestVersion.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
|
||||
export async function downloadLatestApk(serverIp: string, port: string) {
|
||||
const url = `http://${serverIp}:${port}/lst/api/mobile/apk/latest`;
|
||||
|
||||
const apkPath = `${ReactNativeBlobUtil.fs.dirs.DownloadDir}/lst-mobile.apk`;
|
||||
|
||||
// delete old apk if it exists
|
||||
const exists = await ReactNativeBlobUtil.fs.exists(apkPath);
|
||||
|
||||
if (exists) {
|
||||
const stat = await ReactNativeBlobUtil.fs.stat(apkPath);
|
||||
|
||||
// last modified time
|
||||
const lastModified = Number(stat.lastModified);
|
||||
|
||||
// current time
|
||||
const now = Date.now();
|
||||
|
||||
// 5 minutes in ms
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
|
||||
// skip download if file is fresh
|
||||
if (now - lastModified < fiveMinutes) {
|
||||
console.log("APK already downloaded recently, skipping.");
|
||||
|
||||
return {
|
||||
skipped: true,
|
||||
path: apkPath,
|
||||
};
|
||||
}
|
||||
|
||||
// delete old apk before redownload
|
||||
await ReactNativeBlobUtil.fs.unlink(apkPath);
|
||||
}
|
||||
|
||||
const res = await ReactNativeBlobUtil.config({
|
||||
addAndroidDownloads: {
|
||||
useDownloadManager: true,
|
||||
notification: true,
|
||||
title: "LST Mobile Update",
|
||||
description: "Downloading update for StageNow install",
|
||||
mime: "application/vnd.android.package-archive",
|
||||
path: apkPath,
|
||||
mediaScannable: true,
|
||||
},
|
||||
}).fetch("GET", url);
|
||||
|
||||
return res.path();
|
||||
}
|
||||
34
lstMobile/src/lib/socket.io.ts
Normal file
34
lstMobile/src/lib/socket.io.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { io, type Socket } from "socket.io-client";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
|
||||
let socket: Socket | null = null;
|
||||
|
||||
export function getSocket() {
|
||||
const { serverIp, serverPort } = useAppStore.getState();
|
||||
|
||||
//const port = Number(serverPort) >= 50000 ? "3000" : serverPort;
|
||||
const url = `http://${serverIp}:${serverPort}`;
|
||||
|
||||
if (!socket) {
|
||||
socket = io(url, {
|
||||
path: "/lst/api/socket.io",
|
||||
withCredentials: true,
|
||||
autoConnect: true,
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
export function connectSocket() {
|
||||
const socket = getSocket();
|
||||
|
||||
if (!socket.connected && socket.disconnected) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import axios from "axios";
|
||||
import Constants from "expo-constants";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
import { useServerStore } from "../hooks/useServerCheck";
|
||||
import { downloadLatestApk } from "./lastestVersion";
|
||||
|
||||
export type ServerVersionInfo = {
|
||||
packageName: string;
|
||||
@@ -60,13 +62,11 @@ export const versionCheck = async () => {
|
||||
setServerVersion(res.data);
|
||||
}
|
||||
|
||||
// const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||
|
||||
// if (build < res.data.minSupportedVersionCode) {
|
||||
// setStartupRoute("/updateScreen");
|
||||
// setReady(true);
|
||||
// return;
|
||||
// }
|
||||
if (build < res.data.versionCode) {
|
||||
await downloadLatestApk(serverIp, port);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Version check error:", error);
|
||||
}
|
||||
|
||||
14
migrations/0057_worthless_trish_tilby.sql
Normal file
14
migrations/0057_worthless_trish_tilby.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE "dock_door_scanners" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"ip" text NOT NULL,
|
||||
"name" text,
|
||||
"dock_id" text,
|
||||
"dock_name" text NOT NULL,
|
||||
"active" boolean DEFAULT true,
|
||||
"current_loading_order" text DEFAULT '',
|
||||
"add_date" timestamp DEFAULT now(),
|
||||
"add_user" text DEFAULT 'lst-system',
|
||||
"upd_date" timestamp DEFAULT now(),
|
||||
"upd_user" text DEFAULT 'lst-system',
|
||||
CONSTRAINT "dock_door_scanners_name_unique" UNIQUE("name")
|
||||
);
|
||||
1
migrations/0058_damp_donald_blake.sql
Normal file
1
migrations/0058_damp_donald_blake.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "dock_door_scanners" DROP COLUMN "dock_name";
|
||||
2609
migrations/meta/0057_snapshot.json
Normal file
2609
migrations/meta/0057_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2603
migrations/meta/0058_snapshot.json
Normal file
2603
migrations/meta/0058_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -400,6 +400,20 @@
|
||||
"when": 1779454561527,
|
||||
"tag": "0056_shallow_chimera",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 57,
|
||||
"version": "7",
|
||||
"when": 1779843750556,
|
||||
"tag": "0057_worthless_trish_tilby",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 58,
|
||||
"version": "7",
|
||||
"when": 1779846894283,
|
||||
"tag": "0058_damp_donald_blake",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user