stock.geolock.fr/apps/web/src/components/admin/AdminShell.tsx
2026-02-24 16:10:30 +00:00

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>
)
}