test(frontend): work on the frontend to have better admin

This commit is contained in:
2025-04-04 17:09:30 -05:00
parent e1973e4da6
commit ad5e77028d
11 changed files with 375 additions and 225 deletions

View File

@@ -0,0 +1,8 @@
export default function DataMartStats() {
return (
<div>
The stats for all the data mart querys out there and whos and when
they are last used to understand if we want to keep them or not
</div>
);
}

View File

@@ -0,0 +1,8 @@
export default function NotificationMGT() {
return (
<div>
Manage all notifications from here instad of going to the db,
locking some items that are auto updated on server restarts
</div>
);
}

View File

@@ -19,6 +19,11 @@ import { adminUrlCheck } from "@/utils/adminUrlCheck";
import RestartServer from "./RestartServer"; import RestartServer from "./RestartServer";
import StopServer from "./StopServer"; import StopServer from "./StopServer";
import StartServer from "./StartServer"; import StartServer from "./StartServer";
import { Button } from "@/components/ui/button";
import { getSettings } from "@/utils/querys/settings";
import { toast } from "sonner";
import axios from "axios";
//import { useState } from "react";
export type Servers = { export type Servers = {
server_id?: string; server_id?: string;
@@ -33,6 +38,7 @@ export type Servers = {
export default function ServerPage() { export default function ServerPage() {
const { user, token } = useSessionStore(); const { user, token } = useSessionStore();
const { modules } = useModuleStore(); const { modules } = useModuleStore();
//const [upgrading, setUpgrading] = useState(false);
const router = useRouter(); const router = useRouter();
const { data, isError, error, isLoading } = useQuery( const { data, isError, error, isLoading } = useQuery(
@@ -51,10 +57,45 @@ export default function ServerPage() {
if (isError) { if (isError) {
return <div>{JSON.stringify(error)}</div>; return <div>{JSON.stringify(error)}</div>;
} }
const { data: set } = useQuery(getSettings(token ?? ""));
const upgrade = async () => {
let devDir = set.filter((n: any) => n.name === "devDir");
toast.success("All Servers was just triggered.");
try {
const result = await axios.post(
`/api/server/update/localhost`,
{ devDir: devDir[0].value, all: true },
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (result.data.success) {
toast.success(result.data.message);
}
if (!result.data.success) {
toast.success(result.data.message);
}
} catch (error: any) {
toast.error(
`There was an error updating the server: ${error.data.message}`
);
}
};
//console.log(data); //console.log(data);
return ( return (
<LstCard className="m-2 flex place-content-center w-dvh"> <LstCard className="m-2 flex place-content-center w-dvh">
<div className="flex justify-end m-2">
<Button
onClick={upgrade}
disabled={data?.some((d: any) => d.isUpgrading)}
>
Update All Servers
</Button>
</div>
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>

View File

@@ -1,3 +1,4 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@@ -10,6 +11,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { DebugButton } from "@/utils/formStuff/debugButton"; import { DebugButton } from "@/utils/formStuff/debugButton";
import { userFormOptions } from "@/utils/formStuff/options/userformOptions"; import { userFormOptions } from "@/utils/formStuff/options/userformOptions";
import { generatePassword } from "@/utils/passwordGen"; import { generatePassword } from "@/utils/passwordGen";
@@ -18,10 +20,16 @@ import { useForm } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import UserRoles from "./UserRoles";
import { CardHeader } from "@/components/ui/card";
export default function UserCard(data: any) { export default function UserCard(data: any) {
const token = localStorage.getItem("auth_token"); const token = localStorage.getItem("auth_token");
const { refetch } = useQuery(getUsers()); const { refetch } = useQuery(getUsers());
//console.log(modules);
//console.log(data.user);
//console.log(userRoles);
const form = useForm({ const form = useForm({
...userFormOptions(data.user), ...userFormOptions(data.user),
onSubmit: async ({ value }) => { onSubmit: async ({ value }) => {
@@ -53,185 +61,224 @@ export default function UserCard(data: any) {
}, },
}); });
return ( return (
<div> <div className="flex flex-row">
<form <div className="m-2">
onSubmit={(e) => { <LstCard>
e.preventDefault(); <CardHeader>User Profile</CardHeader>
e.stopPropagation(); <form
}} onSubmit={(e) => {
> e.preventDefault();
<form.Field e.stopPropagation();
name="username" }}
validators={{ >
// We can choose between form-wide and field-specific validators <form.Field
onChange: ({ value }) => name="username"
value.length > 3 validators={{
? undefined // We can choose between form-wide and field-specific validators
: "Username must be longer than 3 letters", onChange: ({ value }) =>
}} value.length > 3
children={(field) => { ? undefined
return ( : "Username must be longer than 3 letters",
<div className="m-2 min-w-48 max-w-96 p-2"> }}
<Label htmlFor="username">Username</Label> children={(field) => {
<Input return (
name={field.name} <div className="m-2 min-w-48 max-w-96 p-2">
value={field.state.value} <Label htmlFor="username">
onBlur={field.handleBlur} Username
//type="number" </Label>
onChange={(e) => <Input
field.handleChange(e.target.value) name={field.name}
} value={field.state.value}
/> onBlur={field.handleBlur}
{field.state.meta.errors.length ? ( //type="number"
<em>{field.state.meta.errors.join(",")}</em> onChange={(e) =>
) : null} field.handleChange(
</div> e.target.value
); )
}} }
/>
<form.Field
name="email"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value.length > 3
? undefined
: "You must enter a correct ",
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor="email">Email</Label>
<Input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
//type="number"
onChange={(e) =>
field.handleChange(e.target.value)
}
/>
{field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(",")}</em>
) : null}
</div>
);
}}
/>
<form.Field
name="role"
//listeners={{onChange: ({value})=>{}}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor={field.name}>Select role</Label>
<Select
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger className="w-[180px]">
<SelectValue
id={field.name}
placeholder="Select Role"
/> />
</SelectTrigger> {field.state.meta.errors.length ? (
<SelectContent> <em>
<SelectGroup> {field.state.meta.errors.join(
<SelectLabel>Roles</SelectLabel> ","
<SelectItem value="viewer"> )}
Viewer </em>
</SelectItem> ) : null}
<SelectItem value="operator"> </div>
Operator );
</SelectItem> }}
<SelectItem value="manager"> />
Manager <form.Field
</SelectItem> name="email"
<SelectItem value="admin"> validators={{
Admin // We can choose between form-wide and field-specific validators
</SelectItem> onChange: ({ value }) =>
</SelectGroup> value.length > 3
</SelectContent> ? undefined
</Select> : "You must enter a correct ",
</div> }}
); children={(field) => {
}} return (
/> <div className="m-2 min-w-48 max-w-96 p-2">
<form.Field <Label htmlFor="email">Email</Label>
name="password" <Input
validators={{ name={field.name}
onChangeAsyncDebounceMs: 500, value={field.state.value}
onChangeAsync: ({ value }) => { onBlur={field.handleBlur}
if ( //type="number"
window.location.pathname.includes("/users") && onChange={(e) =>
value.length === 0 field.handleChange(
) { e.target.value
return; )
} }
if (value.length < 4) { />
return "Password must be at least 4 characters long."; {field.state.meta.errors.length ? (
} <em>
{field.state.meta.errors.join(
","
)}
</em>
) : null}
</div>
);
}}
/>
<form.Field
name="role"
//listeners={{onChange: ({value})=>{}}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor={field.name}>
Select role
</Label>
<Select
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger className="w-[180px]">
<SelectValue
id={field.name}
placeholder="Select Role"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
Roles
</SelectLabel>
<SelectItem value="viewer">
Viewer
</SelectItem>
<SelectItem value="operator">
Operator
</SelectItem>
<SelectItem value="manager">
Manager
</SelectItem>
<SelectItem value="admin">
Admin
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
}}
/>
<form.Field
name="password"
validators={{
onChangeAsyncDebounceMs: 500,
onChangeAsync: ({ value }) => {
if (
window.location.pathname.includes(
"/users"
) &&
value.length === 0
) {
return;
}
if (value.length < 4) {
return "Password must be at least 4 characters long.";
}
if (!/[A-Z]/.test(value)) { if (!/[A-Z]/.test(value)) {
return "Password must contain at least one uppercase letter."; return "Password must contain at least one uppercase letter.";
} }
if (!/[a-z]/.test(value)) { if (!/[a-z]/.test(value)) {
return "Password must contain at least one lower case letter."; return "Password must contain at least one lower case letter.";
} }
if (!/[0-9]/.test(value)) { if (!/[0-9]/.test(value)) {
return "Password must contain at least one number."; return "Password must contain at least one number.";
} }
if ( if (
!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test( !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(
value value
) )
) { ) {
return "Password must contain at least one special character."; return "Password must contain at least one special character.";
} }
}, },
}} }}
children={(field) => { children={(field) => {
return ( return (
<div className="m-2 p-2"> <div className="m-2 p-2">
<Label htmlFor="password"> <Label htmlFor="password">
Change Password Change Password
</Label> </Label>
<div className="mt-2 flex flex-row"> <div className="mt-2 flex flex-row">
<Input <Input
className="min-w-48 max-w-96" className="min-w-48 max-w-96"
name={field.name} name={field.name}
value={field.state.value} value={field.state.value}
onBlur={field.handleBlur} onBlur={field.handleBlur}
//type="number" //type="number"
onChange={(e) => onChange={(e) =>
field.handleChange(e.target.value) field.handleChange(
} e.target.value
/> )
<Button }
className="ml-2" />
onClick={() => <Button
field.handleChange( className="ml-2"
generatePassword(8) onClick={() =>
) field.handleChange(
} generatePassword(8)
> )
Random password }
</Button> >
<DebugButton data={form.state.values} /> Random password
</div> </Button>
{field.state.meta.errors.length ? ( <DebugButton
<em>{field.state.meta.errors.join(",")}</em> data={form.state.values}
) : null} />
</div> </div>
); {field.state.meta.errors.length ? (
}} <em>
/> {field.state.meta.errors.join(
</form> ","
<div> )}
<Button onClick={form.handleSubmit}>Save</Button> </em>
) : null}
</div>
);
}}
/>
</form>
</LstCard>
<div>
<Button onClick={form.handleSubmit}>Save</Button>
</div>
</div>
<div className="m-2">
<LstCard>
<CardHeader>User Module / Role Access</CardHeader>
<UserRoles user={data.user} />
</LstCard>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,16 @@
import { Label } from "@/components/ui/label";
import { useModuleStore } from "@/lib/store/useModuleStore";
//import { Checkbox } from "@radix-ui/react-checkbox";
export default function UserRoles(user: any) {
const { modules } = useModuleStore();
console.log(user);
return (
<div>
{modules?.map((m: any) => {
console.log(m);
return <Label>{m.name}</Label>;
})}
</div>
);
}

View File

@@ -1,4 +1,4 @@
import {Cylinder, Package, Truck} from "lucide-react"; import { Cylinder, Package, Truck } from "lucide-react";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
@@ -7,8 +7,8 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from "../../ui/sidebar"; } from "../../ui/sidebar";
import {hasPageAccess} from "@/utils/userAccess"; import { hasPageAccess } from "@/utils/userAccess";
import {User} from "@/types/users"; import { User } from "@/types/users";
// this will need to be moved to a links section the db to make it more easy to remove and add // this will need to be moved to a links section the db to make it more easy to remove and add
const items = [ const items = [
{ {
@@ -20,48 +20,64 @@ const items = [
active: true, active: true,
}, },
{ {
title: "Bulk orders", name: "Bulk orders",
url: "#", moduleName: "logistics",
description: "",
link: "#",
icon: Truck, icon: Truck,
role: ["systemAdmin"], role: ["systemAdmin"],
module: "logistics",
active: true, active: true,
subSubModule: [],
}, },
{ {
title: "Forecast", name: "Forecast",
url: "#", moduleName: "logistics",
description: "",
link: "#",
icon: Truck, icon: Truck,
role: ["systemAdmin"], role: ["systemAdmin"],
module: "logistics",
active: true, active: true,
subSubModule: [],
}, },
{ {
title: "Ocme cycle counts", name: "Ocme cycle counts",
url: "#", moduleName: "logistics",
description: "",
link: "#",
icon: Package, icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"], role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: false, active: false,
subSubModule: [],
}, },
{ {
title: "Material Helper", name: "Material Helper",
url: "/materialHelper/consumption", moduleName: "logistics",
description: "",
link: "/materialHelper/consumption",
icon: Package, icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"], role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: true, active: true,
subSubModule: [],
}, },
{ {
title: "Ocme Cyclecount", name: "Ocme Cyclecount",
url: "/cyclecount", moduleName: "logistics",
description: "",
link: "/cyclecount",
icon: Package, icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"], role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: true, active: true,
subSubModule: [],
}, },
]; ];
export function LogisticsSideBar({user, moduleID}: {user: User | null; moduleID: string}) { export function LogisticsSideBar({
user,
moduleID,
}: {
user: User | null;
moduleID: string;
}) {
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Logistics</SidebarGroupLabel> <SidebarGroupLabel>Logistics</SidebarGroupLabel>
@@ -70,14 +86,15 @@ export function LogisticsSideBar({user, moduleID}: {user: User | null; moduleID:
{items.map((item) => ( {items.map((item) => (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
<> <>
{hasPageAccess(user, item.role, moduleID) && item.active && ( {hasPageAccess(user, item.role, moduleID) &&
<SidebarMenuButton asChild> item.active && (
<a href={item.url}> <SidebarMenuButton asChild>
<item.icon /> <a href={item.url}>
<span>{item.title}</span> <item.icon />
</a> <span>{item.title}</span>
</SidebarMenuButton> </a>
)} </SidebarMenuButton>
)}
</> </>
</SidebarMenuItem> </SidebarMenuItem>
))} ))}

View File

@@ -32,7 +32,7 @@ let lotColumns = [
label: "AvDescription", label: "AvDescription",
}, },
{ {
key: "LOT", key: "lot",
label: "LotNumber", label: "LotNumber",
}, },
{ {
@@ -208,10 +208,14 @@ export default function Lots() {
<TableCell className="font-medium"> <TableCell className="font-medium">
{lot.Alias} {lot.Alias}
</TableCell> </TableCell>
<TableCell className="font-medium"> <TableCell
{lot.LOT} className={`font-medium ${lot.ProlinkLot != lot.lot ? "text-red-500" : ""}`}
>
{lot.lot}
</TableCell> </TableCell>
<TableCell className="font-medium"> <TableCell
className={`font-medium ${lot.ProlinkLot != lot.lot ? "text-red-500" : ""}`}
>
{lot.ProlinkLot} {lot.ProlinkLot}
</TableCell> </TableCell>
<TableCell className="font-medium"> <TableCell className="font-medium">

View File

@@ -1,20 +1,28 @@
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {useModuleStore} from "../../lib/store/useModuleStore"; import { useModuleStore } from "../../lib/store/useModuleStore";
import {useEffect} from "react"; import { useEffect } from "react";
import {useSettingStore} from "@/lib/store/useSettings"; import { useSettingStore } from "@/lib/store/useSettings";
//import {useGetUserRoles} from "@/lib/store/useGetRoles"; import { useGetUserRoles } from "@/lib/store/useGetRoles";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
export const SessionProvider = ({children}: {children: React.ReactNode}) => { export const SessionProvider = ({
const {fetchModules} = useModuleStore(); children,
const {fetchSettings} = useSettingStore(); }: {
//const {fetchUserRoles} = useGetUserRoles(); children: React.ReactNode;
}) => {
const { fetchModules } = useModuleStore();
const { fetchSettings } = useSettingStore();
const { fetchUserRoles } = useGetUserRoles();
useEffect(() => { useEffect(() => {
fetchModules(); fetchModules();
fetchSettings(); fetchSettings();
//fetchUserRoles(); fetchUserRoles();
}, []); }, []);
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>; return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}; };

View File

@@ -12,7 +12,7 @@ function Checkbox({
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
className={cn( className={cn(
"peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}

View File

@@ -1,6 +1,5 @@
import {create} from "zustand"; import { create } from "zustand";
import {useSessionStore} from "./sessionStore"; import { Modules } from "@/types/modules";
import {Modules} from "@/types/modules";
import axios from "axios"; import axios from "axios";
interface SettingState { interface SettingState {
@@ -15,15 +14,17 @@ interface FetchModulesResponse {
export const useGetUserRoles = create<SettingState>()((set) => ({ export const useGetUserRoles = create<SettingState>()((set) => ({
userRoles: [], userRoles: [],
setUserRoles: (userRoles) => set({userRoles}), setUserRoles: (userRoles) => set({ userRoles }),
fetchUserRoles: async () => { fetchUserRoles: async () => {
try { try {
//const response = await axios.get<{data: Setting[]}>(`${process.env.NEXT_PUBLIC_URL}/api/settings/client`); //const response = await axios.get<{data: Setting[]}>(`${process.env.NEXT_PUBLIC_URL}/api/settings/client`);
const {token} = useSessionStore(); const token = localStorage.getItem("auth_token");
const response = await axios.get("/api/auth/getuseraccess", {headers: {Authorization: `Bearer ${token}`}}); const response = await axios.get("/api/auth/getuseraccess", {
headers: { Authorization: `Bearer ${token}` },
});
const data: FetchModulesResponse = response.data; //await response.json(); const data: FetchModulesResponse = response.data; //await response.json();
//console.log(data); //console.log(data);
set({userRoles: data.data}); set({ userRoles: data.data });
} catch (error) { } catch (error) {
console.error("Failed to fetch settings:", error); console.error("Failed to fetch settings:", error);
} }

View File

@@ -1,7 +1,7 @@
export type LotType = { export type LotType = {
AV: number; AV: number;
Alias: string; Alias: string;
LOT: number; lot: number;
LabelOnlineID: number; LabelOnlineID: number;
MachineDescription: string; MachineDescription: string;
MachineID: number; MachineID: number;