fix(ws): маршруты сущностей и локализованные уведомления
Клик по WS-уведомлению ведёт на детальную страницу, тексты через i18n. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+23
-31
@@ -24,6 +24,8 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import { MENU_ITEMS, filterMenuByRole, getMenuSelectedKey, canReceiveWsNotification } from '../config/adminAccess';
|
||||
import { getEntityDetailPath } from '../utils/entityRoutes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
const { Text } = Typography;
|
||||
@@ -35,6 +37,7 @@ const AdminLayout: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user, logout } = useAuthStore();
|
||||
const { t } = useTranslation();
|
||||
const { token: { colorBgContainer } } = theme.useToken();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
@@ -49,26 +52,21 @@ const AdminLayout: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
const { report_id, target_type, target_id, reason } = msg.data || {};
|
||||
const targetPath = target_id ? getEntityDetailPath(target_type, target_id) : undefined;
|
||||
notification.info({
|
||||
title: 'Новая жалоба',
|
||||
description: (
|
||||
<span>
|
||||
Жалоба{' '}
|
||||
{report_id ? (
|
||||
<a href={`/reports/${report_id}`} onClick={(e) => { e.preventDefault(); navigate(`/reports/${report_id}`); }} style={{ fontWeight: 600 }}>
|
||||
#{report_id}
|
||||
</a>
|
||||
) : ('#?')}{' '}
|
||||
на {target_type || 'неизвестный тип'}{' '}
|
||||
{target_id ? (
|
||||
<a href={`/${target_type}s/${target_id}`} onClick={(e) => { e.preventDefault(); navigate(`/${target_type}s/${target_id}`); }} style={{ fontWeight: 600 }}>
|
||||
{target_id}
|
||||
</a>
|
||||
) : ('?')}{' '}
|
||||
{reason ? `(${reason})` : ''}
|
||||
</span>
|
||||
),
|
||||
key: report_id ? `report-${report_id}` : `report-${Date.now()}`,
|
||||
message: t('ws.newReport'),
|
||||
description: [
|
||||
report_id ? `Жалоба #${report_id}` : 'Жалоба',
|
||||
target_type ? `на ${target_type}` : '',
|
||||
target_id ?? '',
|
||||
reason ? `(${reason})` : '',
|
||||
].filter(Boolean).join(' '),
|
||||
placement: 'topRight',
|
||||
onClick: () => {
|
||||
if (report_id) navigate(`/reports/${report_id}`);
|
||||
else if (targetPath) navigate(targetPath);
|
||||
},
|
||||
});
|
||||
} else if (msg.type === 'ticket_created') {
|
||||
if (!canReceiveWsNotification('ticket_created', user?.role)) {
|
||||
@@ -76,25 +74,19 @@ const AdminLayout: React.FC = () => {
|
||||
}
|
||||
const { ticket_id } = msg.data || {};
|
||||
notification.info({
|
||||
title: 'Новый тикет',
|
||||
description: (
|
||||
<span>
|
||||
Тикет{' '}
|
||||
{ticket_id ? (
|
||||
<a href={`/tickets/${ticket_id}`} onClick={(e) => { e.preventDefault(); navigate(`/tickets/${ticket_id}`); }} style={{ fontWeight: 600 }}>
|
||||
#{ticket_id}
|
||||
</a>
|
||||
) : ('без ID')}{' '}
|
||||
создан
|
||||
</span>
|
||||
),
|
||||
key: ticket_id ? `ticket-${ticket_id}` : `ticket-${Date.now()}`,
|
||||
message: t('ws.newTicket'),
|
||||
description: ticket_id ? `Тикет #${ticket_id} создан` : 'Создан новый тикет',
|
||||
placement: 'topRight',
|
||||
onClick: () => {
|
||||
if (ticket_id) navigate(`/tickets/${ticket_id}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
window.addEventListener('admin-ws-message', handler as EventListener);
|
||||
return () => window.removeEventListener('admin-ws-message', handler as EventListener);
|
||||
}, [navigate, user?.role]);
|
||||
}, [navigate, user?.role, t]);
|
||||
|
||||
const onlineNodes = React.useMemo(() => {
|
||||
const cutoff = dayjs().subtract(NODE_STATS_THRESHOLD, 'minute');
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Маршруты к сущностям по target_type (жалобы, WS-уведомления). */
|
||||
const ENTITY_PATH: Record<string, string> = {
|
||||
event: 'events',
|
||||
calendar: 'calendars',
|
||||
review: 'reviews',
|
||||
user: 'users',
|
||||
report: 'reports',
|
||||
ticket: 'tickets',
|
||||
};
|
||||
|
||||
export function getEntityDetailPath(type: string | undefined, id: string): string {
|
||||
const segment = type ? ENTITY_PATH[type] ?? `${type}s` : 'unknown';
|
||||
return `/${segment}/${id}`;
|
||||
}
|
||||
Reference in New Issue
Block a user