import { BarChart3, Bot, BookOpen, Building2, FileText, Key, Newspaper, Package, ScrollText, Search, Users, type LucideIcon, } from 'lucide-react'; import type { PlanFeature } from '@/lib/api/subscriptions'; /** * Definition unique de la navigation applicative. * * Les deux espaces (produit et administration) partagent la meme structure, ce * qui permet a `AppShell` de rendre l'un ou l'autre sans duplication. */ export interface NavItem { key: string; label: string; href: string; icon: LucideIcon; /** Fonctionnalite requise par le plan d'abonnement. */ requiredFeature?: PlanFeature; } export interface NavGroup { key: string; label: string; items: NavItem[]; } export type ShellVariant = 'app' | 'admin'; type Translate = (key: string) => string; interface AppNavParams { /** Traductions du namespace `dashboard`. */ t: Translate; /** Traductions du namespace `shell`. */ tShell: Translate; role?: string; } export function buildAppNav({ t, tShell, role }: AppNavParams): NavGroup[] { const canManageUsers = role === 'ADMIN' || role === 'MANAGER'; return [ { key: 'pilotage', label: tShell('groups.pilotage'), items: [ { key: 'dashboard', label: t('nav.dashboard'), href: '/dashboard', icon: BarChart3, requiredFeature: 'dashboard', }, { key: 'bookings', label: t('nav.bookings'), href: '/dashboard/bookings', icon: Package }, { key: 'documents', label: t('nav.documents'), href: '/dashboard/documents', icon: FileText, }, { key: 'tracking', label: t('nav.tracking'), href: '/dashboard/track-trace', icon: Search, requiredFeature: 'dashboard', }, ], }, { key: 'resources', label: tShell('groups.resources'), items: [ { key: 'assistant', label: t('nav.assistant'), href: '/dashboard/assistant', icon: Bot, }, { key: 'wiki', label: t('nav.wiki'), href: '/dashboard/wiki', icon: BookOpen, requiredFeature: 'wiki', }, ], }, { key: 'organization', label: tShell('groups.organization'), items: [ { key: 'organization', label: t('nav.organization'), href: '/dashboard/settings/organization', icon: Building2, }, ...(canManageUsers ? [ { key: 'users', label: t('nav.users'), href: '/dashboard/settings/users', icon: Users, requiredFeature: 'user_management' as PlanFeature, }, ] : []), { key: 'apiKeys', label: t('nav.apiKeys'), href: '/dashboard/settings/api-keys', icon: Key, requiredFeature: 'api_access' as PlanFeature, }, ], }, ]; } interface AdminNavParams { /** Traductions du namespace `components.adminPanelDropdown`. */ t: Translate; /** Traductions du namespace `shell`. */ tShell: Translate; } export function buildAdminNav({ t, tShell }: AdminNavParams): NavGroup[] { return [ { key: 'management', label: tShell('groups.management'), items: [ { key: 'users', label: t('items.users'), href: '/admin/users', icon: Users }, { key: 'organizations', label: t('items.organizations'), href: '/admin/organizations', icon: Building2, }, { key: 'bookings', label: t('items.bookings'), href: '/admin/bookings', icon: Package }, { key: 'documents', label: t('items.documents'), href: '/admin/documents', icon: FileText, }, ], }, { key: 'content', label: tShell('groups.content'), items: [ { key: 'csvRates', label: t('items.csvRates'), href: '/admin/csv-rates', icon: BarChart3, }, { key: 'blog', label: t('items.blog'), href: '/admin/blog', icon: Newspaper }, ], }, { key: 'system', label: tShell('groups.system'), items: [{ key: 'logs', label: t('items.logs'), href: '/admin/logs', icon: ScrollText }], }, ]; } /** Racine de l'espace, utilisee pour l'etat actif exact et le fil d'Ariane. */ export const SHELL_ROOT: Record = { app: '/dashboard', admin: '/admin', }; /** * Un item est actif si la route courante est l'item lui-meme ou l'un de ses * descendants. La racine de l'espace exige une correspondance exacte, faute de * quoi elle resterait active sur toutes les pages. */ export function isItemActive(href: string, pathname: string, variant: ShellVariant): boolean { if (href === SHELL_ROOT[variant]) return pathname === href; return pathname === href || pathname.startsWith(href + '/'); } /** Retrouve le groupe et l'item correspondant a la route courante. */ export function findActiveNav(groups: NavGroup[], pathname: string, variant: ShellVariant) { let best: { group: NavGroup; item: NavItem } | null = null; for (const group of groups) { for (const item of group.items) { if (!isItemActive(item.href, pathname, variant)) continue; // On garde la correspondance la plus specifique (href le plus long). if (!best || item.href.length > best.item.href.length) { best = { group, item }; } } } return best; }