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 StopServer from "./StopServer";
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 = {
server_id?: string;
@@ -33,6 +38,7 @@ export type Servers = {
export default function ServerPage() {
const { user, token } = useSessionStore();
const { modules } = useModuleStore();
//const [upgrading, setUpgrading] = useState(false);
const router = useRouter();
const { data, isError, error, isLoading } = useQuery(
@@ -51,10 +57,45 @@ export default function ServerPage() {
if (isError) {
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);
return (
<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>
<TableHeader>
<TableRow>

View File

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

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 {
SidebarGroup,
SidebarGroupContent,
@@ -7,8 +7,8 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "../../ui/sidebar";
import {hasPageAccess} from "@/utils/userAccess";
import {User} from "@/types/users";
import { hasPageAccess } from "@/utils/userAccess";
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
const items = [
{
@@ -20,48 +20,64 @@ const items = [
active: true,
},
{
title: "Bulk orders",
url: "#",
name: "Bulk orders",
moduleName: "logistics",
description: "",
link: "#",
icon: Truck,
role: ["systemAdmin"],
module: "logistics",
active: true,
subSubModule: [],
},
{
title: "Forecast",
url: "#",
name: "Forecast",
moduleName: "logistics",
description: "",
link: "#",
icon: Truck,
role: ["systemAdmin"],
module: "logistics",
active: true,
subSubModule: [],
},
{
title: "Ocme cycle counts",
url: "#",
name: "Ocme cycle counts",
moduleName: "logistics",
description: "",
link: "#",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: false,
subSubModule: [],
},
{
title: "Material Helper",
url: "/materialHelper/consumption",
name: "Material Helper",
moduleName: "logistics",
description: "",
link: "/materialHelper/consumption",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: true,
subSubModule: [],
},
{
title: "Ocme Cyclecount",
url: "/cyclecount",
name: "Ocme Cyclecount",
moduleName: "logistics",
description: "",
link: "/cyclecount",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
module: "logistics",
active: true,
subSubModule: [],
},
];
export function LogisticsSideBar({user, moduleID}: {user: User | null; moduleID: string}) {
export function LogisticsSideBar({
user,
moduleID,
}: {
user: User | null;
moduleID: string;
}) {
return (
<SidebarGroup>
<SidebarGroupLabel>Logistics</SidebarGroupLabel>
@@ -70,14 +86,15 @@ export function LogisticsSideBar({user, moduleID}: {user: User | null; moduleID:
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<>
{hasPageAccess(user, item.role, moduleID) && item.active && (
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
)}
{hasPageAccess(user, item.role, moduleID) &&
item.active && (
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
)}
</>
</SidebarMenuItem>
))}

View File

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

View File

@@ -1,20 +1,28 @@
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
import {useModuleStore} from "../../lib/store/useModuleStore";
import {useEffect} from "react";
import {useSettingStore} from "@/lib/store/useSettings";
//import {useGetUserRoles} from "@/lib/store/useGetRoles";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useModuleStore } from "../../lib/store/useModuleStore";
import { useEffect } from "react";
import { useSettingStore } from "@/lib/store/useSettings";
import { useGetUserRoles } from "@/lib/store/useGetRoles";
const queryClient = new QueryClient();
export const SessionProvider = ({children}: {children: React.ReactNode}) => {
const {fetchModules} = useModuleStore();
const {fetchSettings} = useSettingStore();
//const {fetchUserRoles} = useGetUserRoles();
export const SessionProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const { fetchModules } = useModuleStore();
const { fetchSettings } = useSettingStore();
const { fetchUserRoles } = useGetUserRoles();
useEffect(() => {
fetchModules();
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
data-slot="checkbox"
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
)}
{...props}

View File

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

View File

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