chore: initial Vector 2.0 monorepo
Ground-up TypeScript rewrite of the Vector hardware parts inventory
system. Ships the full roadmap (Phases 0-8) in one initial commit:
- pnpm + Turbo monorepo: apps/{api,web,e2e}, packages/{db,shared,ui,config}
- Express 5 + Prisma 5 + zod validation + JWT w/ refresh-token rotation
- React 19 + Vite + shadcn/ui + TanStack Query/Table + nuqs URL state
- Repair/RMA, tags, bulk ops, saved views, CSV audit export
- Analytics dashboard on Recharts + EOL tracking
- Signed webhook subscriptions (HMAC-SHA256) with in-process emitter
- Vitest unit tests (shared schemas, api services/helpers) + Playwright skeleton
- Gitea Actions CI (lint, typecheck, test+coverage, build) + Renovate
Deferred follow-ups: Postgres cutover (data-migration script ready),
BullMQ worker for webhook delivery, @react-pdf PDF export, CSV import wizard.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import {
|
||||
Boxes,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
LayoutDashboard,
|
||||
type LucideIcon,
|
||||
MapPinned,
|
||||
Package,
|
||||
Server,
|
||||
Users as UsersIcon,
|
||||
Webhook,
|
||||
Wrench,
|
||||
} from 'lucide-react';
|
||||
import { cn, Button, Tooltip, TooltipContent, TooltipTrigger } from '@vector/ui';
|
||||
import { useAuth } from '../../contexts/AuthContext.js';
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
adminOnly?: boolean;
|
||||
}
|
||||
|
||||
const NAV_ITEMS: NavItem[] = [
|
||||
{ to: '/', label: 'Dashboard', icon: LayoutDashboard },
|
||||
{ to: '/parts', label: 'Parts', icon: Package },
|
||||
{ to: '/locations', label: 'Locations', icon: MapPinned },
|
||||
{ to: '/manufacturers', label: 'Manufacturers', icon: Boxes },
|
||||
{ to: '/repairs', label: 'Repairs', icon: Wrench },
|
||||
{ to: '/hosts', label: 'Hosts', icon: Server },
|
||||
{ to: '/admin/users', label: 'Users', icon: UsersIcon, adminOnly: true },
|
||||
{ to: '/admin/webhooks', label: 'Webhooks', icon: Webhook, adminOnly: true },
|
||||
];
|
||||
|
||||
export interface SidebarProps {
|
||||
collapsed: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
export function Sidebar({ collapsed, onToggle }: SidebarProps) {
|
||||
const { user } = useAuth();
|
||||
const items = NAV_ITEMS.filter((i) => !i.adminOnly || user?.role === 'ADMIN');
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
'fixed inset-y-0 left-0 z-40 flex flex-col border-r border-border bg-card transition-[width] duration-200',
|
||||
collapsed ? 'w-14' : 'w-64',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-13 items-center gap-2 border-b border-border px-3">
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-brand text-brand-foreground font-semibold">
|
||||
V
|
||||
</div>
|
||||
{!collapsed && <span className="truncate text-sm font-semibold">Vector</span>}
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-0.5 p-2">
|
||||
{items.map((item) => (
|
||||
<NavItemLink key={item.to} item={item} collapsed={collapsed} />
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-border p-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn('w-full', !collapsed && 'justify-start gap-2 px-2')}
|
||||
onClick={onToggle}
|
||||
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||
>
|
||||
{collapsed ? <ChevronsRight className="h-4 w-4" /> : <ChevronsLeft className="h-4 w-4" />}
|
||||
{!collapsed && <span className="text-xs text-muted-foreground">Collapse</span>}
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function NavItemLink({ item, collapsed }: { item: NavItem; collapsed: boolean }) {
|
||||
const content = (
|
||||
<NavLink
|
||||
to={item.to}
|
||||
end={item.to === '/'}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'flex items-center gap-3 rounded-md px-2.5 py-2 text-sm transition-colors',
|
||||
isActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/60 hover:text-foreground',
|
||||
collapsed && 'justify-center px-0',
|
||||
)
|
||||
}
|
||||
>
|
||||
<item.icon className="h-4 w-4 shrink-0" />
|
||||
{!collapsed && <span className="truncate">{item.label}</span>}
|
||||
</NavLink>
|
||||
);
|
||||
if (!collapsed) return content;
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||
<TooltipContent side="right">{item.label}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user