feat(lst): added icons to the sidebar
This commit is contained in:
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: http://localhost:4000/api/auth/login
|
url: http://localhost:3000/api/auth/login
|
||||||
body: json
|
body: json
|
||||||
auth: none
|
auth: none
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,11 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: http://localhost:4000/api/auth/session
|
url: http://localhost:3000/api/auth/session
|
||||||
body: none
|
body: none
|
||||||
auth: basic
|
auth: bearer
|
||||||
}
|
}
|
||||||
|
|
||||||
headers {
|
auth:bearer {
|
||||||
:
|
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJwYXNzd29yZDEyMyIsInJvbGUiOiJhZG1pbiJ9LCJpYXQiOjE3NDAwMTc4MTcsImV4cCI6MTc0MDAxODExN30.84RPgbfNkLMWPE0dmhzF62yybeb9FpetYGwGsh8_m-g
|
||||||
}
|
|
||||||
|
|
||||||
auth:basic {
|
|
||||||
username: admin
|
|
||||||
password: pass123
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antfu/ni": "^23.3.1",
|
"@antfu/ni": "^23.3.1",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"@tanstack/react-router": "^1.106.0",
|
"@tanstack/react-router": "^1.106.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"gridstack": "^11.3.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|||||||
@@ -3,21 +3,99 @@ import {ProductionSideBar} from "./side-components/production";
|
|||||||
import {Header} from "./side-components/header";
|
import {Header} from "./side-components/header";
|
||||||
import {LogisticsSideBar} from "./side-components/logistics";
|
import {LogisticsSideBar} from "./side-components/logistics";
|
||||||
import {QualitySideBar} from "./side-components/quality";
|
import {QualitySideBar} from "./side-components/quality";
|
||||||
import {AdminSideBar} from "./side-components/Admin";
|
|
||||||
import {ForkliftSideBar} from "./side-components/forklift";
|
import {ForkliftSideBar} from "./side-components/forklift";
|
||||||
import {EomSideBar} from "./side-components/eom";
|
import {EomSideBar} from "./side-components/eom";
|
||||||
|
import {AdminSideBar} from "./side-components/admin";
|
||||||
|
|
||||||
|
type Feature = string;
|
||||||
|
|
||||||
|
interface Module {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
features: Feature[];
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RolePermissions {
|
||||||
|
[moduleName: string]: Feature[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Roles {
|
||||||
|
[roleName: string]: RolePermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
role: keyof Roles;
|
||||||
|
}
|
||||||
|
const modules: Module[] = [
|
||||||
|
{id: 1, name: "production", active: true, features: ["view", "edit", "approve"]},
|
||||||
|
{id: 2, name: "logistics", active: true, features: ["view", "assign", "track"]},
|
||||||
|
{id: 3, name: "quality", active: false, features: ["view", "audit", "approve"]},
|
||||||
|
{id: 4, name: "forklift", active: true, features: ["view", "request", "operate"]},
|
||||||
|
{id: 5, name: "admin", active: true, features: ["view", "manage_users", "view_logs", "settings"]},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rolePermissions: Roles = {
|
||||||
|
admin: {
|
||||||
|
production: ["view", "edit", "approve"],
|
||||||
|
logistics: ["view", "assign", "track"],
|
||||||
|
quality: ["view", "audit", "approve"],
|
||||||
|
forklift: ["view", "request", "operate"],
|
||||||
|
admin: ["view", "manage_users", "view_logs", "settings"],
|
||||||
|
},
|
||||||
|
manager: {
|
||||||
|
production: ["view", "edit"],
|
||||||
|
logistics: ["view", "assign"],
|
||||||
|
quality: ["view", "audit"],
|
||||||
|
forklift: ["view", "request"],
|
||||||
|
admin: ["view_logs"],
|
||||||
|
},
|
||||||
|
supervisor: {
|
||||||
|
production: ["view"],
|
||||||
|
logistics: ["view"],
|
||||||
|
quality: ["view"],
|
||||||
|
forklift: ["view", "request"],
|
||||||
|
admin: [],
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
production: ["view"],
|
||||||
|
logistics: [],
|
||||||
|
quality: [],
|
||||||
|
forklift: [],
|
||||||
|
admin: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const users: User[] = [
|
||||||
|
{id: 1, username: "admin", role: "admin"},
|
||||||
|
{id: 2, username: "manager", role: "manager"},
|
||||||
|
{id: 3, username: "supervisor", role: "supervisor"},
|
||||||
|
{id: 4, username: "user", role: "user"},
|
||||||
|
];
|
||||||
|
|
||||||
|
function hasAccess(user: User, moduleName: string, feature: Feature): boolean {
|
||||||
|
return rolePermissions[user.role]?.[moduleName]?.includes(feature) || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moduleActive(moduleName: string): boolean {
|
||||||
|
const module = modules.find((m) => m.name === moduleName);
|
||||||
|
return module ? module.active : false;
|
||||||
|
}
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
|
const user = {id: 4, username: "admin", role: "admin"};
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<Header />
|
<Header />
|
||||||
<ProductionSideBar />
|
{hasAccess(user, "production", "view") && moduleActive("production") && <ProductionSideBar />}
|
||||||
<LogisticsSideBar />
|
{hasAccess(user, "logistics", "view") && moduleActive("logistics") && <LogisticsSideBar />}
|
||||||
<ForkliftSideBar />
|
{hasAccess(user, "forklift", "view") && moduleActive("forklift") && <ForkliftSideBar />}
|
||||||
<EomSideBar />
|
{hasAccess(user, "eom", "view") && moduleActive("admin") && <EomSideBar />}
|
||||||
<QualitySideBar />
|
{hasAccess(user, "quality", "view") && moduleActive("quality") && <QualitySideBar />}
|
||||||
<AdminSideBar />
|
{hasAccess(user, "admin", "view") && moduleActive("admin") && <AdminSideBar />}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Printer} from "lucide-react";
|
import {Atom, Logs, Minus, Plus, Server, Settings, ShieldCheck, Users, Webhook} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -6,41 +6,99 @@ import {
|
|||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
} from "../../ui/sidebar";
|
} from "../../ui/sidebar";
|
||||||
|
import {Collapsible, CollapsibleContent, CollapsibleTrigger} from "../../ui/collapsible";
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Servers",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Server,
|
||||||
},
|
isActive: false,
|
||||||
{
|
|
||||||
title: "Swagger",
|
|
||||||
url: "#",
|
|
||||||
icon: Printer,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Logs",
|
|
||||||
url: "#",
|
|
||||||
icon: Printer,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Users",
|
|
||||||
url: "#",
|
|
||||||
icon: Printer,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "UCD",
|
|
||||||
url: "#",
|
|
||||||
icon: Printer,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const data = {
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Admin",
|
||||||
|
url: "#",
|
||||||
|
icon: ShieldCheck,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
url: "#",
|
||||||
|
icon: Settings,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Swagger",
|
||||||
|
url: "#",
|
||||||
|
icon: Webhook,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Logs",
|
||||||
|
url: "#",
|
||||||
|
icon: Logs,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Users",
|
||||||
|
url: "#",
|
||||||
|
icon: Users,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "UCD",
|
||||||
|
url: "#",
|
||||||
|
icon: Atom,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export function AdminSideBar() {
|
export function AdminSideBar() {
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Admin</SidebarGroupLabel>
|
<SidebarGroupLabel>Admin section</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{data.navMain.map((item, index) => (
|
||||||
|
<Collapsible key={item.title} defaultOpen={index === 1} className="group/collapsible">
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<item.icon />
|
||||||
|
{item.title}{" "}
|
||||||
|
<Plus className="ml-auto group-data-[state=open]/collapsible:hidden" />
|
||||||
|
<Minus className="ml-auto group-data-[state=closed]/collapsible:hidden" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
{item.items?.length ? (
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{item.items.map((item) => (
|
||||||
|
<SidebarMenuSubItem key={item.title}>
|
||||||
|
<SidebarMenuSubButton asChild isActive={item.isActive}>
|
||||||
|
<a href={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
) : null}
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Printer} from "lucide-react";
|
import {Forklift, Hourglass, Minus, Plus, Signature} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -6,21 +6,88 @@ import {
|
|||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
} from "../../ui/sidebar";
|
} from "../../ui/sidebar";
|
||||||
|
import {Collapsible, CollapsibleTrigger} from "../../ui/collapsible";
|
||||||
|
import {CollapsibleContent} from "@radix-ui/react-collapsible";
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Qaulity Request",
|
title: "Gemone",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Forklift,
|
||||||
|
isActive: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Forklift Management",
|
||||||
|
url: "#",
|
||||||
|
icon: Forklift,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "All Forklifts",
|
||||||
|
url: "#",
|
||||||
|
icon: Forklift,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Leasing data",
|
||||||
|
url: "#",
|
||||||
|
isActive: false,
|
||||||
|
icon: Signature,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Forklift Hours",
|
||||||
|
url: "#",
|
||||||
|
isActive: false,
|
||||||
|
icon: Hourglass,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export function ForkliftSideBar() {
|
export function ForkliftSideBar() {
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Forklift</SidebarGroupLabel>
|
<SidebarGroupLabel>Forklift Section</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{data.navMain.map((item, index) => (
|
||||||
|
<Collapsible key={item.title} defaultOpen={index === 1} className="group/collapsible">
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<item.icon />
|
||||||
|
{item.title}{" "}
|
||||||
|
<Plus className="ml-auto group-data-[state=open]/collapsible:hidden" />
|
||||||
|
<Minus className="ml-auto group-data-[state=closed]/collapsible:hidden" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
{item.items?.length ? (
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{item.items.map((item) => (
|
||||||
|
<SidebarMenuSubItem key={item.title}>
|
||||||
|
<SidebarMenuSubButton asChild isActive={item.isActive}>
|
||||||
|
<a href={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
) : null}
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Printer} from "lucide-react";
|
import {Cylinder, Package, Printer, Truck} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -12,22 +12,22 @@ const items = [
|
|||||||
{
|
{
|
||||||
title: "Silo Adjustments",
|
title: "Silo Adjustments",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Cylinder,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bulk orders",
|
title: "Bulk orders",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Truck,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Forecast",
|
title: "Forecast",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Truck,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Ocme cycle counts",
|
title: "Ocme cycle counts",
|
||||||
url: "#",
|
url: "#",
|
||||||
icon: Printer,
|
icon: Package,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
31
frontend/src/components/ui/collapsible.tsx
Normal file
31
frontend/src/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||||
|
|
||||||
|
function Collapsible({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||||
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.CollapsibleTrigger
|
||||||
|
data-slot="collapsible-trigger"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CollapsibleContent({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.CollapsibleContent
|
||||||
|
data-slot="collapsible-content"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||||
@@ -3,7 +3,7 @@ import {verify} from "hono/jwt";
|
|||||||
|
|
||||||
const session = new OpenAPIHono();
|
const session = new OpenAPIHono();
|
||||||
const tags = ["Auth"];
|
const tags = ["Auth"];
|
||||||
const JWT_SECRET = "your-secret-key";
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
tags: ["Auth"],
|
tags: ["Auth"],
|
||||||
@@ -21,6 +21,14 @@ const route = createRoute({
|
|||||||
},
|
},
|
||||||
description: "Login successful",
|
description: "Login successful",
|
||||||
},
|
},
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {message: ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Error of why you were not logged in.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
session.openapi(route, async (c) => {
|
session.openapi(route, async (c) => {
|
||||||
@@ -39,7 +47,7 @@ session.openapi(route, async (c) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await verify(token, JWT_SECRET);
|
const payload = await verify(token, JWT_SECRET);
|
||||||
console.log(payload);
|
//console.log(payload);
|
||||||
return c.json({data: {token, user: payload.user}});
|
return c.json({data: {token, user: payload.user}});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return c.json({error: "Invalid or expired token"}, 401);
|
return c.json({error: "Invalid or expired token"}, 401);
|
||||||
|
|||||||
Reference in New Issue
Block a user