feat(warehousing): ppoo monitoring added
this will monitor ppoo every 45 seconds as long as someone is on the page. closes #13
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
|||||||
} from "./utils/analyticRouteHits.utils.js";
|
} from "./utils/analyticRouteHits.utils.js";
|
||||||
import { createCronJob } from "./utils/croner.utils.js";
|
import { createCronJob } from "./utils/croner.utils.js";
|
||||||
import { sendEmail } from "./utils/sendEmail.utils.js";
|
import { sendEmail } from "./utils/sendEmail.utils.js";
|
||||||
|
import { ppooMonitoring } from "./warehousing/warehousing.ppooMonitor.js";
|
||||||
|
|
||||||
const port = Number(process.env.PORT) || 3000;
|
const port = Number(process.env.PORT) || 3000;
|
||||||
export let systemSettings: Setting[] = [];
|
export let systemSettings: Setting[] = [];
|
||||||
@@ -78,6 +79,10 @@ const start = async () => {
|
|||||||
runRouteHitAnalyticsCron(),
|
runRouteHitAnalyticsCron(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
createCronJob("ppooMonitor", "*/45 * * * * *", async () =>
|
||||||
|
ppooMonitoring(),
|
||||||
|
);
|
||||||
|
|
||||||
createCronJob("cleanHitsUp", "0 0 7 * * *", () => cleanupOldRouteHits());
|
createCronJob("cleanHitsUp", "0 0 7 * * *", () => cleanupOldRouteHits());
|
||||||
// one shots only needed to run on server startups
|
// one shots only needed to run on server startups
|
||||||
createNotifications();
|
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 MAX_HISTORY = 50;
|
||||||
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
||||||
|
|||||||
@@ -1,17 +1,50 @@
|
|||||||
import { desc } from "drizzle-orm";
|
import { desc } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { logs } from "../db/schema/logs.schema.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> = {
|
type RoomDefinition<T = unknown> = {
|
||||||
seed: (limit: number) => Promise<T[]>;
|
seed: (limit: number) => Promise<T[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const protectedRooms: any = {
|
export type StaticRoomId = "logs" | "labels" | "admin" | "admin:build" | "ppoo";
|
||||||
|
export type DynamicRoomId = `dockDoorLoading:${string}`;
|
||||||
|
export type RoomId = StaticRoomId | DynamicRoomId;
|
||||||
|
|
||||||
|
export type RoomConfig = {
|
||||||
|
requiresAuth?: boolean;
|
||||||
|
role?: string[];
|
||||||
|
seed?: (limit: number, roomId: RoomId) => Promise<unknown[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const protectedRooms: Record<StaticRoomId, RoomConfig> = {
|
||||||
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||||
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
|
//admin: { requiresAuth: false, role: ["admin", "systemAdmin"] },
|
||||||
|
labels: {},
|
||||||
|
admin: {},
|
||||||
|
"admin:build": {},
|
||||||
|
ppoo: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getRoomConfig(roomId: string): RoomConfig | null {
|
||||||
|
if (roomId in protectedRooms) {
|
||||||
|
return protectedRooms[roomId as StaticRoomId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomId.startsWith("dockDoorLoading:")) {
|
||||||
|
const dockId = roomId.split(":")[1];
|
||||||
|
|
||||||
|
if (!dockId) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
requiresAuth: true,
|
||||||
|
role: ["admin", "systemAdmin", "dockDoor"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||||
logs: {
|
logs: {
|
||||||
seed: async (limit) => {
|
seed: async (limit) => {
|
||||||
@@ -48,4 +81,14 @@ export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
|||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ppoo: {
|
||||||
|
seed: async (limit) => {
|
||||||
|
console.log(limit);
|
||||||
|
return {
|
||||||
|
type: "snapshot",
|
||||||
|
items: await ppoRun(),
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
} as any;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// the emitter setup
|
// 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;
|
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,33 @@ import {
|
|||||||
roomFlushTimers,
|
roomFlushTimers,
|
||||||
roomHistory,
|
roomHistory,
|
||||||
} from "./roomCache.socket.js";
|
} from "./roomCache.socket.js";
|
||||||
import { roomDefinition } from "./roomDefinitions.socket.js";
|
import { type RoomId, roomDefinition } from "./roomDefinitions.socket.js";
|
||||||
import type { RoomId } from "./types.socket.js";
|
|
||||||
|
|
||||||
// get the db data if not exiting already
|
// get the db data if not exiting already
|
||||||
const log = createLogger({ module: "socket.io", subModule: "roomService" });
|
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) => {
|
export const preseedRoom = async (roomId: RoomId) => {
|
||||||
if (roomHistory.has(roomId)) {
|
if (roomHistory.has(roomId)) {
|
||||||
return roomHistory.get(roomId);
|
return roomHistory.get(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomDef = roomDefinition[roomId];
|
const roomDef = roomDefinition[roomId] as any;
|
||||||
|
|
||||||
if (!roomDef) {
|
if (!roomDef) {
|
||||||
log.error({}, `Room ${roomId} is not defined`);
|
log.error({}, `Room ${roomId} is not defined`);
|
||||||
@@ -32,7 +47,7 @@ export const preseedRoom = async (roomId: RoomId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createRoomEmitter = (io: Server) => {
|
export const createRoomEmitter = (io: Server) => {
|
||||||
const addDataToRoom = <T>(roomId: RoomId, payload: T) => {
|
const addDataToRoom = <T>(roomId: RoomId, payload: T[]) => {
|
||||||
if (!roomHistory.has(roomId)) {
|
if (!roomHistory.has(roomId)) {
|
||||||
roomHistory.set(roomId, []);
|
roomHistory.set(roomId, []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import { Server } from "socket.io";
|
|||||||
import { createLogger } from "../logger/logger.controller.js";
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
import { allowedOrigins } from "../utils/cors.utils.js";
|
import { allowedOrigins } from "../utils/cors.utils.js";
|
||||||
import { registerEmitter } from "./roomEmitter.socket.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 __filename = fileURLToPath(import.meta.url);
|
||||||
//const __dirname = dirname(__filename);
|
//const __dirname = dirname(__filename);
|
||||||
@@ -15,7 +19,7 @@ const log = createLogger({ module: "socket.io", subModule: "setup" });
|
|||||||
|
|
||||||
import { auth } from "../utils/auth.utils.js";
|
import { auth } from "../utils/auth.utils.js";
|
||||||
//import type { Session, User } from "better-auth"; // adjust if needed
|
//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" {
|
// declare module "socket.io" {
|
||||||
// interface Socket {
|
// interface Socket {
|
||||||
@@ -33,6 +37,9 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// manage members of the rooms.
|
||||||
|
registerRoomService(io);
|
||||||
|
|
||||||
// ✅ Create emitter instance
|
// ✅ Create emitter instance
|
||||||
const { addDataToRoom } = createRoomEmitter(io);
|
const { addDataToRoom } = createRoomEmitter(io);
|
||||||
registerEmitter(addDataToRoom);
|
registerEmitter(addDataToRoom);
|
||||||
@@ -78,38 +85,76 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
|||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
s.on("join-room", async (rn) => {
|
// s.on("join-room", async (rn) => {
|
||||||
const config = protectedRooms[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", {
|
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",
|
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 (roles.length > 0 && !roles.includes(s.user?.role)) {
|
||||||
if (config?.role && !roles.includes(s.user?.role)) {
|
|
||||||
return s.emit("room-error", {
|
return s.emit("room-error", {
|
||||||
roomId: rn,
|
roomId: rn,
|
||||||
message: `Not authorized to be in room: ${rn}`,
|
message: `Not authorized to be in room: ${rn}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
s.join(rn);
|
s.join(rn);
|
||||||
|
|
||||||
// get room seeded
|
const history = await preseedRoom(rn as any);
|
||||||
const history = await preseedRoom(rn);
|
|
||||||
log.info({}, `User joined ${rn}: ${s.id}`);
|
log.info({}, `User joined ${rn}: ${s.id}`);
|
||||||
// send the intial data
|
|
||||||
s.emit("room-update", {
|
s.emit("room-update", {
|
||||||
roomId: rn,
|
roomId: rn,
|
||||||
payloads: history,
|
payloads: history,
|
||||||
initial: true,
|
initial: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
s.on("leave-room", (room) => {
|
s.on("leave-room", (room) => {
|
||||||
s.leave(room);
|
s.leave(room);
|
||||||
log.info({}, `${s.id} left room: ${room}`);
|
log.info({}, `${s.id} left room: ${room}`);
|
||||||
|
|||||||
29
backend/warehousing/warehousing.ppooMonitor.ts
Normal file
29
backend/warehousing/warehousing.ppooMonitor.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||||
|
import { hasRoomMembers } from "../socket.io/roomService.socket.js";
|
||||||
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
|
|
||||||
|
export const ppoRun = async () => {
|
||||||
|
const laneData = await runProdApi({
|
||||||
|
method: "post",
|
||||||
|
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
laneIds: ["0"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return laneData?.data ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ppooMonitoring = async () => {
|
||||||
|
if (!hasRoomMembers(`ppoo`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitToRoom("ppoo", {
|
||||||
|
type: "snapshot",
|
||||||
|
items: await ppoRun(),
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
} as any);
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ type RoomErrorPayload = {
|
|||||||
|
|
||||||
export function useSocketRoom<T>(
|
export function useSocketRoom<T>(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
|
enabled = true,
|
||||||
getKey?: (item: T) => string | number,
|
getKey?: (item: T) => string | number,
|
||||||
) {
|
) {
|
||||||
const [data, setData] = useState<T[]>([]);
|
const [data, setData] = useState<T[]>([]);
|
||||||
@@ -35,6 +36,7 @@ export function useSocketRoom<T>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!roomId || !enabled) return;
|
||||||
function handleConnect() {
|
function handleConnect() {
|
||||||
socket.emit("join-room", roomId);
|
socket.emit("join-room", roomId);
|
||||||
setInfo(`Joined room: ${roomId}`);
|
setInfo(`Joined room: ${roomId}`);
|
||||||
@@ -74,7 +76,18 @@ export function useSocketRoom<T>(
|
|||||||
socket.off("room-update", handleUpdate);
|
socket.off("room-update", handleUpdate);
|
||||||
socket.off("room-error", handleError);
|
socket.off("room-error", handleError);
|
||||||
};
|
};
|
||||||
}, [roomId]);
|
}, [roomId, enabled]);
|
||||||
|
|
||||||
return { data, info, clearRoom };
|
return { data, info, clearRoom };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
const isDockDoorPage = location.pathname.startsWith("/dockdoor");
|
||||||
|
|
||||||
|
useSocketRoom(
|
||||||
|
dockId ? `dockdoor:${dockId}` : null,
|
||||||
|
isDockDoorPage,
|
||||||
|
);
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,154 +1,83 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
|
import * as Device from "expo-device";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { useFocusEffect } from "expo-router";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useMemo, 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 {
|
import {
|
||||||
Dialog,
|
Button,
|
||||||
DialogContent,
|
ScrollView,
|
||||||
DialogDescription,
|
Text,
|
||||||
DialogHeader,
|
useWindowDimensions,
|
||||||
DialogTitle,
|
View,
|
||||||
DialogTrigger,
|
} from "react-native";
|
||||||
} from "../../components/ui/dialog";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
import { useAppStore } from "../../hooks/useAppStore";
|
import { Card, CardContent } from "../../components/ui/card";
|
||||||
import { scannerFeedback } from "../../lib/feedbackScan";
|
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
||||||
import { type ZebraScanResult, zebraScanner } from "../../lib/ZebraScanner";
|
|
||||||
|
|
||||||
const InfoRow = ({
|
type PPOO = {
|
||||||
label,
|
type: string;
|
||||||
value,
|
items: any;
|
||||||
}: {
|
createdAt: Date;
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PPOO() {
|
export default function PPOO() {
|
||||||
const [units, setUnits] = useState<any>(null);
|
const { data } = useSocketRoom<any>("ppoo", undefined, "replace") as any;
|
||||||
const serverIp = useAppStore((s) => s.serverIp);
|
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
||||||
|
|
||||||
const handleScan = useCallback(
|
const { width } = useWindowDimensions();
|
||||||
async (scan: ZebraScanResult) => {
|
const isTablet =
|
||||||
setUnits(null);
|
Device.modelName?.toLowerCase().includes("et40") ||
|
||||||
await scannerFeedback({
|
Device.modelName?.toLowerCase().includes("et45");
|
||||||
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",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
const columns = isTablet ? 3 : 1;
|
||||||
}
|
|
||||||
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],
|
|
||||||
);
|
|
||||||
|
|
||||||
useFocusEffect(
|
const gap = 8;
|
||||||
useCallback(() => {
|
const cardWidth =
|
||||||
zebraScanner.startListening();
|
columns === 1 ? width - 16 : (width - gap * (columns + 1)) / columns;
|
||||||
|
|
||||||
const sub = zebraScanner.addScanListener((scan) => {
|
const items = data?.items ?? [];
|
||||||
//console.log("SCAN:", scan);
|
const sortedItems = useMemo(() => {
|
||||||
handleScan(scan);
|
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;
|
||||||
});
|
});
|
||||||
|
}, [items, sortDir]);
|
||||||
|
|
||||||
return () => {
|
//console.log(logsInfo);
|
||||||
sub.remove();
|
|
||||||
zebraScanner.stopListening();
|
|
||||||
//setUnits(null);
|
|
||||||
};
|
|
||||||
}, [handleScan]),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View className="flex items-center mt-2">
|
||||||
style={{
|
<View className="flex m-2">
|
||||||
//justifyContent: "center",
|
<Button
|
||||||
alignItems: "center",
|
onPress={() =>
|
||||||
marginTop: 50,
|
setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))
|
||||||
}}
|
}
|
||||||
>
|
title={`Sort: ${sortDir}`}
|
||||||
{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>
|
</View>
|
||||||
<ScrollView className="w-full" style={{ marginBottom: 20 }}>
|
{sortedItems.length === 0 ? (
|
||||||
<View>
|
<View className="flex items-center">
|
||||||
{units.data.map((i, index) => (
|
<Text>Loading PPOO...</Text>
|
||||||
<View
|
</View>
|
||||||
key={`${i.runningNumber}-${index}`}
|
) : (
|
||||||
style={{
|
<SafeAreaView className="flex-1">
|
||||||
justifyContent: "center",
|
<ScrollView className="w-full">
|
||||||
margin: 2,
|
<View className="w-full flex-row flex-wrap gap-2 m-2">
|
||||||
}}
|
{sortedItems.map((i: any) => {
|
||||||
>
|
return (
|
||||||
<Dialog>
|
<View key={i.runningNumber}>
|
||||||
<DialogTrigger>
|
|
||||||
<Card
|
<Card
|
||||||
className="w-full"
|
//className={isTablet ? "w-[32%]" : "w-full"}
|
||||||
style={{
|
style={{
|
||||||
borderColor:
|
borderColor:
|
||||||
i.state === "QualityBlocked" ? "red" : undefined,
|
i.mainDefectId === 864
|
||||||
|
? "blue"
|
||||||
|
: i.state === "QualityBlocked"
|
||||||
|
? "red"
|
||||||
|
: undefined,
|
||||||
borderWidth: 4,
|
borderWidth: 4,
|
||||||
|
width: cardWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -159,52 +88,18 @@ export default function PPOO() {
|
|||||||
<Text>
|
<Text>
|
||||||
Running Number: {i.runningNumber ?? "Non barcoded"}
|
Running Number: {i.runningNumber ?? "Non barcoded"}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>
|
||||||
|
Date: {format(i.lastMovingDate, "M/d/yyyy HH:mm")}
|
||||||
|
</Text>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</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>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
136
lstMobile/src/hooks/socket.io.hook.ts
Normal file
136
lstMobile/src/hooks/socket.io.hook.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { getSocket } from "../lib/socket.io";
|
||||||
|
|
||||||
|
type RoomUpdatePayload<T> = {
|
||||||
|
roomId: string;
|
||||||
|
payloads: T[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type RoomErrorPayload = {
|
||||||
|
roomId?: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateMode = "append" | "replace";
|
||||||
|
|
||||||
|
export function useSocketRoom<T>(
|
||||||
|
roomId: string,
|
||||||
|
getKey?: (item: T) => string | number,
|
||||||
|
updateMode: UpdateMode = "append",
|
||||||
|
) {
|
||||||
|
const [data, setData] = useState<T[]>([]);
|
||||||
|
const [info, setInfo] = useState(
|
||||||
|
"No data yet — join the room to start receiving",
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearRoom = useCallback(
|
||||||
|
(id?: string | number) => {
|
||||||
|
if (id !== undefined && getKey) {
|
||||||
|
setData((prev) => prev.filter((item) => getKey(item) !== id));
|
||||||
|
setInfo(`Removed item ${id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setData([]);
|
||||||
|
setInfo("Room data cleared");
|
||||||
|
},
|
||||||
|
[getKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
const socket = getSocket();
|
||||||
|
function handleConnect() {
|
||||||
|
socket.emit("join-room", roomId);
|
||||||
|
setInfo(`Joined room: ${roomId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdate(payload: RoomUpdatePayload<T>) {
|
||||||
|
// protects against other room updates hitting this hook
|
||||||
|
if (payload.roomId !== roomId) return;
|
||||||
|
|
||||||
|
// resetting room data for rooms that just need updated data.
|
||||||
|
if (updateMode === "replace") {
|
||||||
|
setData(payload.payloads);
|
||||||
|
} else {
|
||||||
|
setData((prev) => [...payload.payloads, ...prev]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInfo("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(err: RoomErrorPayload) {
|
||||||
|
if (err.roomId && err.roomId !== roomId) return;
|
||||||
|
setInfo(err.message ?? "Room error");
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on("connect", handleConnect);
|
||||||
|
socket.on("room-update", handleUpdate);
|
||||||
|
socket.on("room-error", handleError);
|
||||||
|
|
||||||
|
if (!socket.connected && socket.disconnected) {
|
||||||
|
socket.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already connected, join immediately
|
||||||
|
if (socket.connected) {
|
||||||
|
socket.emit("join-room", roomId);
|
||||||
|
setInfo(`Joined room: ${roomId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.emit("leave-room", roomId);
|
||||||
|
|
||||||
|
socket.off("connect", handleConnect);
|
||||||
|
socket.off("room-update", handleUpdate);
|
||||||
|
socket.off("room-error", handleError);
|
||||||
|
};
|
||||||
|
}, [roomId, updateMode]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const socket = getSocket();
|
||||||
|
// function handleConnect() {
|
||||||
|
// socket.emit("join-room", roomId);
|
||||||
|
// setInfo(`Joined room: ${roomId}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleUpdate(payload: RoomUpdatePayload<T>) {
|
||||||
|
// // protects against other room updates hitting this hook
|
||||||
|
// if (payload.roomId !== roomId) return;
|
||||||
|
|
||||||
|
// setData((prev) => [...payload.payloads, ...prev]);
|
||||||
|
// setInfo("");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleError(err: RoomErrorPayload) {
|
||||||
|
// if (err.roomId && err.roomId !== roomId) return;
|
||||||
|
// setInfo(err.message ?? "Room error");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!socket.connected && socket.disconnected) {
|
||||||
|
// socket.connect();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // If already connected, join immediately
|
||||||
|
// if (socket.connected) {
|
||||||
|
// socket.emit("join-room", roomId);
|
||||||
|
// setInfo(`Joined room: ${roomId}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// socket.on("connect", handleConnect);
|
||||||
|
// socket.on("room-update", handleUpdate);
|
||||||
|
// socket.on("room-error", handleError);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// socket.emit("leave-room", roomId);
|
||||||
|
// console.log("leaving Room");
|
||||||
|
// socket.off("connect", handleConnect);
|
||||||
|
// socket.off("room-update", handleUpdate);
|
||||||
|
// socket.off("room-error", handleError);
|
||||||
|
// };
|
||||||
|
// }, [roomId]);
|
||||||
|
|
||||||
|
return { data, info, clearRoom };
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user