Compare commits
24 Commits
v0.1.0-alp
...
f635415b75
| Author | SHA1 | Date | |
|---|---|---|---|
| f635415b75 | |||
| 40edbc3eae | |||
| 2558b2e5bb | |||
| 45a0dee9ca | |||
| bb7931d6c8 | |||
| 4f848bb649 | |||
| f8335f5217 | |||
| 2a35381fe4 | |||
| e6b92aeb10 | |||
| da87e2e1d3 | |||
| 3ef0f230dd | |||
| 78f7b8a179 | |||
| 1a3d8a7ddc | |||
| 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.
|
||||
19
README.md
19
README.md
@@ -7,7 +7,7 @@
|
||||
Quick summary of current rewrite/migration goal.
|
||||
|
||||
- **Phase:** Backend rewrite
|
||||
- **Last updated:** 2026-04-06
|
||||
- **Last updated:** 2026-05-27
|
||||
|
||||
---
|
||||
|
||||
@@ -17,20 +17,20 @@ Quick summary of current rewrite/migration goal.
|
||||
|----------|--------------|--------|
|
||||
| User Authentication | ~~Login~~, ~~Signup~~, API Key | 🟨 In Progress |
|
||||
| User Profile | ~~Edit profile~~, upload avatar | 🟨 In Progress |
|
||||
| User Admin | Edit user, create user, remove user, alplaprod user integration | ⏳ Not Started |
|
||||
| User Admin | ~~Edit user~~, ~~create user~~, remove user, alplaprod user integration | ⏳ Not Started |
|
||||
| Notifications | ~~Subscribe~~, ~~Create~~, ~~Update~~, ~~~~Remove~~, Manual Trigger | 🟨 In Progress |
|
||||
| Datamart | ~~Create~~, ~~Update~~, ~~Run~~, Deactivate | 🟨 In Progress |
|
||||
| Frontend | Analytics and charts | ⏳ Not Started |
|
||||
| Docs | Instructions and trouble shooting | ⏳ Not Started |
|
||||
| One Click Print | Get printers, monitor printers, label process, material process, Special processes | ⏳ Not Started |
|
||||
| One Click Print | ~~Get printers~~, monitor printers, label process, material process, Special processes | 🟨 In Progress |
|
||||
| Silo Adjustments | Create, History, Comments | ⏳ Not Started |
|
||||
| Demand Management | Orders, Forecast, Special Mappings, Create trucks, Load Trucks (tablet scanning) | ⏳ Not Started |
|
||||
| Open Docks | Integrations | ⏳ Not Started |
|
||||
| Open Docks | Integrations | 🟨 In Progress |
|
||||
| Transport Insight | Integrations | ⏳ Not Started |
|
||||
| Quality Request Tool | Add Pallet, Monitor for moved, status changes, alerts | ⏳ Not Started |
|
||||
| Logistics | Consume material, return and print, label info, relocate | ⏳ Not Started |
|
||||
| EOM | Endpoints, Report Pull for finance | ⏳ Not Started |
|
||||
| OCME | Custom integration | ⏳ Not Started |
|
||||
| ~~OCME~~ | ~~Custom integration~~ | Canceled |
|
||||
| API Migration | Moving to new REST endpoints | 🔧 In Progress |
|
||||
| System | Tests,Builds, Updates, Remote Logging, DB Backups, Alerting | ⏳ Not Started |
|
||||
|
||||
@@ -47,4 +47,13 @@ How to run the current version of the app.
|
||||
git clone https://git.tuffraid.net/cowch/lst_v3.git
|
||||
cd lst_v3
|
||||
npm install
|
||||
```
|
||||
|
||||
Rename the .env-example to .env
|
||||
|
||||
Update all the fields
|
||||
|
||||
```bash
|
||||
npm run dev:db:migrate
|
||||
npm run dev
|
||||
```
|
||||
@@ -46,7 +46,8 @@ const createApp = async () => {
|
||||
server: ${JSON.stringify(umamiConfig.server ?? "unknown")},
|
||||
appVersion: ${JSON.stringify(umamiConfig.appVersion ?? "dev")},
|
||||
umamiHost: ${JSON.stringify(umamiConfig.umamiHost ?? "")},
|
||||
umamiWebsiteId: ${JSON.stringify(umamiConfig.umamiWebsiteId ?? "")}
|
||||
umamiWebsiteId: ${JSON.stringify(umamiConfig.umamiWebsiteId ?? "")},
|
||||
timezone: ${JSON.stringify(process.env.TIMEZONE ?? "America/Chicago")}
|
||||
};
|
||||
`);
|
||||
});
|
||||
|
||||
0
backend/datamart/datamart.controller.test.ts
Normal file
0
backend/datamart/datamart.controller.test.ts
Normal file
68
backend/datamart/getDatamart.route.test.ts
Normal file
68
backend/datamart/getDatamart.route.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../db/db.controller.js", () => ({
|
||||
db: {},
|
||||
}));
|
||||
|
||||
vi.mock("../logger/logger.controller.js", () => ({
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./datamart.controller.js", () => ({
|
||||
runDatamartQuery: vi.fn(async ({ name, options }) => ({
|
||||
success: true,
|
||||
message: `Ran ${name}`,
|
||||
data: {
|
||||
name,
|
||||
options,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
import { runDatamartQuery } from "./datamart.controller.js";
|
||||
import getDatamartRoute from "./getDatamart.route.js";
|
||||
|
||||
function createTestApp() {
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
app.use("/datamart", getDatamartRoute);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
describe("GET /datamart/:name", () => {
|
||||
it("runs a datamart query by name and returns api response", async () => {
|
||||
const app = createTestApp();
|
||||
|
||||
const res = await request(app).get("/datamart/orders").query({
|
||||
value: "123",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
expect(runDatamartQuery).toHaveBeenCalledWith({
|
||||
name: "orders",
|
||||
options: {
|
||||
value: "123",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.body.success).toBe(true);
|
||||
expect(res.body.module).toBe("datamart");
|
||||
expect(res.body.subModule).toBe("query");
|
||||
expect(res.body.data).toEqual({
|
||||
name: "orders",
|
||||
options: {
|
||||
value: "123",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,15 +17,15 @@ export const alplaPurchaseHistory = pgTable("alpla_purchase_history", {
|
||||
status: integer("status"),
|
||||
statusText: text("status_text"),
|
||||
journalNum: integer("journal_num"),
|
||||
add_date: timestamp("add_date").defaultNow(),
|
||||
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
add_user: text("add_user"),
|
||||
upd_user: text("upd_user"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
remark: text("remark"),
|
||||
approvedStatus: text("approved_status").default("new"),
|
||||
position: jsonb("position").default([]),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const alplaPurchaseHistorySchema =
|
||||
|
||||
@@ -3,7 +3,9 @@ import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
export const analytics = pgTable("analytics", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
|
||||
method: text("method").notNull(),
|
||||
routePattern: text("route_pattern").notNull(),
|
||||
|
||||
@@ -16,13 +16,13 @@ export const jobAuditLog = pgTable(
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
jobName: text("job_name"),
|
||||
startedAt: timestamp("start_at"),
|
||||
finishedAt: timestamp("finished_at"),
|
||||
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
||||
durationMs: integer("duration_ms"),
|
||||
status: text("status"), //success | error
|
||||
errorMessage: text("error_message"),
|
||||
errorStack: text("error_stack"),
|
||||
metadata: jsonb("meta_data"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => {
|
||||
return {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const user = pgTable("user", {
|
||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
|
||||
@@ -6,5 +6,5 @@ export const deploymentHistory = pgTable("deployment_history", {
|
||||
buildNumber: integer("build_number").notNull(),
|
||||
status: text("status").notNull(), // started, success, failed
|
||||
message: text("message"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
@@ -28,11 +28,15 @@ export const analyticsDaily = pgTable(
|
||||
avgDurationMs: integer("avg_duration_ms").notNull(),
|
||||
maxDurationMs: integer("max_duration_ms").notNull(),
|
||||
|
||||
firstHitAt: timestamp("first_hit_at").notNull(),
|
||||
lastHitAt: timestamp("last_hit_at").notNull(),
|
||||
firstHitAt: timestamp("first_hit_at", { withTimezone: true }).notNull(),
|
||||
lastHitAt: timestamp("last_hit_at", { withTimezone: true }).notNull(),
|
||||
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
unique("analytics_daily_business_route_unique").on(
|
||||
|
||||
@@ -18,9 +18,9 @@ export const datamart = pgTable("datamart", {
|
||||
active: boolean("active").default(true),
|
||||
options: text("options").default(""),
|
||||
public: boolean("public_access").default(false),
|
||||
add_date: timestamp("add_date").defaultNow(),
|
||||
add_date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
add_user: text("add_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
});
|
||||
|
||||
|
||||
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", { withTimezone: true }).defaultNow(),
|
||||
add_user: text("add_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).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>;
|
||||
@@ -20,7 +20,7 @@ export const invHistoricalData = pgTable("inv_historical_data", {
|
||||
whseId: text("whse_id").default(""),
|
||||
whseName: text("whse_name").default("missing whseName"),
|
||||
upd_user: text("upd_user").default("lst-system"),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const invHistoricalDataSchema = createSelectSchema(invHistoricalData);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const logs = pgTable("logs", {
|
||||
stack: jsonb("stack").default([]),
|
||||
checked: boolean("checked").default(false),
|
||||
hostname: text("hostname"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const logSchema = createSelectSchema(logs);
|
||||
|
||||
@@ -17,8 +17,12 @@ export const opendockApt = pgTable(
|
||||
release: integer("release").notNull().unique("opendock_apt_release_unique"),
|
||||
openDockAptId: text("open_dock_apt_id").notNull(),
|
||||
appointment: jsonb("appointment").notNull().default([]),
|
||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
openDockAptIdIdx: index("opendock_apt_opendock_id_idx").on(
|
||||
|
||||
@@ -22,9 +22,13 @@ export const opendockArticleSetup = pgTable(
|
||||
customerDescription: text("customer_description").notNull(),
|
||||
loadType: loadTypeEnum("load_type").notNull().default("drop"),
|
||||
dock: text("dock").notNull(),
|
||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
add_user: text("add_user").notNull().default("lst-system"),
|
||||
},
|
||||
(table) => ({
|
||||
|
||||
@@ -6,9 +6,13 @@ export const opendockDockSetup = pgTable("opendock_dock_setup", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
dockID: text("dock_id").notNull(),
|
||||
upd_date: timestamp("upd_date").notNull().defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
upd_user: text("upd_user").notNull().default("lst-system"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
add_user: text("add_user").notNull().default("lst-system"),
|
||||
});
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ export const printerLog = pgTable("printer_log", {
|
||||
printerSN: text("printer_sn"),
|
||||
condition: text("condition").notNull(),
|
||||
message: text("message"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
@@ -28,8 +28,8 @@ export const printerData = pgTable(
|
||||
printDelay: integer("printDelay").default(90),
|
||||
processes: jsonb("processes").default([]),
|
||||
printDelayOverride: boolean("print_delay_override").default(false), // this will be more for if we have the lot time active but want to over ride this single line for some reason
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
//uniqueIndex("emailUniqueIndex").on(sql`lower(${table.email})`),
|
||||
|
||||
@@ -30,8 +30,8 @@ export const scanUser = pgTable(
|
||||
role: mobileRoleEnum("role").notNull().default("user"),
|
||||
active: boolean("active").default(true),
|
||||
lastScan: timestamp("last_scan").defaultNow(),
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
userNotificationUnique: unique("scan_user_unique").on(
|
||||
|
||||
@@ -13,7 +13,7 @@ export const scanLog = pgTable("scan_log", {
|
||||
status: text("status"),
|
||||
scannerVersion: text("scanner_version").default("0"),
|
||||
lines: jsonb("lines").default([]),
|
||||
add_Date: timestamp("add_date").defaultNow(),
|
||||
add_Date: timestamp("add_date", { withTimezone: true }).defaultNow(),
|
||||
});
|
||||
|
||||
export const scanLogSchema = createSelectSchema(scanLog);
|
||||
|
||||
@@ -22,7 +22,7 @@ export const serverData = pgTable(
|
||||
contactPhone: text("contact_phone"),
|
||||
active: boolean("active").default(true),
|
||||
serverLoc: text("server_loc"),
|
||||
lastUpdated: timestamp("last_updated").defaultNow(),
|
||||
lastUpdated: timestamp("last_updated", { withTimezone: true }).defaultNow(),
|
||||
buildNumber: integer("build_number"),
|
||||
isUpgrading: boolean("is_upgrading").default(false),
|
||||
},
|
||||
|
||||
@@ -32,9 +32,9 @@ export const settings = pgTable(
|
||||
settingType: settingType(),
|
||||
seedVersion: integer("seed_version").default(1), // this is intended for if we want to update the settings.
|
||||
add_User: text("add_User").default("LST_System").notNull(),
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
add_Date: timestamp("add_Date", { withTimezone: true }).defaultNow(),
|
||||
upd_user: text("upd_User").default("LST_System").notNull(),
|
||||
upd_date: timestamp("upd_date").defaultNow(),
|
||||
upd_date: timestamp("upd_date", { withTimezone: true }).defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
|
||||
|
||||
@@ -12,11 +12,11 @@ import type z from "zod";
|
||||
export const appStats = pgTable("app_stats", {
|
||||
id: text("id").primaryKey().default("primary"),
|
||||
currentBuild: integer("current_build").notNull().default(1),
|
||||
lastBuildAt: timestamp("last_build_at"),
|
||||
lastDeployAt: timestamp("last_deploy_at"),
|
||||
lastBuildAt: timestamp("last_build_at", { withTimezone: true }),
|
||||
lastDeployAt: timestamp("last_deploy_at", { withTimezone: true }),
|
||||
building: boolean("building").notNull().default(false),
|
||||
updating: boolean("updating").notNull().default(false),
|
||||
lastUpdated: timestamp("last_updated").defaultNow(),
|
||||
lastUpdated: timestamp("last_updated", { withTimezone: true }).defaultNow(),
|
||||
meta: jsonb("meta").$type<Record<string, unknown>>().default({}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
70
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
70
backend/dockdoorScanning/dockdoor.closeLoadingOrder.route.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { eq, 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 endLoading = z.object({
|
||||
loadingOrder: z.string(),
|
||||
dockId: z.string(),
|
||||
});
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
// close the loading order
|
||||
|
||||
// clear the loading order off the dock
|
||||
|
||||
try {
|
||||
const validated = endLoading.parse(req.body);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(dockDoorScanners)
|
||||
.set({
|
||||
currentLoadingOrder: "",
|
||||
upd_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, validated.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 ${validated.loadingOrder} was just closed.`,
|
||||
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;
|
||||
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
25
backend/dockdoorScanning/dockdoor.loadUnits.route.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import loadUnit from "./dockdoor.loadUnits.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
const unit = await loadUnit({
|
||||
dockId: req.body.dockId,
|
||||
runningNo: req.body.runningNo,
|
||||
});
|
||||
|
||||
return apiReturn(res, {
|
||||
success: unit.success,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadingUnit",
|
||||
message: unit.message,
|
||||
data: unit?.data ?? [],
|
||||
status: unit.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
143
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
143
backend/dockdoorScanning/dockdoor.loadUnits.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 { emitToRoom } from "../socket.io/roomEmitter.socket.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;
|
||||
runningNo?: string;
|
||||
};
|
||||
|
||||
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 unitToScan = data.sscc
|
||||
? { sscc: data.sscc?.slice(2) }
|
||||
: { runningNo: Number(data.runningNo) };
|
||||
|
||||
const prod = (await runProdApi({
|
||||
method: "post",
|
||||
endpoint: `/public/v1.0/OutboundDeliveries/LoadingOrders/${dock[0]?.currentLoadingOrder}/LoadUnit`,
|
||||
data: [unitToScan],
|
||||
})) as any;
|
||||
|
||||
//emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data ?? []);
|
||||
|
||||
if (!prod?.success) {
|
||||
emitToRoom(`dockDoorLoading:${data.dockId}`, prod?.data.errors[0]);
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Unit encountered an error while loading`,
|
||||
data: prod?.data.errors[0] as any,
|
||||
notify: false,
|
||||
//room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
} else {
|
||||
const emitData = {
|
||||
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||
data: prod.data,
|
||||
code: 0,
|
||||
};
|
||||
emitToRoom(`dockDoorLoading:${data.dockId}`, emitData as any);
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Unit added to loading order`,
|
||||
data: [
|
||||
{
|
||||
message: `The unit ${prod.data.message.messageParams.runningNo} was loaded.`,
|
||||
data: prod.data,
|
||||
code: 0,
|
||||
},
|
||||
] as any,
|
||||
notify: false,
|
||||
//room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return returnFunc({
|
||||
success: true,
|
||||
level: "error",
|
||||
module: "dockdoor",
|
||||
subModule: "loadUnit",
|
||||
message: `Failed to load unit`,
|
||||
data: error as any,
|
||||
notify: false,
|
||||
room: `dockDoorLoading:${data.dockId}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default loadUnit;
|
||||
48
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
48
backend/dockdoorScanning/dockdoor.routes.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
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 load from "./dockdoor.loadUnits.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/finishOrder`,
|
||||
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,
|
||||
);
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/api/dockDoor/loadUnit`,
|
||||
featureCheck("dockDoorScanning"),
|
||||
load,
|
||||
);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
66
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
66
backend/dockdoorScanning/dockdoor.startLoad.route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { eq, 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,
|
||||
})
|
||||
.where(eq(dockDoorScanners.dockId, validated.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 ${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;
|
||||
@@ -37,7 +37,7 @@ const dbStream = new Writable({
|
||||
subModule: obj?.subModule?.toLowerCase(),
|
||||
hostname: obj?.hostname?.toLowerCase(),
|
||||
message: obj.msg,
|
||||
stack: obj?.stack,
|
||||
stack: obj?.stack || obj?.error, // this will add in the error or stack depending on how we pass it.
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ const historicalInvImport = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.length === 0) {
|
||||
if (data.length === 0) {
|
||||
const avSQLQuery = sqlQuerySelector(`datamart.activeArticles`) as SqlQuery;
|
||||
|
||||
if (!avSQLQuery.success) {
|
||||
@@ -139,7 +139,7 @@ const historicalInvImport = async () => {
|
||||
subModule: "inv",
|
||||
message: `Error adding historical data to lst db`,
|
||||
data: errorImport as any,
|
||||
notify: true,
|
||||
notify: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ type Releases = {
|
||||
DeliveryAddressHumanReadableId: string;
|
||||
AdditionalInformation1: string;
|
||||
};
|
||||
|
||||
// TODO: add these docs into the db
|
||||
const actaulDocks = [
|
||||
{ name: "cermac", dockId: "bcb17fae-0b1a-47a7-9fbf-594c5ebccce9" },
|
||||
{ name: "matrix", dockId: "3e32cbfc-49f4-4138-b491-9d5df9c94754" },
|
||||
{ name: "gerber", dockId: "9109e789-6c15-4cd9-87cb-de1b18627b6d" },
|
||||
{ name: "rb", dockId: "6be02526-6183-4789-a73f-e0aa155e6d1e" },
|
||||
];
|
||||
const timeZone = process.env.TIMEZONE as string;
|
||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
@@ -64,6 +72,7 @@ let lastCheck = formatInTimeZone(
|
||||
// };
|
||||
|
||||
const postRelease = async (release: Releases) => {
|
||||
log.debug({}, `Release: ${release.ReleaseNumber} is about to be validated`);
|
||||
if (!odToken.odToken) {
|
||||
log.info({}, "Getting Auth Token");
|
||||
await getToken();
|
||||
@@ -82,21 +91,55 @@ const postRelease = async (release: Releases) => {
|
||||
where: (u, { eq }) => eq(u.name, "defaultLoadType"),
|
||||
});
|
||||
|
||||
// check if the release has the data in it
|
||||
// check if the release has the loadtype in it
|
||||
const releaseLoadtypeCheck = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes("drop");
|
||||
|
||||
// allowed to schedule now, as long as we see od in here somewhere
|
||||
const releaseOkToSchedule = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes("od");
|
||||
|
||||
// dock was sent over
|
||||
const releaseDockInfo = actaulDocks.some((dock) =>
|
||||
(release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.includes(dock.name.toLowerCase()),
|
||||
);
|
||||
|
||||
const opendDockArticleCheck = await db.query.opendockArticleSetup.findFirst({
|
||||
where: (table, { and, eq }) =>
|
||||
and(
|
||||
eq(table.av, release.LineItemArticleWeight),
|
||||
eq(table.av, release.LineItemHumanReadableId),
|
||||
eq(table.customer, release.DeliveryAddressHumanReadableId),
|
||||
),
|
||||
});
|
||||
|
||||
// selected dock
|
||||
const releaseDocks = (release.AdditionalInformation1 ?? "")
|
||||
.toLowerCase()
|
||||
.split(",")
|
||||
.map((x) => x.trim());
|
||||
|
||||
const matchedDock = actaulDocks.find((dock) =>
|
||||
releaseDocks.includes(dock.name.toLowerCase()),
|
||||
);
|
||||
|
||||
const setDock =
|
||||
// validate we dont have the dock in the release
|
||||
releaseDockInfo
|
||||
? matchedDock?.dockId
|
||||
: // validate we dont have the dock in the aritcle check
|
||||
(actaulDocks.find((d) => d.name === opendDockArticleCheck?.dock)
|
||||
?.dockId ?? process.env.DEFAULT_DOCK);
|
||||
|
||||
// TODO: add in docks from lst db here to make it more universal for the team
|
||||
/**
|
||||
* ReleaseState
|
||||
@@ -127,7 +170,7 @@ const postRelease = async (release: Releases) => {
|
||||
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
||||
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
||||
// TODO: look in the remarks in the release and if its says
|
||||
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||
dockId: setDock, // this the warehouse we want it in to start out
|
||||
refNumbers: [release.ReleaseNumber],
|
||||
//refNumber: release.ReleaseNumber,
|
||||
start: release.DeliveryDate,
|
||||
@@ -403,7 +446,12 @@ const postRelease = async (release: Releases) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (
|
||||
(releaseLoadtypeCheck ||
|
||||
opendDockArticleCheck?.loadType === "drop" ||
|
||||
defaultDock?.value === "drop") &&
|
||||
releaseOkToSchedule
|
||||
) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${process.env.OPENDOCK_URL}/appointment`,
|
||||
@@ -458,6 +506,76 @@ const postRelease = async (release: Releases) => {
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// try {
|
||||
// const response = await axios.post(
|
||||
// `${process.env.OPENDOCK_URL}/appointment`,
|
||||
// newDockApt,
|
||||
// {
|
||||
// headers: {
|
||||
// "content-type": "application/json; charset=utf-8",
|
||||
// Authorization: `Bearer ${odToken.odToken}`,
|
||||
// },
|
||||
// },
|
||||
// );
|
||||
|
||||
// // we need the id,release#,status from this response, store it in lst, check if we have a release so we can just update it.
|
||||
// // this will be utilized when we are listening for the changes to the apts. that way we can update the state to arrived. we will run our own checks on this guy during the incoming messages.
|
||||
|
||||
// if (response.status === 400) {
|
||||
// log.error({}, response.data.data.message);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // the response to make it simple we want response.data.id, response.data.relNumber, status will be defaulted to Scheduled if we created it here.
|
||||
// // TODO: add this release data to our db. but save it in json format and well parse it out. that way we future proof it and have everything in here vs just a few things
|
||||
// //console.info(response.data.data, "Was Created");
|
||||
// try {
|
||||
// await db
|
||||
// .insert(opendockApt)
|
||||
// .values({
|
||||
// release: release.ReleaseNumber,
|
||||
// openDockAptId: response.data.data.id,
|
||||
// appointment: response.data.data,
|
||||
// })
|
||||
// .onConflictDoUpdate({
|
||||
// target: opendockApt.release,
|
||||
// set: {
|
||||
// openDockAptId: response.data.data.id,
|
||||
// appointment: response.data.data,
|
||||
// upd_date: sql`NOW()`,
|
||||
// },
|
||||
// })
|
||||
// .returning();
|
||||
|
||||
// log.info({}, `${release.ReleaseNumber} was created`);
|
||||
// } catch (e) {
|
||||
// log.error({ stack: e }, "Error creating new release");
|
||||
// }
|
||||
// // biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
// } catch (e: any) {
|
||||
// log.error(
|
||||
// { stack: e?.response?.data },
|
||||
// `Error posting new release to opendock, ${release.ReleaseNumber}`,
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
log.info(
|
||||
{
|
||||
stack: {
|
||||
release: release.ReleaseNumber,
|
||||
releaseLoadtypeCheck,
|
||||
articleLoadType: opendDockArticleCheck?.loadType,
|
||||
defaultLoadType: defaultDock?.value,
|
||||
releaseOkToSchedule,
|
||||
},
|
||||
},
|
||||
`Skipping OpenDock post - release: ${release.ReleaseNumber} is not allowed to schedule`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await delay(750); // rate limit protection
|
||||
|
||||
@@ -1,11 +1,80 @@
|
||||
SELECT count(*) as activated
|
||||
FROM [test1_AlplaPROD2.0_Read].[support].[FeatureActivation]
|
||||
|
||||
where feature in (108,7)
|
||||
where feature in (7)
|
||||
|
||||
|
||||
/*
|
||||
as more features get activated and need to have this checked to include the new endpoints add here so we can check this.
|
||||
108 = waste
|
||||
7 = warehousing
|
||||
[DefaultTranslation("Blocking")]
|
||||
Blocking = 1,
|
||||
|
||||
[DefaultTranslation("Users")]
|
||||
UserManagement = 2,
|
||||
|
||||
[DefaultTranslation("Complaint Handling")]
|
||||
ComplaintHandling = 3,
|
||||
|
||||
[DefaultTranslation("Demand Management")]
|
||||
DemandManagement = 4,
|
||||
|
||||
[DefaultTranslation("Issue Material")]
|
||||
IssueMaterial = 5,
|
||||
|
||||
[DefaultTranslation("Production Controlling")]
|
||||
ProductionControlling = 6,
|
||||
|
||||
[DefaultTranslation("Warehousing")]
|
||||
Warehousing = 7,
|
||||
|
||||
[DefaultTranslation("Outbound Deliveries")]
|
||||
OutboundDeliveries = 8,
|
||||
|
||||
[DefaultTranslation("Production Scheduling")]
|
||||
ProductionScheduling = 9,
|
||||
|
||||
[DefaultTranslation("Advanced Scheduling")]
|
||||
AdvancedScheduling = 10,
|
||||
|
||||
[DefaultTranslation("Material Requirements Planning")]
|
||||
MaterialRequirementsPlanning = 11,
|
||||
|
||||
[DefaultTranslation("Production Labelling")]
|
||||
ProductionLabelling = 12,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Accounting")]
|
||||
Accounting = 100,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Irradiation")]
|
||||
Irradiation = 101,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Central Moulds")]
|
||||
CentralMoulds = 102,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Maintenance")]
|
||||
Maintenance = 103,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Disable Manual Bookings")]
|
||||
DisableManualBookings = 104,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Purchasing")]
|
||||
Purchasing = 105,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("Tracing")]
|
||||
Tracing = 106,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("AlplaERP (D365)")]
|
||||
AlplaErp = 107,
|
||||
|
||||
[SpecialProcess]
|
||||
[DefaultTranslation("AI chatbot")]
|
||||
AiChatBot = 108
|
||||
*/
|
||||
|
||||
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,57 @@
|
||||
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"
|
||||
| "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) => {
|
||||
@@ -48,4 +88,22 @@ 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;
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: add in dynamic room seeding
|
||||
"dockDoorLoading:2": {
|
||||
seed: async (limit) => {
|
||||
console.log(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,23 @@ export const startTCPServer = async () => {
|
||||
|
||||
printerListen(printerData as PrinterData);
|
||||
}
|
||||
|
||||
// check if its a dock door scanner
|
||||
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", () => {
|
||||
|
||||
@@ -13,7 +13,14 @@ import {
|
||||
//import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import * as rawSchema from "../db/schema/auth.schema.js";
|
||||
import { ac, admin, manager, systemAdmin, user } from "./auth.permissions.js";
|
||||
import {
|
||||
ac,
|
||||
admin,
|
||||
manager,
|
||||
systemAdmin,
|
||||
transport,
|
||||
user,
|
||||
} from "./auth.permissions.js";
|
||||
import { allowedOrigins } from "./cors.utils.js";
|
||||
import { sendEmail } from "./sendEmail.utils.js";
|
||||
|
||||
@@ -164,6 +171,7 @@ export const auth = betterAuth({
|
||||
admin,
|
||||
user,
|
||||
manager,
|
||||
transport,
|
||||
systemAdmin,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
49
frontend/src/components/ui/badge.tsx
Normal file
49
frontend/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Slot } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||
destructive:
|
||||
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
||||
outline:
|
||||
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||
ghost:
|
||||
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
data-variant={variant}
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -11,9 +11,12 @@ type RoomErrorPayload = {
|
||||
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(
|
||||
@@ -44,7 +47,13 @@ export function useSocketRoom<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("");
|
||||
}
|
||||
|
||||
@@ -53,14 +62,14 @@ export function useSocketRoom<T>(
|
||||
setInfo(err.message ?? "Room error");
|
||||
}
|
||||
|
||||
if (!socket.connected) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -74,7 +83,18 @@ export function useSocketRoom<T>(
|
||||
socket.off("room-update", handleUpdate);
|
||||
socket.off("room-error", handleError);
|
||||
};
|
||||
}, [roomId]);
|
||||
}, [roomId, updateMode]);
|
||||
|
||||
return { data, info, clearRoom };
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const isDockDoorPage = location.pathname.startsWith("/dockdoor");
|
||||
|
||||
useSocketRoom(
|
||||
dockId ? `dockdoor:${dockId}` : null,
|
||||
isDockDoorPage,
|
||||
);
|
||||
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ type RuntimeConfig = {
|
||||
appVersion: string;
|
||||
umamiHost: string;
|
||||
umamiWebsiteId: string;
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
declare global {
|
||||
@@ -23,6 +24,7 @@ export const runtimeConfig: RuntimeConfig = {
|
||||
appVersion: window.LST_CONFIG?.appVersion ?? "dev",
|
||||
umamiHost: window.LST_CONFIG?.umamiHost ?? "",
|
||||
umamiWebsiteId: window.LST_CONFIG?.umamiWebsiteId ?? "",
|
||||
timezone: window.LST_CONFIG?.timezone ?? "America/Chicago",
|
||||
};
|
||||
|
||||
export function loadUmami() {
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { useSocketRoom } from "@/hooks/socket.io.hook";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "../../components/ui/tooltip";
|
||||
import LstTable from "../../lib/tableStuff/LstTable";
|
||||
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||
|
||||
export const Route = createFileRoute("/admin/logs")({
|
||||
beforeLoad: async ({ location }) => {
|
||||
@@ -37,117 +47,168 @@ interface LogEntry {
|
||||
[key: string]: any; // catch any extra fields
|
||||
}
|
||||
|
||||
function LevelBadge({ level }: { level: number }) {
|
||||
const config: Record<number, { label: string; className: string }> = {
|
||||
10: { label: "TRACE", className: "bg-gray-100 text-gray-600" },
|
||||
20: { label: "DEBUG", className: "bg-blue-100 text-blue-700" },
|
||||
30: { label: "INFO", className: "bg-green-100 text-green-700" },
|
||||
40: { label: "WARN", className: "bg-yellow-100 text-yellow-700" },
|
||||
50: { label: "ERROR", className: "bg-red-100 text-red-700" },
|
||||
60: { label: "FATAL", className: "bg-purple-100 text-purple-700" },
|
||||
function LevelBadge({ level }: { level: string }) {
|
||||
const config: Record<string, { label: string; className: string }> = {
|
||||
trace: { label: "TRACE", className: "bg-gray-100 text-gray-600" },
|
||||
debug: { label: "DEBUG", className: "bg-blue-50 text-blue-700" },
|
||||
info: { label: "INFO", className: "bg-green-50 text-green-700" },
|
||||
warn: { label: "WARN", className: "bg-yellow-100 text-yellow-700" },
|
||||
error: { label: "ERROR", className: "bg-red-50 text-red-700" },
|
||||
fatal: { label: "FATAL", className: "bg-purple-100 text-purple-700" },
|
||||
};
|
||||
|
||||
const { label, className } = config[level] ?? {
|
||||
label: String(level),
|
||||
className: "bg-gray-100",
|
||||
};
|
||||
const badge = config[level];
|
||||
|
||||
return (
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${className}`}>
|
||||
{label}
|
||||
</span>
|
||||
<Badge
|
||||
className={`rounded px-2 py-0.5 text-xs font-medium ${
|
||||
badge?.className ?? "bg-gray-100 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{badge?.label ?? level}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: logs, info: logsInfo } = useSocketRoom<LogEntry>("logs");
|
||||
//const { user } = Route.useRouteContext();
|
||||
//const router = useRouter();
|
||||
// const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
// const [logsInfo, setLogInfo] = useState(
|
||||
// "No logs yet — join the room to start receiving",
|
||||
// );
|
||||
const { data: logs } = useSocketRoom<LogEntry>("logs");
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
|
||||
// useEffect(() => {
|
||||
// // Connect if not already connected
|
||||
// if (!socket.connected) {
|
||||
// socket.connect();
|
||||
// }
|
||||
|
||||
// socket.on("connect", () => {
|
||||
// socket.emit("join-room", "logs");
|
||||
// });
|
||||
|
||||
// socket.emit("join-room", "logs");
|
||||
// socket.on(
|
||||
// "room-update",
|
||||
// (data: { payloads: LogEntry[]; roomId: string }) => {
|
||||
// setLogs((prev) => [...data.payloads, ...prev]);
|
||||
// },
|
||||
// );
|
||||
|
||||
// socket.on("room-error", (data) => {
|
||||
// setLogInfo(data.message);
|
||||
// });
|
||||
|
||||
// // socket.on("logs", (data) => {
|
||||
// // console.log(data);
|
||||
// // setLogs((prev) => [...data.payloads, ...prev]);
|
||||
// // });
|
||||
|
||||
// // Cleanup listeners on unmount
|
||||
// return () => {
|
||||
// socket.emit("leave-room", "logs");
|
||||
// socket.off("room-update");
|
||||
// socket.off("room-error");
|
||||
// socket.off("logs");
|
||||
// };
|
||||
// }, []);
|
||||
return (
|
||||
<div>
|
||||
{/* Log Table */}
|
||||
<div className="rounded border overflow-auto max-h-[600px]">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted sticky top-0">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2">Time</th>
|
||||
<th className="text-left px-3 py-2">Level</th>
|
||||
<th className="text-left px-3 py-2">Module</th>
|
||||
<th className="text-left px-3 py-2">Host</th>
|
||||
<th className="text-left px-3 py-2">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{logs.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
className="text-center py-6 text-muted-foreground"
|
||||
>
|
||||
{logsInfo}
|
||||
</td>
|
||||
</tr>
|
||||
const column = [
|
||||
columnHelper.accessor("createdAt", {
|
||||
header: ({ column }) => <SearchableHeader column={column} title="Time" />,
|
||||
filterFn: "includesString",
|
||||
cell: (i) =>
|
||||
formatInTimeZone(
|
||||
i.getValue(),
|
||||
`${window.LST_CONFIG?.timezone}`,
|
||||
"MM/dd/yyyy HH:mm:ss",
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("level", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Level" searchable={true} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <LevelBadge level={i.getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("module", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Module" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("subModule", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Submodule" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("hostname", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Client" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("message", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Message" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => {
|
||||
return i.getValue().length > 50 ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<p>{i.getValue().slice(0, 50)}...</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-wrap max-w-48">{i.getValue()}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
logs.map((log, i) => (
|
||||
<tr
|
||||
key={`${log.id}-${i}`}
|
||||
className="border-t hover:bg-muted/50"
|
||||
i.getValue()
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("stack", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Stack" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => {
|
||||
const stack = i.row.original.stack;
|
||||
if (!stack) return <span className="text-muted-foreground">—</span>;
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<p className="max-w-[350px] truncate text-left font-mono text-xs text-muted-foreground hover:text-foreground">
|
||||
{stack.length !== 0 ? "Hover to see stack error" : "-"}
|
||||
</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="max-w-[900px] bg-zinc-950 text-zinc-50"
|
||||
>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
{new Date(log.createdAt).toLocaleTimeString()}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<LevelBadge level={log.level} />
|
||||
</td>
|
||||
<td className="px-3 py-2">{log.module}</td>
|
||||
<td className="px-3 py-2">{log.hostname}</td>
|
||||
<td className="px-3 py-2 max-w-sm truncate">{log.message}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="max-h-[500px] overflow-auto whitespace-pre-wrap rounded-md p-3 text-xs leading-relaxed">
|
||||
<code>{JSON.stringify(stack, null, 2)}</code>
|
||||
</pre>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
}),
|
||||
];
|
||||
return (
|
||||
// <div>
|
||||
// {/* Log Table */}
|
||||
// <div className="rounded border overflow-auto max-h-[600px]">
|
||||
// <table className="w-full text-sm">
|
||||
// <thead className="bg-muted sticky top-0">
|
||||
// <tr>
|
||||
// <th className="text-left px-3 py-2">Time</th>
|
||||
// <th className="text-left px-3 py-2">Level</th>
|
||||
// <th className="text-left px-3 py-2">Module</th>
|
||||
// <th className="text-left px-3 py-2">Host</th>
|
||||
// <th className="text-left px-3 py-2">Message</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {logs.length === 0 ? (
|
||||
// <tr>
|
||||
// <td
|
||||
// colSpan={6}
|
||||
// className="text-center py-6 text-muted-foreground"
|
||||
// >
|
||||
// {logsInfo}
|
||||
// </td>
|
||||
// </tr>
|
||||
// ) : (
|
||||
// logs.map((log, i) => (
|
||||
// <tr
|
||||
// key={`${log.id}-${i}`}
|
||||
// className="border-t hover:bg-muted/50"
|
||||
// >
|
||||
// <td className="px-3 py-2 whitespace-nowrap">
|
||||
// {new Date(log.createdAt).toLocaleTimeString()}
|
||||
// </td>
|
||||
// <td className="px-3 py-2">
|
||||
// <LevelBadge level={log.level} />
|
||||
// </td>
|
||||
// <td className="px-3 py-2">{log.module}</td>
|
||||
// <td className="px-3 py-2">{log.hostname}</td>
|
||||
// <td className="px-3 py-2 max-w-sm truncate">{log.message}</td>
|
||||
// </tr>
|
||||
// ))
|
||||
// )}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// </div>
|
||||
<LstTable data={logs} columns={column} pageSize={50} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import z from "zod";
|
||||
|
||||
import { useSession } from "../lib/auth-client";
|
||||
import { trackLstEvent } from "../lib/umami.utils";
|
||||
|
||||
|
||||
@@ -132,6 +132,10 @@ export default function NewArticleLink({ refetch }: { refetch: any }) {
|
||||
label: "Matrix",
|
||||
value: "matrix",
|
||||
},
|
||||
{
|
||||
label: "RB",
|
||||
value: "rb",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -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": 42,
|
||||
"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",
|
||||
|
||||
104
lstMobile/package-lock.json
generated
104
lstMobile/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@rn-primitives/portal": "^1.4.0",
|
||||
"@rn-primitives/separator": "^1.4.0",
|
||||
"@rn-primitives/slot": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.99.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"axios": "^1.15.0",
|
||||
"babel-preset-expo": "^55.0.18",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -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",
|
||||
@@ -5316,9 +5318,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.100.9",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.9.tgz",
|
||||
"integrity": "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ==",
|
||||
"version": "5.100.14",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz",
|
||||
"integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -5326,12 +5328,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.100.9",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.9.tgz",
|
||||
"integrity": "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A==",
|
||||
"version": "5.100.14",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz",
|
||||
"integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.100.9"
|
||||
"@tanstack/query-core": "5.100.14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -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",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@rn-primitives/portal": "^1.4.0",
|
||||
"@rn-primitives/separator": "^1.4.0",
|
||||
"@rn-primitives/slot": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.99.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"axios": "^1.15.0",
|
||||
"babel-preset-expo": "^55.0.18",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -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("/");
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,134 @@
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import * as Device from "expo-device";
|
||||
import { Link } from "expo-router";
|
||||
import {
|
||||
Button,
|
||||
ScrollView,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function LaneCheck() {
|
||||
const getInfo = async () => {
|
||||
const info = "ho";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { getActiveLoadingOrders } from "../../lib/queryStuff/getActiveLoadingOrders";
|
||||
import { getDocks } from "../../lib/queryStuff/getDocks";
|
||||
|
||||
console.log(info);
|
||||
export default function DockScan() {
|
||||
const { data } = useSuspenseQuery(getDocks());
|
||||
const {
|
||||
data: loadingOrders,
|
||||
refetch,
|
||||
isLoading,
|
||||
} = useSuspenseQuery(getActiveLoadingOrders());
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet =
|
||||
Device.modelName?.toLowerCase().includes("et40") ||
|
||||
Device.modelName?.toLowerCase().includes("et45");
|
||||
|
||||
const columns = isTablet ? 3 : 1;
|
||||
|
||||
const gap = 8;
|
||||
const cardWidth =
|
||||
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||
|
||||
const updateLoadingOrders = () => {
|
||||
refetch();
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: `Refreshing Loading Orders`,
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading...</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<View className="flex">
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
// flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
<Text>Dock Scanning</Text>
|
||||
<Button onPress={getInfo}>
|
||||
<Text>Check info</Text>
|
||||
</Button>
|
||||
<Text className="text-2xl text-bold">Dock Scanning</Text>
|
||||
<Button title="Update Loading Orders" onPress={updateLoadingOrders} />
|
||||
</View>
|
||||
<View>
|
||||
<SafeAreaView className="flex">
|
||||
<ScrollView className="w-full">
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{data.map((i: any) => {
|
||||
const loadingPlan =
|
||||
i.currentLoadingOrder !== ""
|
||||
? loadingOrders.filter(
|
||||
(x: any) => x.id === Number(i.currentLoadingOrder),
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<View key={i.id}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: "/dock/[id]",
|
||||
params: {
|
||||
id: i.dockId.toString(),
|
||||
currentLoading: i.currentLoadingOrder,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Text>{i.name}</Text>
|
||||
{i.currentLoadingOrder === "" ? (
|
||||
<Text>Tap to active new loading order</Text>
|
||||
) : (
|
||||
<View>
|
||||
<Text>
|
||||
Current Loading order : {i.currentLoadingOrder}
|
||||
</Text>
|
||||
{loadingPlan && loadingPlan.length > 0 && (
|
||||
<View>
|
||||
<Text>
|
||||
{`${loadingPlan[0].loadingPlanItems[0].articleId} - ${loadingPlan[0].loadingPlanItems[0].articleDescription}`}
|
||||
</Text>
|
||||
<Text>
|
||||
Current Loaded :{" "}
|
||||
{
|
||||
loadingPlan[0].loadingPlanItems[0]
|
||||
.loadedQuantityLUs
|
||||
}{" "}
|
||||
/{" "}
|
||||
{
|
||||
loadingPlan[0].loadingPlanItems[0]
|
||||
.plannedQuantityLUs
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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,154 +1,81 @@
|
||||
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 sortDir === "asc" ? aDate - bDate : bDate - aDate;
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.remove();
|
||||
zebraScanner.stopListening();
|
||||
//setUnits(null);
|
||||
};
|
||||
}, [handleScan]),
|
||||
);
|
||||
}, [items, sortDir]);
|
||||
|
||||
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 className="flex items-center mt-2">
|
||||
<View className="flex m-2">
|
||||
<Button
|
||||
onPress={() =>
|
||||
setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))
|
||||
}
|
||||
title={`Sort: ${sortDir}`}
|
||||
/>
|
||||
</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>
|
||||
{sortedItems.length === 0 ? (
|
||||
<View className="flex items-center">
|
||||
<Text>Loading PPOO...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<SafeAreaView className="flex">
|
||||
<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="w-full"
|
||||
//className={isTablet ? "w-[32%]" : "w-full"}
|
||||
style={{
|
||||
borderColor:
|
||||
i.state === "QualityBlocked" ? "red" : undefined,
|
||||
i.mainDefectId === 864
|
||||
? "blue"
|
||||
: i.state === "QualityBlocked"
|
||||
? "red"
|
||||
: undefined,
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
@@ -159,52 +86,18 @@ export default function PPOO() {
|
||||
<Text>
|
||||
Running Number: {i.runningNumber ?? "Non barcoded"}
|
||||
</Text>
|
||||
<Text>
|
||||
Date: {format(i.lastMovingDate, "M/d/yyyy HH:mm")}
|
||||
</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>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,19 +2,27 @@ import { PortalHost } from "@rn-primitives/portal";
|
||||
import { Stack } from "expo-router";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import "../../global.css";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
import useDeviceLock from "../hooks/useDeviceCheck";
|
||||
import { useDeviceOrientationLock } from "../hooks/useDeviceOrientationLock";
|
||||
import { queryClient } from "../lib/queryStuff/queryClient";
|
||||
import { connectSocket } from "../lib/socket.io";
|
||||
import { zebraScanner } from "../lib/ZebraScanner";
|
||||
|
||||
export default function RootLayout() {
|
||||
useDeviceLock();
|
||||
useDeviceOrientationLock();
|
||||
|
||||
useEffect(() => {
|
||||
zebraScanner.ensureProfile();
|
||||
connectSocket();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StatusBar style="dark" />
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="index" />
|
||||
@@ -24,6 +32,8 @@ export default function RootLayout() {
|
||||
<Stack.Screen name="(tabs)" />
|
||||
</Stack>
|
||||
<PortalHost />
|
||||
</QueryClientProvider>
|
||||
|
||||
<Toast />
|
||||
</>
|
||||
);
|
||||
|
||||
169
lstMobile/src/app/dock/[id].tsx
Normal file
169
lstMobile/src/app/dock/[id].tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import * as Device from "expo-device";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { api } from "../../lib/apiHelper";
|
||||
import { getActiveLoadingOrders } from "../../lib/queryStuff/getActiveLoadingOrders";
|
||||
|
||||
export default function DockPage() {
|
||||
const { id, currentLoading } = useLocalSearchParams<{
|
||||
id: string;
|
||||
currentLoading: string;
|
||||
}>();
|
||||
const router = useRouter();
|
||||
const [active] = useState(currentLoading !== "");
|
||||
|
||||
const { width } = useWindowDimensions();
|
||||
const isTablet =
|
||||
Device.modelName?.toLowerCase().includes("et40") ||
|
||||
Device.modelName?.toLowerCase().includes("et45");
|
||||
|
||||
const columns = isTablet ? 3 : 1;
|
||||
|
||||
const gap = 8;
|
||||
const cardWidth =
|
||||
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||
|
||||
const {
|
||||
data: loadingOrders,
|
||||
refetch,
|
||||
isLoading,
|
||||
} = useSuspenseQuery(getActiveLoadingOrders());
|
||||
|
||||
const dockFilter = loadingOrders.filter((i: any) => i.dockId === Number(id));
|
||||
|
||||
// add in start loading order, if this is already on the dock we will disabled and change to view current pallets
|
||||
const startLoad = async (loadingOrder: string, dockId: string) => {
|
||||
try {
|
||||
const res = await api.post("/dockDoor/startLoad", {
|
||||
loadingOrder,
|
||||
dockId,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
Toast.show({ type: "success", text1: res.data.message });
|
||||
refetch();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const endLoad = async (loadingOrder: string, dockId: string) => {
|
||||
try {
|
||||
const res = await api.post("/dockDoor/endLoad", {
|
||||
loadingOrder,
|
||||
dockId,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
Toast.show({ type: "success", text1: res.data.message });
|
||||
refetch();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// add in ending loading order disabeled until all pallets are loaded.
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View className="flex flex-row justify-between gap-1 ml-1 mr-1">
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">← Back</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text className="text-xl mt-1">{dockFilter[0].dockDescription}</Text>
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
router.push({
|
||||
pathname: "/dock/scans/[scanner]",
|
||||
params: {
|
||||
scanner: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">Scans</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView>
|
||||
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||
{dockFilter.map((i: any) => {
|
||||
return (
|
||||
<View key={i.id}>
|
||||
<Card
|
||||
style={{
|
||||
borderWidth: 4,
|
||||
width: cardWidth,
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<View>
|
||||
<Text>Loading Order: {dockFilter[0].id}</Text>
|
||||
<Text>
|
||||
{`${dockFilter[0].loadingPlanItems[0].articleId} - ${dockFilter[0].loadingPlanItems[0].articleDescription}`}
|
||||
</Text>
|
||||
<Text>
|
||||
Current Loaded :{" "}
|
||||
{dockFilter[0].loadingPlanItems[0].loadedQuantityLUs} /{" "}
|
||||
{dockFilter[0].loadingPlanItems[0].plannedQuantityLUs}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="mt-2 flex flex-row gap-2 justify-between">
|
||||
<Button
|
||||
title="Start Load"
|
||||
onPress={() =>
|
||||
startLoad(dockFilter[0].id.toString(), id)
|
||||
}
|
||||
disabled={active}
|
||||
/>
|
||||
<Button
|
||||
title="End Load"
|
||||
onPress={() => endLoad(dockFilter[0].id.toString(), id)}
|
||||
disabled={active}
|
||||
/>
|
||||
</View>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
71
lstMobile/src/app/dock/scans/[scanner].tsx
Normal file
71
lstMobile/src/app/dock/scans/[scanner].tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { Card } from "../../../components/ui/card";
|
||||
import { useSocketRoom } from "../../../hooks/socket.io.hook";
|
||||
import { getActiveLoadingOrders } from "../../../lib/queryStuff/getActiveLoadingOrders";
|
||||
|
||||
export default function DockPage() {
|
||||
const { scanner } = useLocalSearchParams<{
|
||||
scanner: string;
|
||||
}>();
|
||||
const { data: loadingOrders, isLoading } = useSuspenseQuery(
|
||||
getActiveLoadingOrders(),
|
||||
);
|
||||
const { data } = useSocketRoom<any>(
|
||||
`dockDoorLoading:${scanner}`,
|
||||
undefined,
|
||||
"append",
|
||||
) as any;
|
||||
const dockFilter = loadingOrders.filter(
|
||||
(i: any) => i.dockId === Number(scanner),
|
||||
);
|
||||
const router = useRouter();
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Text>Loading...</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
return (
|
||||
<SafeAreaView className="w-full">
|
||||
<View className="flex flex-row justify-between gap-1 ml-1 mr-1">
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">← Back</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text className="text-xl mt-1">{dockFilter[0].dockDescription}</Text>
|
||||
<View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
router.replace({
|
||||
pathname: "/(tabs)/dockScan",
|
||||
})
|
||||
}
|
||||
className="self-start rounded-xl bg-gray-200 px-4 py-2"
|
||||
>
|
||||
<Text className="font-semibold">Docks</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View>
|
||||
{data.map((i: any, index: any) => {
|
||||
return (
|
||||
<View key={index} className="m-2">
|
||||
<Card>
|
||||
<Text>{JSON.stringify(i)}</Text>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { useRouter } from "expo-router";
|
||||
import { Settings } from "lucide-react-native";
|
||||
import { useState } from "react";
|
||||
import { Alert, Button, Text, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { ConfigButton } from "../components/ui/configButton";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
import { useMobileAuthStore } from "../hooks/useMobileAuth";
|
||||
@@ -52,11 +54,6 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
const config = () => {
|
||||
console.log("config");
|
||||
return router.replace("/setup");
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -67,9 +64,17 @@ export default function Login() {
|
||||
}}
|
||||
>
|
||||
<View className="flex items-center m-5">
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }}>
|
||||
<View className="flex flex-row">
|
||||
<View>
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }} className="mt-2">
|
||||
LST Scanner Login
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<ConfigButton />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="w-64 p-4">
|
||||
<Input
|
||||
className="w-fit"
|
||||
@@ -89,7 +94,6 @@ export default function Login() {
|
||||
</View>
|
||||
<View className="flex gap-2 flex-row">
|
||||
<Button title="Login" onPress={onLogin} />
|
||||
<Button title="Config" onPress={config} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -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 }}>
|
||||
<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" }}>
|
||||
User: {formatName(user?.name ?? "")}
|
||||
Lst user: {formatName(user?.name ?? "")}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 18, fontWeight: "600" }}>
|
||||
{/* <Text style={{ fontSize: 14, fontWeight: "600" }}>
|
||||
LST Scanner id: {user?.scannerId}
|
||||
</Text>
|
||||
</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 || shouldUpdate) && (
|
||||
<View className="bg-slate-500">
|
||||
{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
|
||||
<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">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
20
lstMobile/src/components/ui/configButton.tsx
Normal file
20
lstMobile/src/components/ui/configButton.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { Settings } from "lucide-react-native";
|
||||
import { Pressable } from "react-native";
|
||||
|
||||
export function ConfigButton() {
|
||||
const router = useRouter();
|
||||
const config = () => {
|
||||
console.log("config");
|
||||
return router.replace("/setup");
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={config}
|
||||
className="h-12 w-12 items-center justify-center rounded-x"
|
||||
>
|
||||
<Settings color="black" size={24} />
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
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 };
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from "axios";
|
||||
import Constants from "expo-constants";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { setApiConfig } from "../lib/apiHelper";
|
||||
import { devDelay } from "../lib/devMode";
|
||||
import { versionCheck } from "../lib/versionValidation";
|
||||
import { useAppStore } from "./useAppStore";
|
||||
@@ -26,6 +27,11 @@ export function useAppStartup() {
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
|
||||
setApiConfig({
|
||||
serverIp,
|
||||
serverPort,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasHydrated) {
|
||||
setStatus("loading");
|
||||
|
||||
32
lstMobile/src/hooks/useDeviceOrientationLock.ts
Normal file
32
lstMobile/src/hooks/useDeviceOrientationLock.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as Device from "expo-device";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const LANDSCAPE_MODELS = ["ET45", "ET40"]; // tablets
|
||||
const PORTRAIT_MODELS = ["TC21", "TC26", "TC8300"]; // scanners
|
||||
|
||||
const isTabletModel = (modelName?: string | null) => {
|
||||
const model = modelName?.toUpperCase() ?? "";
|
||||
|
||||
return LANDSCAPE_MODELS.some((m) => model.includes(m));
|
||||
};
|
||||
|
||||
export function useDeviceOrientationLock() {
|
||||
useEffect(() => {
|
||||
async function lockOrientation() {
|
||||
try {
|
||||
const model = Device.modelName;
|
||||
|
||||
await ScreenOrientation.lockAsync(
|
||||
isTabletModel(model)
|
||||
? ScreenOrientation.OrientationLock.LANDSCAPE
|
||||
: ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn("Failed to lock orientation", err);
|
||||
}
|
||||
}
|
||||
|
||||
void lockOrientation();
|
||||
}, []);
|
||||
}
|
||||
63
lstMobile/src/lib/apiHelper.ts
Normal file
63
lstMobile/src/lib/apiHelper.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import axios from "axios";
|
||||
import { router } from "expo-router";
|
||||
|
||||
type ApiConfig = {
|
||||
serverIp: string;
|
||||
serverPort: string | number;
|
||||
};
|
||||
|
||||
let currentConfig: ApiConfig | null = null;
|
||||
|
||||
export function setApiConfig(config: ApiConfig) {
|
||||
currentConfig = config;
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
if (!currentConfig) {
|
||||
throw new Error("API config not initialized");
|
||||
}
|
||||
console.log(
|
||||
`http://${currentConfig.serverIp}:${currentConfig.serverPort}/lst/api`,
|
||||
);
|
||||
return `http://${currentConfig.serverIp}:${currentConfig.serverPort}/lst/api`;
|
||||
}
|
||||
|
||||
export const api = axios.create({
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
config.baseURL = getBaseUrl();
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const isNetworkError =
|
||||
error.code === "ERR_NETWORK" ||
|
||||
error.code === "ECONNABORTED" ||
|
||||
error.message === "Network Error" ||
|
||||
error.message === "Failed to fetch" ||
|
||||
!error.response;
|
||||
|
||||
// unauthorized
|
||||
if (error.response?.status === 401) {
|
||||
router.replace("/login");
|
||||
}
|
||||
|
||||
// forbidden
|
||||
if (error.response?.status === 403) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
// app/server offline
|
||||
if (isNetworkError) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
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();
|
||||
}
|
||||
21
lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
Normal file
21
lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getActiveLoadingOrders() {
|
||||
return queryOptions({
|
||||
queryKey: ["getActiveLoadingOrders"],
|
||||
queryFn: () => dataFetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const dataFetch = async () => {
|
||||
const { data } = await api.get("/dockDoor/activeLoadingOrders");
|
||||
if (!data.success) {
|
||||
throw new Error(data.message ?? "Failed to load articles");
|
||||
}
|
||||
|
||||
return data.data ?? [];
|
||||
};
|
||||
21
lstMobile/src/lib/queryStuff/getDocks.ts
Normal file
21
lstMobile/src/lib/queryStuff/getDocks.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getDocks() {
|
||||
return queryOptions({
|
||||
queryKey: ["getDocks"],
|
||||
queryFn: () => dataFetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const dataFetch = async () => {
|
||||
const { data } = await api.get("/dockDoor/scanners");
|
||||
if (!data.success) {
|
||||
throw new Error(data.message ?? "Failed to load articles");
|
||||
}
|
||||
|
||||
return data.data ?? [];
|
||||
};
|
||||
17
lstMobile/src/lib/queryStuff/queryClient.ts
Normal file
17
lstMobile/src/lib/queryStuff/queryClient.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 30_000,
|
||||
gcTime: 5 * 60_000,
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMount: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
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";
|
||||
69
migrations/0059_sparkling_joystick.sql
Normal file
69
migrations/0059_sparkling_joystick.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "add_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "add_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "alpla_purchase_history" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "analytics" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "analytics" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "job_audit_log" ALTER COLUMN "finished_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "job_audit_log" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "job_audit_log" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "user" ALTER COLUMN "updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "user" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "deployment_history" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "deployment_history" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "first_hit_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "last_hit_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "updated_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "analytics_daily" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "datamart" ALTER COLUMN "add_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "datamart" ALTER COLUMN "add_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "datamart" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "datamart" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "dock_door_scanners" ALTER COLUMN "add_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "dock_door_scanners" ALTER COLUMN "add_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "dock_door_scanners" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "dock_door_scanners" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "inv_historical_data" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "inv_historical_data" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "logs" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "logs" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_apt" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_apt" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_apt" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_apt" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_article_setup" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_article_setup" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_article_setup" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_article_setup" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_dock_setup" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_dock_setup" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "opendock_dock_setup" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "opendock_dock_setup" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "printer_log" ALTER COLUMN "created_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "printer_log" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "printer_data" ALTER COLUMN "add_Date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "printer_data" ALTER COLUMN "add_Date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "printer_data" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "printer_data" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "scan_log" ALTER COLUMN "add_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "scan_log" ALTER COLUMN "add_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "scan_users" ALTER COLUMN "add_Date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "scan_users" ALTER COLUMN "add_Date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "scan_users" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "scan_users" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "server_data" ALTER COLUMN "last_updated" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "server_data" ALTER COLUMN "last_updated" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "settings" ALTER COLUMN "add_Date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ALTER COLUMN "add_Date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "settings" ALTER COLUMN "upd_date" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ALTER COLUMN "upd_date" SET DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "app_stats" ALTER COLUMN "last_build_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "app_stats" ALTER COLUMN "last_deploy_at" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "app_stats" ALTER COLUMN "last_updated" SET DATA TYPE timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "app_stats" ALTER COLUMN "last_updated" SET DEFAULT now();
|
||||
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
2603
migrations/meta/0059_snapshot.json
Normal file
2603
migrations/meta/0059_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -400,6 +400,27 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
"idx": 59,
|
||||
"version": "7",
|
||||
"when": 1780349486711,
|
||||
"tag": "0059_sparkling_joystick",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user