feat: show blocked_count in broadcast admin UI

- Add blocked_count to Broadcast interface
- 4-column stats grid with blocked card (warning color)
- Show blocked count in broadcast list when > 0
- Fetch single broadcast by ID instead of listing all
- Add i18n keys for blocked/blockedShort in ru/en/zh/fa
This commit is contained in:
Fringg
2026-02-17 18:37:45 +03:00
parent 74c4c87ec0
commit 9cf8e095b8
7 changed files with 21 additions and 5 deletions

View File

@@ -90,6 +90,7 @@ export interface Broadcast {
total_count: number;
sent_count: number;
failed_count: number;
blocked_count: number;
status:
| 'queued'
| 'in_progress'

View File

@@ -1196,6 +1196,8 @@
"total": "Total",
"sent": "Sent",
"failed": "Failed",
"blocked": "Blocked",
"blockedShort": "blocked",
"filter": "Filter",
"message": "Message",
"unknownAdmin": "Unknown",

View File

@@ -917,6 +917,8 @@
"total": "کل",
"sent": "ارسال شده",
"failed": "ناموفق",
"blocked": "مسدود شده",
"blockedShort": "مسدود",
"filter": "فیلتر",
"message": "پیام",
"unknownAdmin": "نامعلوم",

View File

@@ -1221,6 +1221,8 @@
"total": "Всего",
"sent": "Отправлено",
"failed": "Ошибки",
"blocked": "Заблокировали",
"blockedShort": "заблок.",
"filter": "Фильтр",
"message": "Сообщение",
"unknownAdmin": "Неизвестный админ",

View File

@@ -969,6 +969,8 @@
"total": "总计",
"sent": "已发送",
"failed": "失败",
"blocked": "已屏蔽",
"blockedShort": "屏蔽",
"filter": "筛选",
"message": "消息",
"unknownAdmin": "未知",

View File

@@ -167,10 +167,7 @@ export default function AdminBroadcastDetail() {
queryKey: ['admin', 'broadcasts', 'detail', broadcastId],
queryFn: async () => {
if (!broadcastId) throw new Error('Invalid broadcast ID');
const response = await adminBroadcastsApi.list(100, 0);
const found = response.items.find((b) => b.id === broadcastId);
if (!found) throw new Error('Broadcast not found');
return found;
return adminBroadcastsApi.get(broadcastId);
},
enabled: !!broadcastId && !isNaN(broadcastId),
refetchInterval: (query) => {
@@ -266,7 +263,7 @@ export default function AdminBroadcastDetail() {
)}
{/* Stats */}
<div className="grid grid-cols-3 gap-3">
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-4 text-center">
<p className="text-3xl font-bold text-dark-100">{broadcast.total_count}</p>
<p className="text-sm text-dark-400">{t('admin.broadcasts.total')}</p>
@@ -275,6 +272,10 @@ export default function AdminBroadcastDetail() {
<p className="text-3xl font-bold text-success-400">{broadcast.sent_count}</p>
<p className="text-sm text-dark-400">{t('admin.broadcasts.sent')}</p>
</div>
<div className="rounded-xl border border-warning-500/30 bg-warning-500/10 p-4 text-center">
<p className="text-3xl font-bold text-warning-400">{broadcast.blocked_count}</p>
<p className="text-sm text-dark-400">{t('admin.broadcasts.blocked')}</p>
</div>
<div className="rounded-xl border border-error-500/30 bg-error-500/10 p-4 text-center">
<p className="text-3xl font-bold text-error-400">{broadcast.failed_count}</p>
<p className="text-sm text-dark-400">{t('admin.broadcasts.failed')}</p>

View File

@@ -217,6 +217,12 @@ export default function AdminBroadcasts() {
<span>{broadcast.target_type}</span>
<span>
{broadcast.sent_count}/{broadcast.total_count}
{broadcast.blocked_count > 0 && (
<span className="text-warning-400">
{' '}
({broadcast.blocked_count} {t('admin.broadcasts.blockedShort')})
</span>
)}
</span>
<span>{new Date(broadcast.created_at).toLocaleDateString()}</span>
</div>