Compare commits

...

4 Commits

50 changed files with 17588 additions and 163 deletions

1
.gitignore vendored
View File

@@ -13,7 +13,6 @@ controllerBuilds
# ignoring the old app that will be built into this one to make deploying faster and more easy as we do the migration
lstV2/frontend/.tanstack
mobileLst
keys
# Logs
logs

110
.vscode/settings.json vendored
View File

@@ -1,59 +1,63 @@
{
"editor.defaultFormatter": "biomejs.biome",
"workbench.colorTheme": "Default Dark+",
"terminal.integrated.env.windows": {},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"editor.defaultFormatter": "biomejs.biome",
"workbench.colorTheme": "Default Dark+",
"terminal.integrated.env.windows": {},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[javascript]": {
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.formatOnSave": true
},
"[json]": {
"editor.formatOnSave": true
},
"[graphql]": {
"editor.formatOnSave": true
},
"[handlebars]": {
"editor.formatOnSave": true
},
"[go]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "golang.go"
},
"[powershell]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-vscode.powershell" // requires PowerShell extension
},
"[bat]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "foxundermoon.shell-format" // supports .sh, .bat, .cmd
},
"[cmd]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[javascript]": {
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.formatOnSave": true
},
"[json]": {
"editor.formatOnSave": true
},
"[graphql]": {
"editor.formatOnSave": true
},
"[handlebars]": {
"editor.formatOnSave": true
},
"[go]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "golang.go"
},
"[powershell]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-vscode.powershell" // requires PowerShell extension
},
"[bat]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "foxundermoon.shell-format" // supports .sh, .bat, .cmd
},
"[cmd]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "foxundermoon.shell-format"
},
// Optional: Configure goimports instead of gofmt
"go.formatTool": "goimports",
"cSpell.words": [
"acitve",
"alpla",
"alplamart",
"alplaprod",
"intiallally",
"ppoo",
"prodlabels"
]
// Optional: Configure goimports instead of gofmt
"go.formatTool": "goimports",
"cSpell.words": [
"acitve",
"alpla",
"alplamart",
"alplaprod",
"intiallally",
"ppoo",
"prodlabels"
],
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
"gitea.instanceURL": "https://git.tuffraid.net",
"gitea.owner": "cowch",
"gitea.repo": "lst"
}

View File

@@ -100,8 +100,8 @@
"category": "quality",
"active": false,
"icon": "",
"link": "",
"roles": ["admin", "systemAdmin", "manager", "viewer", "tester"]
"link": "/lst/app/old/quality",
"roles": ["admin", "systemAdmin", "manager", "supervisor", "tester"]
},
{
"name": "eom",

View File

@@ -40,7 +40,7 @@ export default function TableNoExpand({
});
return (
<div className="p-4">
<div className="w-fit">
<div className="">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@@ -21,6 +21,7 @@ import { Route as AppAdminLayoutRouteRouteImport } from './routes/_app/_adminLay
import { Route as OldOldIndexRouteImport } from './routes/_old/old/index'
import { Route as AppauthLoginRouteImport } from './routes/_app/(auth)/login'
import { Route as OldOldRfidIndexRouteImport } from './routes/_old/old/rfid/index'
import { Route as OldOldQualityIndexRouteImport } from './routes/_old/old/quality/index'
import { Route as OldOldOcpIndexRouteImport } from './routes/_old/old/ocp/index'
import { Route as MobileMobileLayoutMIndexRouteImport } from './routes/_mobile/_mobileLayout/m/index'
import { Route as AppForkliftsForkliftsIndexRouteImport } from './routes/_app/_forklifts/forklifts/index'
@@ -107,6 +108,11 @@ const OldOldRfidIndexRoute = OldOldRfidIndexRouteImport.update({
path: '/rfid/',
getParentRoute: () => OldOldRouteRoute,
} as any)
const OldOldQualityIndexRoute = OldOldQualityIndexRouteImport.update({
id: '/quality/',
path: '/quality/',
getParentRoute: () => OldOldRouteRoute,
} as any)
const OldOldOcpIndexRoute = OldOldOcpIndexRouteImport.update({
id: '/ocp/',
path: '/ocp/',
@@ -303,6 +309,7 @@ export interface FileRoutesByFullPath {
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
'/m': typeof MobileMobileLayoutMIndexRoute
'/old/ocp': typeof OldOldOcpIndexRoute
'/old/quality': typeof OldOldQualityIndexRoute
'/old/rfid': typeof OldOldRfidIndexRoute
'/admin/modules': typeof AppAdminLayoutAdminSystemModulesRoute
'/admin/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
@@ -339,6 +346,7 @@ export interface FileRoutesByTo {
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
'/m': typeof MobileMobileLayoutMIndexRoute
'/old/ocp': typeof OldOldOcpIndexRoute
'/old/quality': typeof OldOldQualityIndexRoute
'/old/rfid': typeof OldOldRfidIndexRoute
'/admin/modules': typeof AppAdminLayoutAdminSystemModulesRoute
'/admin/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
@@ -383,6 +391,7 @@ export interface FileRoutesById {
'/_app/_forklifts/forklifts/': typeof AppForkliftsForkliftsIndexRoute
'/_mobile/_mobileLayout/m/': typeof MobileMobileLayoutMIndexRoute
'/_old/old/ocp/': typeof OldOldOcpIndexRoute
'/_old/old/quality/': typeof OldOldQualityIndexRoute
'/_old/old/rfid/': typeof OldOldRfidIndexRoute
'/_app/_adminLayout/admin/_system/modules': typeof AppAdminLayoutAdminSystemModulesRoute
'/_app/_adminLayout/admin/_system/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
@@ -422,6 +431,7 @@ export interface FileRouteTypes {
| '/forklifts'
| '/m'
| '/old/ocp'
| '/old/quality'
| '/old/rfid'
| '/admin/modules'
| '/admin/settings'
@@ -458,6 +468,7 @@ export interface FileRouteTypes {
| '/forklifts'
| '/m'
| '/old/ocp'
| '/old/quality'
| '/old/rfid'
| '/admin/modules'
| '/admin/settings'
@@ -501,6 +512,7 @@ export interface FileRouteTypes {
| '/_app/_forklifts/forklifts/'
| '/_mobile/_mobileLayout/m/'
| '/_old/old/ocp/'
| '/_old/old/quality/'
| '/_old/old/rfid/'
| '/_app/_adminLayout/admin/_system/modules'
| '/_app/_adminLayout/admin/_system/settings'
@@ -602,6 +614,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof OldOldRfidIndexRouteImport
parentRoute: typeof OldOldRouteRoute
}
'/_old/old/quality/': {
id: '/_old/old/quality/'
path: '/quality'
fullPath: '/old/quality'
preLoaderRoute: typeof OldOldQualityIndexRouteImport
parentRoute: typeof OldOldRouteRoute
}
'/_old/old/ocp/': {
id: '/_old/old/ocp/'
path: '/ocp'
@@ -949,6 +968,7 @@ const MobileMobileLayoutRouteRouteWithChildren =
interface OldOldRouteRouteChildren {
OldOldIndexRoute: typeof OldOldIndexRoute
OldOldOcpIndexRoute: typeof OldOldOcpIndexRoute
OldOldQualityIndexRoute: typeof OldOldQualityIndexRoute
OldOldRfidIndexRoute: typeof OldOldRfidIndexRoute
OldOldlogisticsSiloAdjustmentsHistRoute: typeof OldOldlogisticsSiloAdjustmentsHistRoute
OldOldlogisticsBarcodegenIndexRoute: typeof OldOldlogisticsBarcodegenIndexRoute
@@ -964,6 +984,7 @@ interface OldOldRouteRouteChildren {
const OldOldRouteRouteChildren: OldOldRouteRouteChildren = {
OldOldIndexRoute: OldOldIndexRoute,
OldOldOcpIndexRoute: OldOldOcpIndexRoute,
OldOldQualityIndexRoute: OldOldQualityIndexRoute,
OldOldRfidIndexRoute: OldOldRfidIndexRoute,
OldOldlogisticsSiloAdjustmentsHistRoute:
OldOldlogisticsSiloAdjustmentsHistRoute,

View File

@@ -29,6 +29,7 @@ export function AddCards() {
<Cards name={"ppoo"} inventory />
<Cards name={"inv-empty"} rowType={"empty"} />
<Cards name={"inv-fg"} rowType={"fg"} />
<Cards name={"qualityCard"} />
</div>
<div className="">
<Cards name={"inv-materials"} rowType={"materials"} />

View File

@@ -1,4 +1,5 @@
import { useCardStore } from "../../-lib/store/useCardStore";
import Quality from "../../quality/-components/WarehouseCard";
import INVCheckCard from "../logistics/warehouse/InventoryCard";
import OpenOrders from "../logistics/warehouse/openOrders";
import PPOO from "../logistics/warehouse/PPOOCard";
@@ -7,6 +8,7 @@ const componentsMap: any = {
ppoo: PPOO,
inv: INVCheckCard,
openOrder: OpenOrders,
qualityCard: Quality,
//QualityRequest,
};
@@ -26,14 +28,21 @@ export default function DashBoard() {
<Component age={a.age} type={a.rowType} />
</div>
);
} else {
//console.log(name.split("-")[0], a);
}
if (name === "qualityCard") {
return (
<div key={a.name} className="col-span-3">
<Component data={a} />
<div key={a.name} className="col-span-6">
<Component />
</div>
);
}
//console.log(name.split("-")[0], a);
return (
<div key={a.name} className="col-span-3">
<Component data={a} />
</div>
);
})}
</div>
);

View File

@@ -17,6 +17,7 @@ import { AdminSideBar } from "./side-components/admin";
import { Header } from "./side-components/header";
import { LogisticsSideBar } from "./side-components/logistics";
import { ProductionSideBar } from "./side-components/production";
import { QualitySideBar } from "./side-components/quality";
export function AppSidebar() {
const { session } = useAuth();
@@ -31,11 +32,12 @@ export function AppSidebar() {
{/* userAccess("logistics", ["systemAdmin", "admin","manager","viewer"]) */}
<LogisticsSideBar user={session?.user as any} userRoles={userRoles} />
<QualitySideBar user={session?.user as any} userRoles={userRoles} />
{userAccess(null, ["systemAdmin"]) && (
<>
{/* <ForkliftSideBar />
<EomSideBar />
<QualitySideBar /> */}
*/}
<AdminSideBar />
</>
)}

View File

@@ -1,4 +1,6 @@
import { Printer } from "lucide-react";
import { Cat, Printer } from "lucide-react";
import type { UserRoles } from "@/lib/authClient";
import type { User } from "@/types";
import {
SidebarGroup,
SidebarGroupContent,
@@ -7,31 +9,49 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "../../../../../../components/ui/sidebar";
import { useModuleStore } from "../../../-lib/store/useModuleStore";
import { hasPageAccess } from "../../../-utils/userAccess";
const items = [
{
title: "Qaulity Request",
url: "#",
icon: Printer,
},
];
const iconMap: any = {
Printer: Printer,
Cat: Cat,
};
export function QualitySideBar({
user,
userRoles,
}: {
user: User | null;
userRoles: UserRoles[] | null;
}) {
const { modules } = useModuleStore();
const items = modules?.filter((m) => m.category === "quality" && m.active);
const userUpdate = { ...user, roles: userRoles };
export function QualitySideBar() {
return (
<SidebarGroup>
<SidebarGroupLabel>Quality</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
{items.map((item) => {
if (!item.active) return;
const Icon = iconMap[item.icon === "" ? "Cat" : item.icon];
return (
<SidebarMenuItem key={item.module_id}>
<>
{hasPageAccess(userUpdate as any, item.roles, item.name) && (
<SidebarMenuButton asChild>
<a href={item.link}>
<Icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
)}
</>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>

View File

@@ -0,0 +1,20 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getPallets() {
return queryOptions({
queryKey: ["getPallets"],
queryFn: () => fetch(),
//enabled:
staleTime: 1000,
refetchInterval: 60 * 1000,
refetchOnWindowFocus: true,
});
}
const fetch = async () => {
const { data } = await axios.get(`/lst/old/api/quality/getrequest`);
// if we are not localhost ignore the devDir setting.
//const url: string = window.location.host.split(":")[0];
return data.data ?? [];
};

View File

@@ -0,0 +1,331 @@
import { useQuery } from "@tanstack/react-query";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import axios from "axios";
import { ArrowDown, ArrowUp } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useAuth, userAccess } from "@/lib/authClient";
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
import { getPallets } from "../../-utils/querys/quality/getPallets";
type Pallets = {
request_id: string;
article: string;
description: string;
runningNr: string;
lotNr: string;
warehouseAtRequest: string;
locationAtRequest: string;
warehouseMovedTo: string;
locationMovedTo: string;
durationToMove: null;
qualityDurationToInspect: number;
returnDurationToInspect: number;
locationDropOff: string;
palletStatus: number;
palletStatusText: string;
palletRequest: number;
priority: number;
add_date: Date;
add_user: string;
upd_date: Date;
upd_user: string;
};
export default function QualityRequest() {
const { data, isLoading, refetch } = useQuery(getPallets());
const columnHelper = createColumnHelper<Pallets>();
const { session } = useAuth();
const navigate = useNavigate();
const router = useRouterState();
const currentPath = router.location.href;
const palletCompleted = async (e: any) => {
if (!session || !session.user) {
toast.error("You are allowed to do this unless you are logged in");
navigate({ to: "/login", search: { redirect: currentPath } });
return;
}
const data = {
username: session?.user.username,
runningNr: Number(e.original.runningNr),
palletStatusText: "return",
};
try {
const res = await axios.post("/lst/old/api/quality/newrequest", data);
//console.log(res.data);
if (res.data.success) {
toast.success(res.data.message);
refetch();
}
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
console.log(error);
toast.error("Encountered and error please try again");
}
};
const columns = [
columnHelper.accessor("article", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Article</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("description", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Alias</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("runningNr", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Running Number</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("lotNr", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Lot Number</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("locationAtRequest", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Location At Request</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("locationDropOff", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Drop off Location</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ getValue }) => {
return (
<>
<span>{getValue() ? getValue() : "missing dropoff location"}</span>
</>
);
},
}),
columnHelper.accessor("palletStatusText", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Pallet Status</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("palletRequest", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Check Completed</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ row }) => {
// if pending
const okToCompleteStats = [2, 4, 5];
return (
<>
{okToCompleteStats.includes(row.original.palletStatus) ? (
<Button variant="outline" onClick={() => palletCompleted(row)}>
Check Complete
</Button>
) : (
<span>Pending to be completed</span>
)}
</>
);
},
}),
];
let adminColumns: any = [];
if (userAccess("quality", ["systemAdmin", "admin", "supervisor"])) {
adminColumns = [
...columns,
columnHelper.accessor("priority", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
<span className="flex flex-row gap-2">Priority</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ row, getValue }) => {
// if pending
const [p, setP] = useState(`${getValue()}`);
console.log(getValue());
return (
<>
<Select
value={p}
onValueChange={async (value) => {
setP(value);
const data = {
username: session?.user.username,
runningNr: Number(row.original.runningNr),
priority: value,
};
try {
const res = await axios.post(
"/lst/old/api/quality/newrequest",
data,
);
//console.log(res.data);
if (res.data.success) {
toast.success(res.data.message);
refetch();
}
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
console.log(error);
toast.error("Encountered and error please try again");
}
}}
>
<SelectTrigger
// className={cn(
// "w-[100px]",
// active
// ? "border-green-500 text-green-600"
// : "border-gray-400 text-gray-500",
// )}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Urgent</SelectItem>
<SelectItem value="2">High</SelectItem>
<SelectItem value="3">Medium</SelectItem>
<SelectItem value="4">Low</SelectItem>
</SelectContent>
</Select>
</>
);
},
}),
];
}
if (isLoading) {
return <div className="m-auto">Loading user data</div>;
}
return <TableNoExpand data={data} columns={adminColumns} />;
}

View File

@@ -0,0 +1,161 @@
import { useQuery } from "@tanstack/react-query";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import axios from "axios";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useAuth } from "@/lib/authClient";
import { useAppForm } from "@/lib/formStuff";
import { getPallets } from "../../-utils/querys/quality/getPallets";
const areaSelection = [
{ value: "rework", label: "Rework" },
{ value: "holdArea", label: "Hold Area" },
{ value: "inspectionArea", label: "Inspection Area" },
];
export default function RequestPallet() {
const { session } = useAuth();
const navigate = useNavigate();
const router = useRouterState();
const { refetch } = useQuery(getPallets());
const currentPath = router.location.href;
const form = useAppForm({
defaultValues: {
username: "",
runningNr: 0,
moveTo: "",
},
onSubmit: async ({ value }) => {
if (!session || !session.user) {
toast.error("You are allowed to do this unless you are logged in");
navigate({ to: "/login", search: { redirect: currentPath } });
return;
}
if (value.runningNr === 0) {
return toast.error(
"You did not change the running number, please add a valid running number",
);
}
const postData = {
runningNr: Number(value.runningNr),
moveTo: value.moveTo,
username: session?.user.username,
};
console.log(postData);
try {
const res = await axios.post(
"/lst/old/api/quality/newrequest",
postData,
);
console.log(res.data);
if (res.data.success) {
toast.success(res.data.message);
form.reset();
refetch();
}
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
console.log(error);
toast.error("Encountered and error please try again");
}
},
});
return (
<Dialog>
<DialogTrigger>
<Button variant="outline">Request Pallet</Button>
</DialogTrigger>
<DialogContent className="w-5/6 m-2">
<DialogHeader>
<DialogTitle>
Request a pallet for the warehouse to retrieve{" "}
</DialogTitle>
<DialogDescription>
This form is used to request a pallet to be brought up to be
inspected, reworked, placed on hold, or please fill out and the
warehouse will see and bring to the designated location. Once
finished click done or cancel.
</DialogDescription>
</DialogHeader>
<div>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div className="w-5/6 m-2">
<form.AppField
name="runningNr"
children={(field) => (
<field.InputField
label="Pallet Running Number"
inputType="number"
required={true}
/>
)}
/>
</div>
<div className="w-5/6 m-2">
<form.AppField
name="moveTo"
// listeners={{
// onChange: ({ value }) => {
// onValueChange(value);
// },
// }}
children={(field) => (
<field.SelectField
label="Select Area"
placeholder="Quality Areas"
options={areaSelection}
/>
)}
/>
</div>
<div className="w-5/6 m-2">
<DialogFooter>
<DialogClose asChild>
<Button
variant="outline"
onClick={() => {
form.reset();
}}
>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button
variant="outline"
onClick={() => {
form.reset();
}}
>
Done
</Button>
</DialogClose>
<Button type="submit">Submit</Button>
</DialogFooter>
</div>
</form>
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,121 @@
//import { LstCard } from "@/components/extendedUI/LstCard";
import { useQuery } from "@tanstack/react-query";
import { createColumnHelper } from "@tanstack/react-table";
import { ArrowDown, ArrowUp } from "lucide-react";
import { Button } from "@/components/ui/button";
import { CardHeader } from "@/components/ui/card";
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
import { LstCard } from "../../-components/extendedUi/LstCard";
import { getPallets } from "../../-utils/querys/quality/getPallets";
//import { CircleX } from "lucide-react";
//import { Suspense } from "react";
//import { toast } from "sonner";
type Pallets = {
article: string;
runningNr: string;
warehouseAtRequest: string;
locationAtRequest: string;
locationDropOff: string;
};
export default function PPOO() {
//{ style = {} }
const { data, isError, isLoading } = useQuery(getPallets());
const columnHelper = createColumnHelper<Pallets>();
if (isLoading) return <div>Loading pallet data...</div>;
if (isError) {
return (
<div>
<p>There was an error getting the pallets.</p>
</div>
);
}
const monitoring = [1, 4, 6, 7];
const filteredData = data.filter((n: any) =>
monitoring.includes(n.palletStatus),
);
const columns = [
columnHelper.accessor("article", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Article</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("runningNr", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Running Number</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("locationAtRequest", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Location</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
columnHelper.accessor("locationDropOff", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Drop off at</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
}),
];
return (
<LstCard className="w-fit">
<CardHeader>
Quality Request Card Total Pallets {filteredData.length}
</CardHeader>
<TableNoExpand data={filteredData} columns={columns} />
</LstCard>
);
}

View File

@@ -0,0 +1,44 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { checkUserAccess } from "@/lib/authClient";
import QualityRequest from "./-components/QualityRequest";
import RequestPallet from "./-components/RequestPallet";
export const Route = createFileRoute("/_old/old/quality/")({
component: RouteComponent,
beforeLoad: async () => {
const auth = await checkUserAccess({
allowedRoles: [
"systemAdmin",
"technician",
"admin",
"manager",
"supervisor",
],
moduleName: "quality", // optional
});
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.pathname + location.search,
},
});
}
},
});
function RouteComponent() {
return (
<div className="flex flex-col m-2">
<div className="flex justify-items-start ml-2">
<RequestPallet />
</div>
<QualityRequest />
</div>
);
}

View File

@@ -0,0 +1 @@
ALTER TABLE "qualityRequest" ADD COLUMN "priority" integer DEFAULT 4;

File diff suppressed because it is too large Load Diff

View File

@@ -533,6 +533,13 @@
"when": 1762983466464,
"tag": "0075_tan_unicorn",
"breakpoints": true
},
{
"idx": 76,
"version": "7",
"when": 1763134709763,
"tag": "0076_stiff_champions",
"breakpoints": true
}
]
}

View File

@@ -1,45 +1,46 @@
import {
text,
pgTable,
numeric,
index,
timestamp,
boolean,
uuid,
uniqueIndex,
integer,
boolean,
index,
integer,
numeric,
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const qualityRequest = pgTable(
"qualityRequest",
{
request_id: uuid("request_id").defaultRandom().primaryKey(),
article: numeric("article"),
description: text("description"),
runningNr: text("runningNr"),
lotNr: numeric("lotNr"),
warehouseAtRequest: text("warehouseAtRequest"),
locationAtRequest: text("locationAtRequest"),
warehouseMovedTo: text("warehouseMovedTo"),
locationMovedTo: text("locationMovedTo"),
durationToMove: integer("durationToMove"),
qualityDurationToInspect: integer("qualityDurationToInspect"),
returnDurationToInspect: integer("returnDurationToInspect"),
locationDropOff: text("locationDropOff"),
palletStatus: integer("palletStatus"),
palletStatusText: text("palletStatusText"),
palletRequest: integer("palletRequest"),
add_date: timestamp("add_date").defaultNow(),
add_user: text("add_user").default("LST"),
upd_date: timestamp("upd_date").defaultNow(),
upd_user: text("upd_user").default("LST"),
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
// uniqueIndex("role_name").on(table.name),
]
"qualityRequest",
{
request_id: uuid("request_id").defaultRandom().primaryKey(),
article: numeric("article"),
description: text("description"),
runningNr: text("runningNr"),
lotNr: numeric("lotNr"),
warehouseAtRequest: text("warehouseAtRequest"),
locationAtRequest: text("locationAtRequest"),
warehouseMovedTo: text("warehouseMovedTo"),
locationMovedTo: text("locationMovedTo"),
durationToMove: integer("durationToMove"),
qualityDurationToInspect: integer("qualityDurationToInspect"),
returnDurationToInspect: integer("returnDurationToInspect"),
locationDropOff: text("locationDropOff"),
palletStatus: integer("palletStatus"),
palletStatusText: text("palletStatusText"),
palletRequest: integer("palletRequest"),
priority: integer("priority").default(4), // 1,2,3,4
add_date: timestamp("add_date").defaultNow(),
add_user: text("add_user").default("LST"),
upd_date: timestamp("upd_date").defaultNow(),
upd_user: text("upd_user").default("LST"),
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
// uniqueIndex("role_name").on(table.name),
],
);
// Schema for inserting a user - can be used to validate API requests

View File

@@ -6,9 +6,26 @@ import {
parseISO,
startOfWeek,
} from "date-fns";
import { db } from "../../../../../database/dbclient.js";
import { invHistoricalData } from "../../../../../database/schema/historicalINV.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { materialPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
import {
materialPerDay,
materialPurchasesPerDay,
} from "../../../sqlServer/querys/dataMart/materialPerDay.js";
import { materialPurchases } from "./materialPurchases.js";
import { buildInventoryTimeline } from "./materialWithInv.js";
const getInv = async () => {
const { data, error } = await tryCatch(db.select().from(invHistoricalData));
if (error) {
return [];
}
return data;
};
function toDate(val: any) {
if (val instanceof Date) return val;
@@ -16,7 +33,7 @@ function toDate(val: any) {
return new Date(val);
}
export function sumByMaterialAndWeek(data: any) {
function sumByMaterialAndWeek(data: any) {
/** @type {Record<string, Record<string, number>>} */
const grouped: any = {};
@@ -80,9 +97,45 @@ export default async function materialPerDayCheck() {
};
}
// purchases
const { data: p, error: pe } = (await tryCatch(
query(
materialPurchasesPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!data.success) {
return {
success: false,
message: data.message,
data: [],
};
}
const openingInventory: Record<string, number> = {};
const inventoryRows = await getInv();
for (const row of inventoryRows) {
openingInventory[row.article] = Number(row.total_QTY) || 0;
}
return {
success: true,
message: "material data",
data: sumByMaterialAndWeek(data.data),
data: buildInventoryTimeline(
sumByMaterialAndWeek(data.data) as any,
openingInventory,
),
};
}

View File

@@ -0,0 +1,38 @@
import { formatISO, parseISO, startOfWeek } from "date-fns";
function toDate(val: any) {
if (val instanceof Date) return val;
if (typeof val === "string") return parseISO(val.replace(" ", "T"));
return new Date(val);
}
export const materialPurchases = async (data: any) => {
/** @type {Record<string, Record<string, number>>} */
const grouped: any = {};
for (const r of data) {
const mat = String(r.MaterialHumanReadableId);
const d = toDate(r.CalDate);
const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), {
representation: "date",
});
grouped[mat] ??= {};
grouped[mat][week] ??= 0;
grouped[mat][week] += Number(r.DailyMaterialDemand) || 0;
}
const result = [];
for (const [mat, weeks] of Object.entries(grouped)) {
// @ts-ignore
for (const [week, total] of Object.entries(weeks)) {
result.push({
MaterialHumanReadableId: mat,
WeekStart: week,
WeeklyDemand: Number(total).toFixed(2),
});
}
}
return result;
};

View File

@@ -0,0 +1,64 @@
export const buildInventoryTimeline = (
weeklyDemand: Array<{
MaterialHumanReadableId: string;
WeekStart: string;
WeeklyDemand: number;
}>,
opening: Record<string, number>,
) => {
// group weekly demand by material
const grouped: Record<
string,
Array<{ WeekStart: string; Demand: number }>
> = {};
for (const d of weeklyDemand) {
const mat = d.MaterialHumanReadableId;
grouped[mat] ??= [];
grouped[mat].push({
WeekStart: d.WeekStart,
Demand: Number(d.WeeklyDemand),
});
}
// sort weeks chronologically per material
for (const mat of Object.keys(grouped)) {
grouped[mat].sort(
(a, b) =>
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
);
}
const result: Array<{
MaterialHumanReadableId: string;
WeekStart: string;
OpeningInventory: number;
Consumption: number;
ClosingInventory: number;
}> = [];
for (const [mat, weeks] of Object.entries(grouped)) {
// get starting inventory from the ERP result
let inv = opening[mat] ?? 0;
for (const w of weeks) {
const week = w.WeekStart;
const demand = Number(w.Demand);
const openingInv = inv;
const closingInv = openingInv - demand;
result.push({
MaterialHumanReadableId: mat,
WeekStart: week,
OpeningInventory: Number(openingInv.toFixed(2)),
Consumption: Number(demand.toFixed(2)),
ClosingInventory: Number(closingInv.toFixed(2)),
});
inv = closingInv;
}
}
return result;
};

View File

@@ -2,8 +2,8 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { apiHit } from "../../../globalUtils/apiHits.js";
import { responses } from "../../../globalUtils/routeDefs/responses.js";
import materialPerDayCheck from "../controller/materialsPerDay/materialPerDay.js";
import fifoIndexCheck from "../controller/notifications/fifoIndex.js";
import materialPerDayCheck from "../controller/notifications/materialPerDay.js";
const app = new OpenAPIHono({ strict: false });

View File

@@ -1,5 +1,6 @@
import { differenceInMinutes } from "date-fns";
import { eq, sql } from "drizzle-orm";
import { priority } from "st-ethernet-ip/dist/enip/cip/connection-manager/index.js";
import { db } from "../../../../database/dbclient.js";
import { qualityRequest } from "../../../../database/schema/qualityRequest.js";
import { timeZoneFix } from "../../../globalUtils/timeZoneFix.js";
@@ -39,11 +40,13 @@ export const addNewPallet = async (data: any) => {
const palletData: any = c;
// if the pallet exist then tell the user to check on it
const pStatus = [1, 4, 6];
if (palletData && pStatus.includes(palletData[0]?.palletStatus)) {
return {
success: false,
message: `Running number ${data.runningNr} is already pending or reactivated please follow up with the warehouse team on status to be moved.`,
};
if (!data.priority) {
if (palletData && pStatus.includes(palletData[0]?.palletStatus)) {
return {
success: false,
message: `Running number ${data.runningNr} is already pending or reactivated please follow up with the warehouse team on status to be moved.`,
};
}
}
// update the existing pallet if already in the system
@@ -61,25 +64,36 @@ export const addNewPallet = async (data: any) => {
data: pe,
};
}
const pData = {
warehouseAtRequest: p[0].warehouseAtRequest,
locationAtRequest: p[0].locationAtRequest,
warehouseMovedTo: null,
locationMovedTo: null,
palletStatus: data.palletStatusText === "return" ? 6 : 4,
//durationToMove: 0,
palletStatusText:
data.palletStatusText === "return" ? "return" : "reactivated",
qualityDurationToInspect:
data.palletStatusText === "return"
? differenceInMinutes(new Date(Date.now()), p[0].lastMove)
: 0,
locationDropOff:
data.palletStatusText === "return" ? "Return to warhouse" : "",
palletRequest: palletData[0].palletStatus + 1,
upd_user: data.user,
upd_date: sql`NOW()`,
};
let pData = {};
if (data.priority) {
pData = {
priority: data.priority,
upd_user: data.user,
upd_date: sql`NOW()`,
};
} else {
pData = {
warehouseAtRequest: p[0].warehouseAtRequest,
locationAtRequest: p[0].locationAtRequest,
warehouseMovedTo: null,
locationMovedTo: null,
palletStatus: data.palletStatusText === "return" ? 6 : 4,
//durationToMove: 0,
palletStatusText:
data.palletStatusText === "return" ? "return" : "reactivated",
qualityDurationToInspect:
data.palletStatusText === "return"
? differenceInMinutes(new Date(Date.now()), p[0].lastMove)
: 0,
locationDropOff:
data.palletStatusText === "return" ? "Return to warhouse" : "",
palletRequest: palletData[0].palletStatus + 1,
upd_user: data.user,
upd_date: sql`NOW()`,
};
}
const { data: u, error } = await tryCatch(
db

View File

@@ -1,25 +1,28 @@
import { desc } from "drizzle-orm";
import { asc, desc } from "drizzle-orm";
import { db } from "../../../../database/dbclient.js";
import { qualityRequest } from "../../../../database/schema/qualityRequest.js";
import { tryCatch } from "../../../globalUtils/tryCatch.js";
import qualityBlockingMonitor from "../../notifications/controller/notifications/qualityBlocking.js";
export const getRequest = async () => {
const { data, error } = await tryCatch(
db.select().from(qualityRequest).orderBy(desc(qualityRequest.add_date))
);
const { data, error } = await tryCatch(
db
.select()
.from(qualityRequest)
.orderBy(asc(qualityRequest.priority), asc(qualityRequest.add_date)),
);
if (error) {
return {
success: false,
message: "There was an error getting the quality request",
data: error,
};
}
if (error) {
return {
success: false,
message: "There was an error getting the quality request",
data: error,
};
}
return {
success: true,
message: "Quality request pallets.",
data,
};
return {
success: true,
message: "Quality request pallets.",
data,
};
};

View File

@@ -105,7 +105,7 @@ export const qualityCycle = async () => {
);
} else {
createLog(
"info",
"debug",
"lst",
"quality",
`Pallet ${
@@ -119,7 +119,7 @@ export const qualityCycle = async () => {
}
await delay(150);
} else {
createLog("info", "lst", "quality", "nothing to update");
//createLog("info", "lst", "quality", "nothing to update");
}
},
5 * 60 * 1000,

View File

@@ -113,3 +113,21 @@ ORDER BY
n.ProductionLotHumanReadableId
OPTION (MAXRECURSION 0);
`;
export const materialPurchasesPerDay = `
use AlplaPROD_test1
declare @startDate nvarchar(max) = '[startDate]'
declare @endDate nvarchar(max) = '[endDate]'
SELECT
[IdBestellung] as purhcaseOrder
,[IdArtikelVarianten]
,convert(date, [BestellDatum], 120) as orderDate
,convert(date, [Lieferdatum], 120) as deliveryDate
,[BestellMenge] as qty
,[BestellMengeVPK] as pallets
FROM [dbo].[T_Bestellpositionen]
where PositionsStatus = 2 and convert(date, [Lieferdatum], 120) between @startDate and @endDate --and IdBestellung in( 5535,5534,5513)
`;

43
mobileLst/.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
# generated native folders
/ios
/android

50
mobileLst/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Welcome to your Expo app 👋
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
## Get started
1. Install dependencies
```bash
npm install
```
2. Start the app
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.

71
mobileLst/app.json Normal file
View File

@@ -0,0 +1,71 @@
{
"expo": {
"name": "LSTScanner",
"slug": "lst-scanner-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"scheme": "lstscanner",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"updates": {
"enabled": true,
"url": "http://10.193.0.56:4000/api/mobile/updates",
"fallbackToCacheTimeout": 30000,
"codeSigningCertificate": "./certs/certificate.pem",
"codeSigningMetadata": {
"keyid": "self-hosted",
"alg": "rsa-v1_5-sha256"
}
},
"runtimeVersion": "1.0.0",
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"package": "com.company.lstscanner",
"versionCode": 1,
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
"./plugins/androidCustomizations",
[
"expo-splash-screen",
{
"image": "./assets/icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
],
[
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true
},
"ios": {}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}

View File

@@ -0,0 +1,5 @@
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}

122
mobileLst/app/index.tsx Normal file
View File

@@ -0,0 +1,122 @@
import React, { useState } from "react";
import {
Button,
FlatList,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import * as Updates from 'expo-updates';
export default function App() {
const [buffer, setBuffer] = useState("");
const [items, setItems] = useState<string[]>([]);
const [status, setStatus] = useState("");
const [updates, setUpdates] = useState("")
const handleChange = (text: string) => {
// Scanner "types" characters then appends Enter (\n)
if (text.endsWith("\n")) {
const code = text.trim(); // Remove newline
handleScan(code);
setBuffer(""); // Reset for next scan
} else {
setBuffer(text);
}
};
async function checkServerUpdate() {
setUpdates('Checking for updates...');
try {
const res = await Updates.checkForUpdateAsync();
if (res.isAvailable) {
setUpdates('Update available! Fetching and reloading...');
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
} else {
setUpdates('No new update available.');
}
} catch (e:any) {
setUpdates(`Update check failed: ${e.message}`);
}
}
const handleScan = (code: string) => {
//console.log("Scanned:", code);
if (code.toUpperCase().startsWith("LOC")) {
console.log("Triggered relocate to lane");
performRelocate(code);
} else {
setItems((prev) => [...prev, code]);
setStatus(`Added: ${code}`);
}
};
const performRelocate = (locationCode: string) => {
if (items.length === 0) {
setStatus(`Relocate to ${locationCode} requested, but no items`);
return;
}
// Do your API call or whatever the "relocate" means
console.log("Relocating", items, "to", locationCode);
setStatus(`Moved ${items.length} items to ${locationCode}`);
setItems([]);
};
return (
<View style={styles.container}>
<Button
title="Clear Items"
onPress={() => {
setItems([]);
}}
/>
<Text style={styles.title}>Scan Barcodes</Text>
{/* The working input you already have */}
<TextInput
autoFocus
value={buffer}
onChangeText={handleChange}
showSoftInputOnFocus={false}
keyboardType="visible-password"
style={{
position: "absolute",
opacity: 0, // invisible
height: 0, // takes no space
width: 0,
}}
/>
<Pressable onPress={checkServerUpdate}style={{
marginVertical: 12,
padding: 12,
backgroundColor: '#007AFF',
borderRadius: 6,
}}>
<Text>Check Update</Text>
</Pressable>
<Text>Update Data</Text>
<Text>{updates}</Text>
<FlatList
data={items}
keyExtractor={(_, i) => i.toString()}
renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
/>
<Text style={styles.status}>{status}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
title: { fontWeight: "bold", fontSize: 20 },
item: { paddingVertical: 4, fontSize: 16 },
status: { marginTop: 20, fontStyle: "italic" },
});

BIN
mobileLst/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIQF/BpAuRICIFJboP3h6vDRzANBgkqhkiG9w0BAQ0FADA1
MQswCQYDVQQGEwJBVDEOMAwGA1UEChMFQUxQTEExFjAUBgNVBAMTDUFMUExBIFJv
b3QgQ0EwIBcNMjAxMDI3MTIzNTUxWhgPMjA1MDEwMjcxMjQ1NTBaMDUxCzAJBgNV
BAYTAkFUMQ4wDAYDVQQKEwVBTFBMQTEWMBQGA1UEAxMNQUxQTEEgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKt7ZejwqZz7AvCN7zCSAxVZ
Htw/WKX3UezouDBdWY6UQh54POCTEwv5BZ8wiAp1GvkJSbUVbB/9VBNZtopAGFkp
pB5MAjoPY/PWir01vxymgDXDOEn8Big/ahUt7MQI02UwpQ/j0fwxcVtWOxyoKN1O
E1yR3vJfXOwGZ4y8zIN2ErBg+z1CUXUS/UxfpmAdQ6vJgYL6pjcCxKOHPdYo7a2H
RD3Ck6qEcD0K89/Cx3R5Q3poc3YKM3A3xnIb43wUhzIXDdPQtIxMA4dA/Tz8LAWE
ZQ/6bkOpDFPZMxy4x5mcIMo6Ak0CIOFLoWfnukcpUbzgfcDInot3X5YX5NboDdPV
yidWzyT3pM5AO4Kk6by3Y9KiiRmj83k3F7TaiwqU9o9Q5M3U14ZCV2D4coCrMYzw
VHBFSV2LiH8qEaV4DXBO36Ty6TQBGbFUJUtxbD9pDOoOab3KEda7JtEbFolZyQAk
21hyoPs7DmhbVmV5ih6QEKNHOt+7U9UeGy39kbEShVVoF0VaC+7J8SyRjO7/QawW
JjFOkF8QSPGAH7l8iGKwtvcZ39VmbJhas16KlxmrlHxRL5Gms4IXYuaqSPsLIAcr
SfOdf2Xq2Up4SYnPHW5Kg2FjHTMRFF0XlMRoXcqD4tTXa9w2IGpQ+EoHeEfNga49
Xw8AaWx6EDzLdyzwXWr1AgMBAAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBTVTBh/edUJ6IgeZ29ZguyqoYipdjAQBgkrBgEEAYI3
FQEEAwIBADANBgkqhkiG9w0BAQ0FAAOCAgEAm/HO1AIJSX4ic8Soph2pak6ztxNo
crd8jVkGsRI2l1vmVBhvsSZkDUGiCnTVm3bYrHEEgmZlp2uTdMd8t5ufjM55VWZ2
gdOuuqha8cXDuwqsOzhX/IGdARrRJFABHO4amdQpMBDxPGswIjXQfKc0UeFQ+/lr
Fe0lmqegIKlpogEydz6qMQ6BnsUI8WQiakggVkl9flpsCR7lk8gWoBHQebGs2TWt
7IA/RRWm5S2OSToTh2E38MuRaiAwwaJNch2WflixJyWUVi5FbZ0l8ZGzxTvCcDgn
UC4KpyeId3fONANQrSmCz6X+ORyPbzLiIojG+y65h/cKw7cMI99+sU3s2akVAGpo
rYH1MU4KAqyChp71Xr+V0bvGMLQKuFFn/6xQ/MF24Btxjcp3ODVEyQ+lNMFfg5pB
RuhXiYAMZJSUyJGRLOI5RFBB2l8rBRRe7epsjPKrfwt30YXX7hO2Uo1JoL1ov1Um
Vt2SpB3b2Kk4xHwp2PCAfA+VtB8nJVC49JdXi7rlYh5IUAUexrsvMPLeHlDrldYb
1TcSeglLTIGFQBxElG4I7yDaWnIzTmlY/zDiK4h4l/rzyWo0NVu2KLnT0iAZ2/w5
AjAeIyKnFP7PXYQwAdiW/B/cwfZTrsDaxq8PkZjiMznVzG+EQSgmcqT0z6aNJmoU
3z/B7cuoI5Eu0OU=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -0,0 +1,41 @@
import React, { useEffect, useRef, useState } from "react";
import { Text, TextInput, View } from "react-native";
export default function HiddenScannerListener({ onScan }: { onScan: any }) {
const [buffer, setBuffer] = useState("");
const inputRef = useRef<TextInput>(null);
// Keep focusing the invisible input
useEffect(() => {
const timer = setInterval(() => {
if (inputRef.current) inputRef.current.focus();
}, 500);
return () => clearInterval(timer);
}, []);
const handleChange = (text: string) => {
// Most scanners append '\n' (Enter) or '\t'; trim and send
if (text.endsWith("\n") || text.endsWith("\t")) {
const code = text.trim();
if (code) onScan?.(code);
setBuffer("");
} else {
setBuffer(text);
}
};
return (
<TextInput
ref={inputRef}
value={buffer}
onChangeText={handleChange}
autoFocus
style={{
position: "absolute",
opacity: 0, // invisible
height: 0, // takes no space
width: 0,
}}
/>
);
}

View File

@@ -0,0 +1,10 @@
// https://docs.expo.dev/guides/using-eslint/
const { defineConfig } = require('eslint/config');
const expoConfig = require('eslint-config-expo/flat');
module.exports = defineConfig([
expoConfig,
{
ignores: ['dist/*'],
},
]);

13666
mobileLst/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

58
mobileLst/package.json Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "mobilelst",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"build:clean": "expo prebuild --clean && cd android && gradlew.bat assembleRelease && npm run ",
"build:android": "cd android && gradlew.bat assembleRelease",
"build:ota": "set EXPO_NO_EAS_UPDATE=1 && npx expo export --platform android --output-dir dist --experimental-bundle",
"update": "adb install android/app/build/outputs/apk/release/app-release.apk",
"debug": "adb logcat | Select-String \"Expo"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"axios": "^1.13.2",
"expo": "~54.0.23",
"expo-build-properties": "~1.0.9",
"expo-camera": "~17.0.9",
"expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.10",
"expo-linking": "~8.0.8",
"expo-router": "~6.0.14",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.8",
"expo-updates": "~29.0.12",
"expo-web-browser": "~15.0.9",
"expo-zebra-scanner": "^6.0.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2"
},
"private": true
}

View File

@@ -0,0 +1,94 @@
const {
withAndroidManifest,
withDangerousMod,
withGradleProperties,
} = require("@expo/config-plugins");
const fs = require("fs");
const path = require("path");
function withCustomAndroidConfig(config) {
// Copy cert files
config = withDangerousMod(config, [
"android",
async (config) => {
const projectRoot = config.modRequest.projectRoot;
const certSourceDir = path.join(projectRoot, "certFiles");
const rawDestDir = path.join(
projectRoot,
"android/app/src/main/res/raw"
);
const xmlDestDir = path.join(
projectRoot,
"android/app/src/main/res/xml"
);
// Create directories if they don't exist
if (!fs.existsSync(rawDestDir)) {
fs.mkdirSync(rawDestDir, { recursive: true });
}
if (!fs.existsSync(xmlDestDir)) {
fs.mkdirSync(xmlDestDir, { recursive: true });
}
// Copy your cert files
if (fs.existsSync(path.join(certSourceDir, "raw"))) {
fs.cpSync(path.join(certSourceDir, "raw"), rawDestDir, {
recursive: true,
});
}
if (fs.existsSync(path.join(certSourceDir, "xml"))) {
fs.cpSync(path.join(certSourceDir, "xml"), xmlDestDir, {
recursive: true,
});
}
console.log("✅ Copied cert files to android/app/src/main/res");
return config;
},
]);
// Modify AndroidManifest
config = withAndroidManifest(config, (config) => {
const mainApplication = config.modResults.manifest.application[0];
// Add network security config if not already present
if (!mainApplication.$["android:networkSecurityConfig"]) {
mainApplication.$["android:networkSecurityConfig"] =
"@xml/network_security_config";
}
console.log("✅ Modified AndroidManifest.xml");
return config;
});
// Update gradle.properties for better performance
config = withGradleProperties(config, (config) => {
// Remove existing jvmargs if present
config.modResults = config.modResults.filter(
(item) =>
!(item.type === "property" && item.key === "org.gradle.jvmargs")
);
// Add your custom jvmargs
config.modResults.push(
{
type: "property",
key: "org.gradle.jvmargs",
value: "-Xmx16384m -XX:MaxMetaspaceSize=2024m",
},
{
type: "property",
key: "org.gradle.daemon",
value: "false",
}
);
console.log("✅ Updated gradle.properties with 16GB heap");
return config;
});
return config;
}
module.exports = withCustomAndroidConfig;

15
mobileLst/tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx"
]
}