refactor(lst): more dashboard work

This commit is contained in:
2025-02-20 13:18:47 -06:00
parent 604fdf1545
commit d939332499
13 changed files with 152 additions and 190 deletions

View File

@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@antfu/ni": "^23.3.1", "@antfu/ni": "^23.3.1",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.3", "@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",
@@ -20,13 +21,14 @@
"@tailwindcss/vite": "^4.0.6", "@tailwindcss/vite": "^4.0.6",
"@tanstack/react-query": "^5.66.5", "@tanstack/react-query": "^5.66.5",
"@tanstack/react-router": "^1.106.0", "@tanstack/react-router": "^1.106.0",
"@types/react-grid-layout": "^1.3.5",
"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",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-grid-layout": "^1.5.0",
"shadcn": "^2.4.0-canary.6", "shadcn": "^2.4.0-canary.6",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.6", "tailwindcss": "^4.0.6",

View File

@@ -68,12 +68,12 @@ const rolePermissions: Roles = {
}, },
}; };
const users: User[] = [ // const users: User[] = [
{id: 1, username: "admin", role: "admin"}, // {id: 1, username: "admin", role: "admin"},
{id: 2, username: "manager", role: "manager"}, // {id: 2, username: "manager", role: "manager"},
{id: 3, username: "supervisor", role: "supervisor"}, // {id: 3, username: "supervisor", role: "supervisor"},
{id: 4, username: "user", role: "user"}, // {id: 4, username: "user", role: "user"},
]; // ];
function hasAccess(user: User, moduleName: string, feature: Feature): boolean { function hasAccess(user: User, moduleName: string, feature: Feature): boolean {
return rolePermissions[user.role]?.[moduleName]?.includes(feature) || false; return rolePermissions[user.role]?.[moduleName]?.includes(feature) || false;

View File

@@ -1,3 +1,4 @@
import {Link} from "@tanstack/react-router";
import {SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem} from "../../ui/sidebar"; import {SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem} from "../../ui/sidebar";
export function Header() { export function Header() {
@@ -5,15 +6,18 @@ export function Header() {
<SidebarHeader> <SidebarHeader>
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg" asChild> <Link to="/">
<div> <SidebarMenuButton size="lg" asChild>
<img src="/imgs/dkLst.png" alt="Description" className="size-8" /> <div className="flex flex-row">
<div className="flex flex-col gap-0.5 leading-none"> <img src="/imgs/dkLst.png" alt="Description" className="size-8" />
<span className="font-semibold">Logistics Support Tool</span>
<span className="">v1.0.0</span> <div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold">Logistics Support Tool</span>
<span className="">v1.0.0</span>
</div>
</div> </div>
</div> </SidebarMenuButton>
</SidebarMenuButton> </Link>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
</SidebarHeader> </SidebarHeader>

View File

@@ -1,4 +1,4 @@
import {Cylinder, Package, Printer, Truck} from "lucide-react"; import {Cylinder, Package, Truck} from "lucide-react";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,

View File

@@ -11,7 +11,7 @@ import {
const items = [ const items = [
{ {
title: "One Click Print", title: "One Click Print",
url: "#", url: "/ocp",
icon: Printer, icon: Printer,
}, },
]; ];

View File

@@ -0,0 +1,51 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,18 +1,12 @@
import {StrictMode, useEffect, useState} from "react"; import {StrictMode} from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import "./styles.css"; import "./styles.css";
import Cookies from "js-cookie";
import {SessionProvider} from "./components/providers/Providers.tsx"; import {SessionProvider} from "./components/providers/Providers.tsx";
import {RouterProvider, createRouter} from "@tanstack/react-router"; import {RouterProvider, createRouter} from "@tanstack/react-router";
import {SidebarProvider, SidebarTrigger} from "./components/ui/sidebar";
import {AppSidebar} from "./components/layout/lst-sidebar.tsx";
import {ThemeProvider} from "./components/layout/theme-provider.tsx";
// Import the generated route tree // Import the generated route tree
import {routeTree} from "./routeTree.gen"; import {routeTree} from "./routeTree.gen";
import {ModeToggle} from "./components/layout/mode-toggle.tsx";
import {TanStackRouterDevtools} from "@tanstack/router-devtools";
// Create a new router instance // Create a new router instance
const router = createRouter({routeTree}); const router = createRouter({routeTree});
@@ -24,32 +18,6 @@ declare module "@tanstack/react-router" {
} }
} }
function App() {
const [defaultOpen, setDefaultOpen] = useState(false);
useEffect(() => {
const sidebarState = Cookies.get("sidebar_state") === "true";
setDefaultOpen(sidebarState);
}, []);
// come back later and deal with the defaultOpen={sidebarState} as its not wokring with the cookies
console.log(defaultOpen);
return (
// <ThemeProvider>
// <nav className="flex justify-end">
// <div className="m-2">
// <ModeToggle />
// </div>
// </nav>
// <SidebarProvider>
// <AppSidebar />
// <RouterProvider router={router} />
// </SidebarProvider>
// </ThemeProvider>
<RouterProvider router={router} />
);
}
// Render the app // Render the app
const rootElement = document.getElementById("root")!; const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) { if (!rootElement.innerHTML) {
@@ -57,7 +25,7 @@ if (!rootElement.innerHTML) {
root.render( root.render(
<StrictMode> <StrictMode>
<SessionProvider> <SessionProvider>
<App /> <RouterProvider router={router} />
</SessionProvider> </SessionProvider>
</StrictMode> </StrictMode>
); );

View File

@@ -17,9 +17,7 @@ import { Route as AuthImport } from './routes/_auth'
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from './routes/index'
import { Route as OcpIndexImport } from './routes/ocp/index' import { Route as OcpIndexImport } from './routes/ocp/index'
import { Route as OcpLotsImport } from './routes/ocp/lots' import { Route as OcpLotsImport } from './routes/ocp/lots'
import { Route as OcpLineIDImport } from './routes/ocp/$lineID'
import { Route as AuthProfileImport } from './routes/_auth/profile' import { Route as AuthProfileImport } from './routes/_auth/profile'
import { Route as AuthDashboardImport } from './routes/_auth/dashboard'
// Create/Update Routes // Create/Update Routes
@@ -58,24 +56,12 @@ const OcpLotsRoute = OcpLotsImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const OcpLineIDRoute = OcpLineIDImport.update({
id: '/ocp/$lineID',
path: '/ocp/$lineID',
getParentRoute: () => rootRoute,
} as any)
const AuthProfileRoute = AuthProfileImport.update({ const AuthProfileRoute = AuthProfileImport.update({
id: '/profile', id: '/profile',
path: '/profile', path: '/profile',
getParentRoute: () => AuthRoute, getParentRoute: () => AuthRoute,
} as any) } as any)
const AuthDashboardRoute = AuthDashboardImport.update({
id: '/dashboard',
path: '/dashboard',
getParentRoute: () => AuthRoute,
} as any)
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@@ -108,13 +94,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LoginImport preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/_auth/dashboard': {
id: '/_auth/dashboard'
path: '/dashboard'
fullPath: '/dashboard'
preLoaderRoute: typeof AuthDashboardImport
parentRoute: typeof AuthImport
}
'/_auth/profile': { '/_auth/profile': {
id: '/_auth/profile' id: '/_auth/profile'
path: '/profile' path: '/profile'
@@ -122,13 +101,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthProfileImport preLoaderRoute: typeof AuthProfileImport
parentRoute: typeof AuthImport parentRoute: typeof AuthImport
} }
'/ocp/$lineID': {
id: '/ocp/$lineID'
path: '/ocp/$lineID'
fullPath: '/ocp/$lineID'
preLoaderRoute: typeof OcpLineIDImport
parentRoute: typeof rootRoute
}
'/ocp/lots': { '/ocp/lots': {
id: '/ocp/lots' id: '/ocp/lots'
path: '/ocp/lots' path: '/ocp/lots'
@@ -149,12 +121,10 @@ declare module '@tanstack/react-router' {
// Create and export the route tree // Create and export the route tree
interface AuthRouteChildren { interface AuthRouteChildren {
AuthDashboardRoute: typeof AuthDashboardRoute
AuthProfileRoute: typeof AuthProfileRoute AuthProfileRoute: typeof AuthProfileRoute
} }
const AuthRouteChildren: AuthRouteChildren = { const AuthRouteChildren: AuthRouteChildren = {
AuthDashboardRoute: AuthDashboardRoute,
AuthProfileRoute: AuthProfileRoute, AuthProfileRoute: AuthProfileRoute,
} }
@@ -165,9 +135,7 @@ export interface FileRoutesByFullPath {
'': typeof AuthRouteWithChildren '': typeof AuthRouteWithChildren
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/dashboard': typeof AuthDashboardRoute
'/profile': typeof AuthProfileRoute '/profile': typeof AuthProfileRoute
'/ocp/$lineID': typeof OcpLineIDRoute
'/ocp/lots': typeof OcpLotsRoute '/ocp/lots': typeof OcpLotsRoute
'/ocp': typeof OcpIndexRoute '/ocp': typeof OcpIndexRoute
} }
@@ -177,9 +145,7 @@ export interface FileRoutesByTo {
'': typeof AuthRouteWithChildren '': typeof AuthRouteWithChildren
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/dashboard': typeof AuthDashboardRoute
'/profile': typeof AuthProfileRoute '/profile': typeof AuthProfileRoute
'/ocp/$lineID': typeof OcpLineIDRoute
'/ocp/lots': typeof OcpLotsRoute '/ocp/lots': typeof OcpLotsRoute
'/ocp': typeof OcpIndexRoute '/ocp': typeof OcpIndexRoute
} }
@@ -190,45 +156,23 @@ export interface FileRoutesById {
'/_auth': typeof AuthRouteWithChildren '/_auth': typeof AuthRouteWithChildren
'/about': typeof AboutRoute '/about': typeof AboutRoute
'/login': typeof LoginRoute '/login': typeof LoginRoute
'/_auth/dashboard': typeof AuthDashboardRoute
'/_auth/profile': typeof AuthProfileRoute '/_auth/profile': typeof AuthProfileRoute
'/ocp/$lineID': typeof OcpLineIDRoute
'/ocp/lots': typeof OcpLotsRoute '/ocp/lots': typeof OcpLotsRoute
'/ocp/': typeof OcpIndexRoute '/ocp/': typeof OcpIndexRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: fullPaths: '/' | '' | '/about' | '/login' | '/profile' | '/ocp/lots' | '/ocp'
| '/'
| ''
| '/about'
| '/login'
| '/dashboard'
| '/profile'
| '/ocp/$lineID'
| '/ocp/lots'
| '/ocp'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to: '/' | '' | '/about' | '/login' | '/profile' | '/ocp/lots' | '/ocp'
| '/'
| ''
| '/about'
| '/login'
| '/dashboard'
| '/profile'
| '/ocp/$lineID'
| '/ocp/lots'
| '/ocp'
id: id:
| '__root__' | '__root__'
| '/' | '/'
| '/_auth' | '/_auth'
| '/about' | '/about'
| '/login' | '/login'
| '/_auth/dashboard'
| '/_auth/profile' | '/_auth/profile'
| '/ocp/$lineID'
| '/ocp/lots' | '/ocp/lots'
| '/ocp/' | '/ocp/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
@@ -239,7 +183,6 @@ export interface RootRouteChildren {
AuthRoute: typeof AuthRouteWithChildren AuthRoute: typeof AuthRouteWithChildren
AboutRoute: typeof AboutRoute AboutRoute: typeof AboutRoute
LoginRoute: typeof LoginRoute LoginRoute: typeof LoginRoute
OcpLineIDRoute: typeof OcpLineIDRoute
OcpLotsRoute: typeof OcpLotsRoute OcpLotsRoute: typeof OcpLotsRoute
OcpIndexRoute: typeof OcpIndexRoute OcpIndexRoute: typeof OcpIndexRoute
} }
@@ -249,7 +192,6 @@ const rootRouteChildren: RootRouteChildren = {
AuthRoute: AuthRouteWithChildren, AuthRoute: AuthRouteWithChildren,
AboutRoute: AboutRoute, AboutRoute: AboutRoute,
LoginRoute: LoginRoute, LoginRoute: LoginRoute,
OcpLineIDRoute: OcpLineIDRoute,
OcpLotsRoute: OcpLotsRoute, OcpLotsRoute: OcpLotsRoute,
OcpIndexRoute: OcpIndexRoute, OcpIndexRoute: OcpIndexRoute,
} }
@@ -268,7 +210,6 @@ export const routeTree = rootRoute
"/_auth", "/_auth",
"/about", "/about",
"/login", "/login",
"/ocp/$lineID",
"/ocp/lots", "/ocp/lots",
"/ocp/" "/ocp/"
] ]
@@ -279,7 +220,6 @@ export const routeTree = rootRoute
"/_auth": { "/_auth": {
"filePath": "_auth.tsx", "filePath": "_auth.tsx",
"children": [ "children": [
"/_auth/dashboard",
"/_auth/profile" "/_auth/profile"
] ]
}, },
@@ -289,17 +229,10 @@ export const routeTree = rootRoute
"/login": { "/login": {
"filePath": "login.tsx" "filePath": "login.tsx"
}, },
"/_auth/dashboard": {
"filePath": "_auth/dashboard.tsx",
"parent": "/_auth"
},
"/_auth/profile": { "/_auth/profile": {
"filePath": "_auth/profile.tsx", "filePath": "_auth/profile.tsx",
"parent": "/_auth" "parent": "/_auth"
}, },
"/ocp/$lineID": {
"filePath": "ocp/$lineID.tsx"
},
"/ocp/lots": { "/ocp/lots": {
"filePath": "ocp/lots.tsx" "filePath": "ocp/lots.tsx"
}, },

View File

@@ -5,6 +5,15 @@ import {SidebarProvider} from "../components/ui/sidebar";
import {ThemeProvider} from "../components/layout/theme-provider"; import {ThemeProvider} from "../components/layout/theme-provider";
import {ModeToggle} from "../components/layout/mode-toggle"; import {ModeToggle} from "../components/layout/mode-toggle";
import {AppSidebar} from "../components/layout/lst-sidebar"; import {AppSidebar} from "../components/layout/lst-sidebar";
import {Avatar, AvatarFallback, AvatarImage} from "../components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../components/ui/dropdown-menu";
// same as the layout // same as the layout
export const Route = createRootRoute({ export const Route = createRootRoute({
@@ -15,8 +24,31 @@ export const Route = createRootRoute({
<> <>
<ThemeProvider> <ThemeProvider>
<nav className="flex justify-end"> <nav className="flex justify-end">
<div className="m-2"> <div className="m-2 flex flex-row">
<ModeToggle /> <div className="m-auto pr-2">
<p>Add Card</p>
</div>
<div className="m-1">
<ModeToggle />
</div>
<div className="m-1">
<DropdownMenu>
<DropdownMenuTrigger>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuItem>Subscription</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</nav> </nav>
<SidebarProvider defaultOpen={sidebarState}> <SidebarProvider defaultOpen={sidebarState}>

View File

@@ -1,16 +0,0 @@
import {createFileRoute} from "@tanstack/react-router";
import {Button} from "../../components/ui/button";
import {useLogout} from "../../lib/hooks/useLogout";
export const Route = createFileRoute("/_auth/dashboard")({
component: RouteComponent,
});
function RouteComponent() {
const logout = useLogout();
return (
<div>
Hello "/_auth/dashboard"!<Button onClick={() => logout()}>Logout</Button>
</div>
);
}

View File

@@ -1,30 +1,48 @@
import {createFileRoute} from "@tanstack/react-router"; import {createFileRoute} from "@tanstack/react-router";
import LoginForm from "../components/auth/LoginForm"; import GridLayout from "react-grid-layout";
import {Button} from "../components/ui/button"; import "../../node_modules/react-grid-layout/css/styles.css";
import {useLogout} from "../lib/hooks/useLogout"; import "../../node_modules/react-resizable/css/styles.css";
import {useSession} from "../lib/hooks/useSession";
import {useSessionStore} from "../lib/store/sessionStore";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: Index, component: Index,
}); });
function Index() { function Index() {
const {session} = useSession(); // const [layout, setLayout] = useState([
const {user} = useSessionStore(); // {
const logout = useLogout(); // i: "PPOO",
// x: 0,
// y: 0,
// w: 5,
// h: 3,
// minW: 2,
// maxW: 6,
// minH: 2,
// maxH: 4,
// isResizable: true,
// isDraggable: true,
// },
// {i: "OCPLogs", x: 2, y: 0, w: 5, h: 3, isResizable: true, isDraggable: true},
// ]);
// const [cardData, setCardData] = useState([
// {i: "card1", name: "PPOO"},
// {i: "card2", name: "OCPLogs"},
// ]);
return ( return (
<div className="p-2"> <>
{session ? ( {/* <AddCards addCard={addCard} cards={cards} /> */}
<> <GridLayout className="layout" cols={12} rowHeight={30} width={window.innerWidth}>
<h3>Welcome Home {user?.username}</h3> <div className="bg-blue-400" key="a" data-grid={{x: 0, y: 0, w: 1, h: 2, static: true}}>
<p>Your current role is: {user?.role}</p> a
<br></br> </div>
<Button onClick={() => logout()}>Logout</Button>{" "} <div className="bg-blue-400" key="b" data-grid={{x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4}}>
</> b
) : ( </div>
<LoginForm /> <div className="bg-blue-400" key="c" data-grid={{x: 4, y: 0, w: 1, h: 2}}>
)} c
</div> </div>
</GridLayout>
</>
); );
} }

View File

@@ -1,17 +0,0 @@
import {createFileRoute} from "@tanstack/react-router";
export const Route = createFileRoute("/ocp/$lineID")({
component: RouteComponent,
loader: async ({params}) => {
await new Promise((r) => setTimeout(r, 1500));
//throw new Error();
return {lineID: params.lineID};
},
pendingComponent: () => <div className="m-auto">Loading....</div>,
errorComponent: () => <div className="m-auto">Error....</div>,
});
function RouteComponent() {
const {lineID} = Route.useLoaderData();
return <div>Hello "/ocp/{lineID}"!</div>;
}

View File

@@ -2,30 +2,17 @@ import {createFileRoute, Link} from "@tanstack/react-router";
export const Route = createFileRoute("/ocp/")({ export const Route = createFileRoute("/ocp/")({
component: RouteComponent, component: RouteComponent,
validateSearch: (search) => {
return {
q: (search.q as string) || "",
};
},
loaderDeps: ({search: {q}}) => ({q}),
loader: async ({deps: {q}}) => {
return {line: q};
},
}); });
function RouteComponent() { function RouteComponent() {
const {line} = Route.useLoaderData();
const lines = ["l", "2", "3"]; const lines = ["l", "2", "3"];
return ( return (
<div> <div>
<h2>Hello "/ocp/{line}/something"!</h2> <h2>Hello "/ocp/something"!</h2>
{lines.map((line) => { {lines.map((line) => {
return ( return (
<div key={line}> <div key={line}>
<Link to="/ocp/$lineID" params={{lineID: line}}> <Link to="/ocp">Specific Line</Link>
Post
</Link>
</div> </div>
); );
})} })}