Add main menu page with role-based modules (#7)
- `/` is now the main menu; orders/receipts page moved to `/receipts` - Module registry (features/menu/modules.ts) drives both the menu tiles and route guards (ModuleRoute); admin sees every module - Menu: module search with Ctrl+K, profile with logout, responsive grid, palette matching the login page - «← Головне меню» link on receipts and cash registers pages; «Каси» moved from the receipts toolbar into the menu Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import type { UserRole } from '@/api/types'
|
||||
|
||||
export const ROLE_LABEL: Record<UserRole, string> = {
|
||||
admin: 'Адміністратор',
|
||||
cashier: 'Касир',
|
||||
viewer: 'Спостерігач',
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Navigate, Outlet } from 'react-router-dom'
|
||||
|
||||
import { useAuth } from '@/features/auth/useAuth'
|
||||
import { canAccess, getModule } from '@/features/menu/modules'
|
||||
|
||||
/** Пускает в модуль только роли из реестра; остальных возвращает в главное меню. */
|
||||
export function ModuleRoute({ moduleId }: { moduleId: string }) {
|
||||
const { user } = useAuth()
|
||||
if (!canAccess(user, getModule(moduleId).roles)) return <Navigate to="/" replace />
|
||||
return <Outlet />
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { User, UserRole } from '@/api/types'
|
||||
|
||||
/**
|
||||
* Реестр модулей главного меню. Единственное место, где модуль связывается
|
||||
* с путём и ролями: по нему рисуется меню и защищается роут модуля.
|
||||
*/
|
||||
export interface AppModule {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
path: string
|
||||
/** Роли, которым модуль доступен; администратор видит всё и в списке не нужен. */
|
||||
roles: readonly UserRole[]
|
||||
/** `d` для stroke-иконки 24×24. */
|
||||
icon: string
|
||||
}
|
||||
|
||||
export const MODULES: readonly AppModule[] = [
|
||||
{
|
||||
id: 'receipts',
|
||||
title: 'Чеки',
|
||||
description: 'Замовлення з CRM, ЕТТН-чеки Checkbox і статуси Нової Пошти',
|
||||
path: '/receipts',
|
||||
roles: ['cashier', 'viewer'],
|
||||
icon: 'M6 3h12v18l-3-2-3 2-3-2-3 2z M9 8h6 M9 12h6 M9 16h3',
|
||||
},
|
||||
{
|
||||
id: 'cash-registers',
|
||||
title: 'Каси',
|
||||
description: 'Каси Checkbox, ліцензійні ключі та ключі Нової Пошти',
|
||||
path: '/cash-registers',
|
||||
roles: [],
|
||||
icon: 'M4 10h16v10H4z M7 10V4h10v6 M8 14h2 M14 14h2 M8 17h2 M14 17h2',
|
||||
},
|
||||
]
|
||||
|
||||
export function canAccess(user: User | null | undefined, roles: readonly UserRole[]): boolean {
|
||||
if (!user) return false
|
||||
return user.role === 'admin' || roles.includes(user.role)
|
||||
}
|
||||
|
||||
export function getModule(id: string): AppModule {
|
||||
const module = MODULES.find((m) => m.id === id)
|
||||
if (!module) throw new Error(`Невідомий модуль: ${id}`)
|
||||
return module
|
||||
}
|
||||
Reference in New Issue
Block a user