fix(alerts): показать подтверждённое состояние очереди сообщений

This commit is contained in:
Denozordec committed 2026-10-03 19:09:42 +07:00
1 parent b1ad9515fa
commit 89c31289bd
24 files changed
+166 -22

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
"use client"
import {useCallback,useEffect,useRef,useState} from "react"
import type {AlertsOutboxOverview} from "@mmapp/contracts/alerts"
import {can} from "@/lib/auth"
import {getAlertsOutbox} from "@/shared/api/alerts-outbox"
import {OpsPanel} from "@/components/ops-panel"
import {Button} from "@/components/ui/button"
export function AlertsOutboxPanel({base}:{base:string}){
const [data,setData]=useState<AlertsOutboxOverview|null>(null),[busy,setBusy]=useState(false),[error,setError]=useState<string|null>(null),[receivedAt,setReceivedAt]=useState<string|null>(null)
const active=useRef(true),revision=useRef(0),controller=useRef<AbortController|null>(null)
const refresh=useCallback(async()=>{
if(!active.current||!can("mm:alerts:read"))return
controller.current?.abort();controller.current=new AbortController();const requestRevision=++revision.current;setBusy(true);setError(null)
const current=()=>active.current&&requestRevision===revision.current&&can("mm:alerts:read")
try{const result=await getAlertsOutbox(base,{signal:controller.current.signal});if(current()){setData(result);setReceivedAt(new Date().toISOString())}}catch(e){if(current())setError(e instanceof Error?e.message:"Не удалось получить очередь")}
finally{if(current())setBusy(false)}
},[base])
useEffect(()=>{const generation=revision;active.current=true;let pending=true;queueMicrotask(()=>{if(pending)void refresh()});return()=>{pending=false;active.current=false;generation.current++;controller.current?.abort()}},[refresh])
return <section aria-label="Очередь Telegram" className="min-w-0"><OpsPanel title="Очередь Telegram" description="Приём сообщения API и доставка в чат — разные состояния." headerRight={<Button type="button" variant="outline" size="sm" aria-label="Обновить очередь Telegram" disabled={busy} onClick={()=>void refresh()}>Обновить</Button>} contentClassName="space-y-3 px-5 py-4">
{error?<p role="alert" className="text-xs text-destructive break-words">{error}{data?". Показан последний успешный снимок очереди.":""}</p>:null}
<p role="status" className="text-xs text-muted-foreground">{busy?"Обновление очереди…":data?"Последний успешный ответ: "+new Date(receivedAt!).toLocaleString("ru-RU"):"Данные очереди ещё не получены."}</p>
{data?<>
<p className="text-sm">Ожидают: {data.counts.pending} · API принял: {data.counts.sent} · Завершены с ошибкой: {data.counts.failed}</p>
{data.counts.unknown||data.counts.processing||data.counts.unrecognized?<p role="status" className="text-xs break-words">Нужна проверка: неизвестный результат — {data.counts.unknown}, в обработке — {data.counts.processing}, нераспознанный статус — {data.counts.unrecognized}. Эти записи автоматически не переотправляются. Обработка может означать текущий запрос или прерванный процесс отправки; проверьте чат и журнал перед повтором.</p>:null}
<p className="text-xs text-muted-foreground">Показано {data.items.length} из {data.counts.processing+data.counts.unknown} удержанных записей. Повторная отправка из этого экрана пока недоступна.</p>
{data.items.length?<ul className="space-y-2">{data.items.map(item=><li key={item.id} className="break-words text-xs"><p className="font-medium">{item.ruleName||item.id}</p><p className="text-muted-foreground">{item.status==="unknown"?"Результат неизвестен":"Запрос начат, завершение не подтверждено"} · {new Date(item.attemptedAt).toLocaleString("ru-RU")}</p></li>)}</ul>:null}
</>:null}
</OpsPanel></section>
}