diff --git a/lstMobile/app.json b/lstMobile/app.json
index c25a4a1..7159d27 100644
--- a/lstMobile/app.json
+++ b/lstMobile/app.json
@@ -15,14 +15,14 @@
"foregroundImage": "./assets/adaptive-icon-white.png",
"backgroundColor": "#ffffff"
},
- "versionCode": 39,
+ "versionCode": 42,
"minSupportedVersionCode": 33,
"predictiveBackGestureEnabled": false,
"package": "net.alpla.lst.mobile",
"permissions": [
- "android.permission.WRITE_EXTERNAL_STORAGE",
- "android.permission.READ_EXTERNAL_STORAGE"
- ]
+ "android.permission.WRITE_EXTERNAL_STORAGE",
+ "android.permission.READ_EXTERNAL_STORAGE"
+ ]
},
"web": {
"output": "static",
diff --git a/lstMobile/package-lock.json b/lstMobile/package-lock.json
index 31e65e4..0e4e3fc 100644
--- a/lstMobile/package-lock.json
+++ b/lstMobile/package-lock.json
@@ -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",
@@ -5318,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",
@@ -5328,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",
diff --git a/lstMobile/package.json b/lstMobile/package.json
index 3548a62..0b822dd 100644
--- a/lstMobile/package.json
+++ b/lstMobile/package.json
@@ -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",
diff --git a/lstMobile/src/app/(tabs)/_layout.tsx b/lstMobile/src/app/(tabs)/_layout.tsx
index 355fe44..b5296a1 100644
--- a/lstMobile/src/app/(tabs)/_layout.tsx
+++ b/lstMobile/src/app/(tabs)/_layout.tsx
@@ -51,7 +51,7 @@ export default function TabsLayout() {
onPress: async () => {
// clear auth/session
logoutScanner();
- router.replace("/(tabs)/scanner");
+ router.replace("/");
// clear zustand/session stuff
//useAuthStore.getState().reset();
diff --git a/lstMobile/src/app/(tabs)/dockScan.tsx b/lstMobile/src/app/(tabs)/dockScan.tsx
index 690ae5f..3e16e7d 100644
--- a/lstMobile/src/app/(tabs)/dockScan.tsx
+++ b/lstMobile/src/app/(tabs)/dockScan.tsx
@@ -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 (
+
+ Loading...
+
+ );
return (
-
- Dock Scanning
-
+
+
+ Dock Scanning
+
+
+
+
+
+
+ {data.map((i: any) => {
+ const loadingPlan =
+ i.currentLoadingOrder !== ""
+ ? loadingOrders.filter(
+ (x: any) => x.id === Number(i.currentLoadingOrder),
+ )
+ : [];
+
+ return (
+
+
+
+
+ {i.name}
+ {i.currentLoadingOrder === "" ? (
+ Tap to active new loading order
+ ) : (
+
+
+ Current Loading order : {i.currentLoadingOrder}
+
+ {loadingPlan && loadingPlan.length > 0 && (
+
+
+ {`${loadingPlan[0].loadingPlanItems[0].articleId} - ${loadingPlan[0].loadingPlanItems[0].articleDescription}`}
+
+
+ Current Loaded :{" "}
+ {
+ loadingPlan[0].loadingPlanItems[0]
+ .loadedQuantityLUs
+ }{" "}
+ /{" "}
+ {
+ loadingPlan[0].loadingPlanItems[0]
+ .plannedQuantityLUs
+ }
+
+
+ )}
+
+ )}
+
+
+
+
+ );
+ })}
+
+
+
+
);
}
diff --git a/lstMobile/src/app/(tabs)/ppoo.tsx b/lstMobile/src/app/(tabs)/ppoo.tsx
index 3849aa8..366ed3b 100644
--- a/lstMobile/src/app/(tabs)/ppoo.tsx
+++ b/lstMobile/src/app/(tabs)/ppoo.tsx
@@ -44,8 +44,6 @@ export default function PPOO() {
});
}, [items, sortDir]);
- //console.log(logsInfo);
-
return (
@@ -61,7 +59,7 @@ export default function PPOO() {
Loading PPOO...
) : (
-
+
{sortedItems.map((i: any) => {
diff --git a/lstMobile/src/app/_layout.tsx b/lstMobile/src/app/_layout.tsx
index 67c0963..faace91 100644
--- a/lstMobile/src/app/_layout.tsx
+++ b/lstMobile/src/app/_layout.tsx
@@ -2,14 +2,18 @@ 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();
@@ -18,15 +22,18 @@ export default function RootLayout() {
return (
<>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
>
);
diff --git a/lstMobile/src/app/dock/[id].tsx b/lstMobile/src/app/dock/[id].tsx
new file mode 100644
index 0000000..71e0857
--- /dev/null
+++ b/lstMobile/src/app/dock/[id].tsx
@@ -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 (
+
+ Loading
+
+ );
+ return (
+
+
+
+ router.back()}
+ className="self-start rounded-xl bg-gray-200 px-4 py-2"
+ >
+ ← Back
+
+
+
+ {dockFilter[0].dockDescription}
+
+
+ router.push({
+ pathname: "/dock/scans/[scanner]",
+ params: {
+ scanner: id,
+ },
+ })
+ }
+ className="self-start rounded-xl bg-gray-200 px-4 py-2"
+ >
+ Scans
+
+
+
+
+
+ {dockFilter.map((i: any) => {
+ return (
+
+
+
+
+ Loading Order: {dockFilter[0].id}
+
+ {`${dockFilter[0].loadingPlanItems[0].articleId} - ${dockFilter[0].loadingPlanItems[0].articleDescription}`}
+
+
+ Current Loaded :{" "}
+ {dockFilter[0].loadingPlanItems[0].loadedQuantityLUs} /{" "}
+ {dockFilter[0].loadingPlanItems[0].plannedQuantityLUs}
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/lstMobile/src/app/dock/scans/[scanner].tsx b/lstMobile/src/app/dock/scans/[scanner].tsx
new file mode 100644
index 0000000..bbdace4
--- /dev/null
+++ b/lstMobile/src/app/dock/scans/[scanner].tsx
@@ -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(
+ `dockDoorLoading:${scanner}`,
+ undefined,
+ "append",
+ ) as any;
+ const dockFilter = loadingOrders.filter(
+ (i: any) => i.dockId === Number(scanner),
+ );
+ const router = useRouter();
+
+ if (isLoading)
+ return (
+
+ Loading...
+
+ );
+ return (
+
+
+
+ router.back()}
+ className="self-start rounded-xl bg-gray-200 px-4 py-2"
+ >
+ ← Back
+
+
+
+ {dockFilter[0].dockDescription}
+
+
+ router.replace({
+ pathname: "/(tabs)/dockScan",
+ })
+ }
+ className="self-start rounded-xl bg-gray-200 px-4 py-2"
+ >
+ Docks
+
+
+
+ {data.map((i: any, index: any) => {
+ return (
+
+
+ {JSON.stringify(i)}
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/lstMobile/src/app/login.tsx b/lstMobile/src/app/login.tsx
index 38ec522..b02832e 100644
--- a/lstMobile/src/app/login.tsx
+++ b/lstMobile/src/app/login.tsx
@@ -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 (
-
- LST Scanner Login
-
+
+
+
+ LST Scanner Login
+
+
+
+
+
+
+
-
);
diff --git a/lstMobile/src/components/ui/configButton.tsx b/lstMobile/src/components/ui/configButton.tsx
new file mode 100644
index 0000000..f18207e
--- /dev/null
+++ b/lstMobile/src/components/ui/configButton.tsx
@@ -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 (
+
+
+
+ );
+}
diff --git a/lstMobile/src/hooks/useAppStartup.tsx b/lstMobile/src/hooks/useAppStartup.tsx
index b509ab0..787bba3 100644
--- a/lstMobile/src/hooks/useAppStartup.tsx
+++ b/lstMobile/src/hooks/useAppStartup.tsx
@@ -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");
diff --git a/lstMobile/src/hooks/useDeviceOrientationLock.ts b/lstMobile/src/hooks/useDeviceOrientationLock.ts
new file mode 100644
index 0000000..fafbaed
--- /dev/null
+++ b/lstMobile/src/hooks/useDeviceOrientationLock.ts
@@ -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();
+ }, []);
+}
diff --git a/lstMobile/src/lib/apiHelper.ts b/lstMobile/src/lib/apiHelper.ts
new file mode 100644
index 0000000..42670ba
--- /dev/null
+++ b/lstMobile/src/lib/apiHelper.ts
@@ -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);
+ },
+);
diff --git a/lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts b/lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
new file mode 100644
index 0000000..7689c6f
--- /dev/null
+++ b/lstMobile/src/lib/queryStuff/getActiveLoadingOrders.ts
@@ -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 ?? [];
+};
diff --git a/lstMobile/src/lib/queryStuff/getDocks.ts b/lstMobile/src/lib/queryStuff/getDocks.ts
new file mode 100644
index 0000000..be07a7e
--- /dev/null
+++ b/lstMobile/src/lib/queryStuff/getDocks.ts
@@ -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 ?? [];
+};
diff --git a/lstMobile/src/lib/queryStuff/queryClient.ts b/lstMobile/src/lib/queryStuff/queryClient.ts
new file mode 100644
index 0000000..3bb3d4b
--- /dev/null
+++ b/lstMobile/src/lib/queryStuff/queryClient.ts
@@ -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,
+ },
+ },
+});