feat(admin): Control Center redesign, Explore dual-view и полный RU/EN i18n
CI / test (push) Failing after 3m9s
CI / push-image (push) Has been skipped
CI / ci-done (push) Failing after 0s

Refs EventHub/EventHubFrontAdmin#32
This commit is contained in:
2026-07-14 17:58:33 +03:00
parent de379e28bf
commit fd2c1f10ad
95 changed files with 12699 additions and 4410 deletions
+85 -18
View File
@@ -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';
}