163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { usePathname } from 'next/navigation'
|
|
import { useState, type ReactNode } from 'react'
|
|
import { Menu, LayoutDashboard, Package, ArrowLeftRight, Plus, KeyRound, PackageSearch, ShoppingCart, LogOut, Users } from 'lucide-react'
|
|
import type { AuthUser } from '@localiztoi/shared'
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetTrigger,
|
|
SheetTitle,
|
|
} from '@/components/ui/sheet'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const isActivePath = (pathname: string, href: string) => {
|
|
if (href === '/admin') {
|
|
return pathname === '/admin'
|
|
}
|
|
return pathname === href || pathname.startsWith(`${href}/`)
|
|
}
|
|
|
|
const navLinks = [
|
|
{ href: '/admin', label: 'Dashboard', icon: LayoutDashboard },
|
|
{ href: '/admin/amazon-orders', label: 'Amazon', icon: ShoppingCart },
|
|
{ href: '/admin/orders', label: 'Commandes', icon: Package },
|
|
{ href: '/admin/sku-mappings', label: 'Correspondances SKU', icon: ArrowLeftRight },
|
|
{ href: '/admin/axonaut-stock', label: 'Stock Axonaut', icon: PackageSearch },
|
|
{ href: '/admin/api-keys', label: 'Tokens API', icon: KeyRound },
|
|
{ href: '/admin/users', label: 'Utilisateurs', icon: Users },
|
|
]
|
|
|
|
const NavContent = ({
|
|
pathname,
|
|
onNavigate,
|
|
}: {
|
|
pathname: string
|
|
onNavigate?: () => void
|
|
}) => (
|
|
<nav className="flex flex-col gap-1 p-3">
|
|
{navLinks.map(({ href, label, icon: Icon }) => {
|
|
const active = isActivePath(pathname, href)
|
|
return (
|
|
<Button
|
|
key={href}
|
|
variant={active ? 'secondary' : 'ghost'}
|
|
className={cn('w-full justify-start h-11 gap-3', active && 'font-semibold')}
|
|
asChild
|
|
>
|
|
<Link href={href} onClick={onNavigate}>
|
|
<Icon className="h-4 w-4" />
|
|
{label}
|
|
</Link>
|
|
</Button>
|
|
)
|
|
})}
|
|
</nav>
|
|
)
|
|
|
|
export const AdminShell = ({ children, user, onLogout }: { children: ReactNode; user: AuthUser; onLogout: () => void }) => {
|
|
const pathname = usePathname()
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
|
|
// Pages en mode plein écran (sans header ni sidebar)
|
|
const isFullscreen = pathname.match(/\/admin\/orders\/[^/]+\/scan(-tracking)?$/)
|
|
|
|
if (isFullscreen) {
|
|
return (
|
|
<div className="h-screen overflow-hidden">
|
|
<main className="h-full overflow-y-auto p-4">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen overflow-hidden">
|
|
{/* Desktop sidebar */}
|
|
<aside className="hidden md:flex md:flex-col w-64 border-r bg-background shrink-0">
|
|
<div className="p-4 border-b">
|
|
<p className="font-bold text-base tracking-tight">Localiztoi Stock</p>
|
|
<p className="text-xs text-muted-foreground">Console Admin</p>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto">
|
|
<NavContent pathname={pathname} />
|
|
</div>
|
|
|
|
<div className="p-3 border-t">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-medium truncate">{user.displayName}</p>
|
|
<p className="text-xs text-muted-foreground truncate">{user.username}</p>
|
|
</div>
|
|
<Button variant="ghost" size="icon" className="shrink-0 text-muted-foreground" onClick={onLogout} title="Déconnexion">
|
|
<LogOut className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main area */}
|
|
<div className="flex flex-col flex-1 overflow-hidden">
|
|
{/* Header */}
|
|
<header className="h-14 border-b bg-background flex items-center justify-between px-4 shrink-0 sticky top-0 z-10">
|
|
<div className="flex items-center gap-3">
|
|
{/* Mobile hamburger */}
|
|
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
|
|
<SheetTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="md:hidden">
|
|
<Menu className="h-5 w-5" />
|
|
<span className="sr-only">Ouvrir le menu</span>
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent side="left" className="w-64 p-0 flex flex-col">
|
|
<SheetTitle className="p-4 border-b font-bold text-base tracking-tight">
|
|
Localiztoi Stock
|
|
</SheetTitle>
|
|
<div className="flex-1 overflow-y-auto">
|
|
<NavContent
|
|
pathname={pathname}
|
|
onNavigate={() => setMobileMenuOpen(false)}
|
|
/>
|
|
</div>
|
|
<div className="p-3 border-t">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-medium truncate">{user.displayName}</p>
|
|
<p className="text-xs text-muted-foreground truncate">{user.username}</p>
|
|
</div>
|
|
<Button variant="ghost" size="icon" className="shrink-0 text-muted-foreground" onClick={() => { setMobileMenuOpen(false); onLogout() }} title="Déconnexion">
|
|
<LogOut className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
|
|
<span className="font-semibold md:hidden">Localiztoi</span>
|
|
<span className="hidden md:block text-sm text-muted-foreground">{pathname}</span>
|
|
</div>
|
|
|
|
<Button size="sm" asChild>
|
|
<Link href="/admin/orders/new">
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
<span className="hidden sm:inline">Créer une commande</span>
|
|
<span className="sm:hidden">Nouvelle</span>
|
|
</Link>
|
|
</Button>
|
|
</header>
|
|
|
|
{/* Page content */}
|
|
<main className="flex-1 overflow-y-auto p-4 md:p-6">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|