feat(admin): Control Center redesign, Explore dual-view и полный RU/EN i18n
Refs EventHub/EventHubFrontAdmin#32
This commit is contained in:
+85
-18
@@ -5,6 +5,8 @@ export const ROUTE_ACCESS: Record<string, AdminRole[] | undefined> = {
|
||||
'/dashboard': undefined,
|
||||
'/profile': undefined,
|
||||
'/monitoring': undefined,
|
||||
'/inbox/reports': ['superadmin', 'admin', 'moderator'],
|
||||
'/inbox/tickets': ['superadmin', 'admin', 'support'],
|
||||
'/users': ['superadmin', 'admin'],
|
||||
'/calendars': ['superadmin', 'admin'],
|
||||
'/events': ['superadmin', 'admin'],
|
||||
@@ -17,29 +19,75 @@ export const ROUTE_ACCESS: Record<string, AdminRole[] | undefined> = {
|
||||
'/audit': ['superadmin'],
|
||||
};
|
||||
|
||||
export const MENU_ITEMS: Array<{
|
||||
export type NavItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
roles?: AdminRole[];
|
||||
}> = [
|
||||
{ key: '/dashboard', label: 'Дашборд' },
|
||||
{ key: '/users', label: 'Пользователи', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/calendars', label: 'Календари', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/events', label: 'События', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/reports', label: 'Жалобы', roles: ['superadmin', 'admin', 'moderator'] },
|
||||
{ key: '/reviews', label: 'Отзывы', roles: ['superadmin', 'admin', 'moderator'] },
|
||||
{ key: '/banned-words', label: 'Бан-слова', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/tickets', label: 'Тикеты', roles: ['superadmin', 'admin', 'support'] },
|
||||
{ key: '/subscriptions', label: 'Подписки', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/admins', label: 'Администраторы', roles: ['superadmin'] },
|
||||
{ key: '/audit', label: 'Аудит', roles: ['superadmin'] },
|
||||
{ key: '/monitoring', label: 'Мониторинг' },
|
||||
};
|
||||
|
||||
export type NavGroup = {
|
||||
id: string;
|
||||
label: string;
|
||||
items: NavItem[];
|
||||
};
|
||||
|
||||
/** Группы навигации Control Center (label = i18n key). */
|
||||
export const NAV_GROUPS: NavGroup[] = [
|
||||
{
|
||||
id: 'work',
|
||||
label: 'navGroup.work',
|
||||
items: [
|
||||
{ key: '/inbox/reports', label: 'nav.inboxReports', roles: ['superadmin', 'admin', 'moderator'] },
|
||||
{ key: '/inbox/tickets', label: 'nav.inboxTickets', roles: ['superadmin', 'admin', 'support'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'overview',
|
||||
label: 'navGroup.overview',
|
||||
items: [
|
||||
{ key: '/dashboard', label: 'nav.dashboard' },
|
||||
{ key: '/monitoring', label: 'nav.monitoring' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
label: 'navGroup.content',
|
||||
items: [
|
||||
{ key: '/users', label: 'nav.users', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/calendars', label: 'nav.calendars', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/events', label: 'nav.events', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/subscriptions', label: 'nav.subscriptions', roles: ['superadmin', 'admin'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'moderation',
|
||||
label: 'navGroup.moderation',
|
||||
items: [
|
||||
{ key: '/reports', label: 'nav.reports', roles: ['superadmin', 'admin', 'moderator'] },
|
||||
{ key: '/reviews', label: 'nav.reviews', roles: ['superadmin', 'admin', 'moderator'] },
|
||||
{ key: '/banned-words', label: 'nav.bannedWords', roles: ['superadmin', 'admin'] },
|
||||
{ key: '/tickets', label: 'nav.tickets', roles: ['superadmin', 'admin', 'support'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
label: 'navGroup.system',
|
||||
items: [
|
||||
{ key: '/admins', label: 'nav.admins', roles: ['superadmin'] },
|
||||
{ key: '/audit', label: 'nav.audit', roles: ['superadmin'] },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** Плоский список для совместимости и getMenuSelectedKey. */
|
||||
export const MENU_ITEMS: NavItem[] = NAV_GROUPS.flatMap((g) => g.items);
|
||||
|
||||
export function canAccessRoute(pathname: string, role: string | undefined): boolean {
|
||||
if (!role) return false;
|
||||
const base = pathname.split('/').slice(0, 2).join('/') || '/';
|
||||
const allowed = ROUTE_ACCESS[base];
|
||||
const inboxBase = pathname.startsWith('/inbox/') ? pathname.split('/').slice(0, 3).join('/') : null;
|
||||
const check = inboxBase ?? base;
|
||||
const allowed = ROUTE_ACCESS[check] ?? ROUTE_ACCESS[base];
|
||||
if (allowed === undefined) {
|
||||
return ROUTE_ACCESS[pathname] === undefined || ROUTE_ACCESS[pathname]!.includes(role as AdminRole);
|
||||
}
|
||||
@@ -53,11 +101,24 @@ export function filterMenuByRole<T extends { roles?: AdminRole[] }>(
|
||||
return items.filter((item) => !item.roles || (role && item.roles.includes(role as AdminRole)));
|
||||
}
|
||||
|
||||
export function filterNavGroupsByRole(groups: NavGroup[], role: string | undefined): NavGroup[] {
|
||||
return groups
|
||||
.map((group) => ({
|
||||
...group,
|
||||
items: filterMenuByRole(group.items, role),
|
||||
}))
|
||||
.filter((group) => group.items.length > 0);
|
||||
}
|
||||
|
||||
/** Ключ пункта меню для detail-маршрутов (/users/:id → /users). */
|
||||
export function getMenuSelectedKey(pathname: string): string {
|
||||
if (MENU_ITEMS.some((item) => item.key === pathname)) {
|
||||
return pathname;
|
||||
}
|
||||
if (pathname.startsWith('/inbox/')) {
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
if (parts.length >= 2) return `/inbox/${parts[1]}`;
|
||||
}
|
||||
const segment = pathname.split('/').filter(Boolean)[0];
|
||||
if (segment) {
|
||||
const parent = `/${segment}`;
|
||||
@@ -69,14 +130,20 @@ export function getMenuSelectedKey(pathname: string): string {
|
||||
}
|
||||
|
||||
const WS_NOTIFICATION_ROUTES: Record<'report_created' | 'ticket_created', string> = {
|
||||
report_created: '/reports',
|
||||
ticket_created: '/tickets',
|
||||
report_created: '/inbox/reports',
|
||||
ticket_created: '/inbox/tickets',
|
||||
};
|
||||
|
||||
/** Доступ к WS-уведомлению по роли (согласовано с ROUTE_ACCESS). */
|
||||
export function canReceiveWsNotification(
|
||||
type: 'report_created' | 'ticket_created',
|
||||
role: string | undefined
|
||||
): boolean {
|
||||
return canAccessRoute(WS_NOTIFICATION_ROUTES[type], role);
|
||||
}
|
||||
|
||||
/** Стартовая страница по роли после логина. */
|
||||
export function getDefaultRouteForRole(role: AdminRole | undefined): string {
|
||||
if (role === 'moderator') return '/inbox/reports';
|
||||
if (role === 'support') return '/inbox/tickets';
|
||||
return '/dashboard';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user