From 252939f63a9c997b94603aefa43b9bae287f3eb3 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Wed, 13 May 2026 10:08:57 +0300 Subject: [PATCH] =?UTF-8?q?fix(sidebar):=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B8=D1=82=D1=8C=20reviews=20badge=20=D0=BA=20rea?= =?UTF-8?q?l=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Бэйдж 'На ревью' в sidebar (desktop + mobile) был захардкожен в 0 — TODO comment ждал cheap /drafts/pending/count endpoint. На самом деле existing /admin/reviews/pending и /admin/schema-reviews/pending уже возвращают paged response с totalElements/total в metadata — request size=1 даёт нужный counter с минимальным payload (~1 строка + 4 metadata field'а). Новый hook useReviewsBadgeCount(): - Параллельно дёргает обе queue (record + schema) - Суммирует totalElements (record) + total (schema) - Полл каждые 60s (lighter чем 30s queue refresh — badge не critical) - Gated на auth.isAuthenticated (anonymous не должен дёргать admin endpoint'ы) Sidebar.tsx (оба варианта — desktop + mobile MobileSidebar): - reviewsCount: 0 → reviews.total --- ordinis-admin-ui/src/api/queries.ts | 34 +++++++++++++++++++ .../src/components/layout/Sidebar.tsx | 14 ++++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/ordinis-admin-ui/src/api/queries.ts b/ordinis-admin-ui/src/api/queries.ts index 4affe22..efa4022 100644 --- a/ordinis-admin-ui/src/api/queries.ts +++ b/ordinis-admin-ui/src/api/queries.ts @@ -622,6 +622,40 @@ export const reviewQueueQuery = (page = 0, size = 50) => export const useReviewQueue = (page = 0, size = 50) => useQuery(reviewQueueQuery(page, size)) +/** + * Lightweight count для sidebar badge — fetch only metadata (size=1). + * + *

Combines record-reviews + schema-reviews queues. Возвращает сумму pending + * drafts обоих видов. Поллится каждые 30s в синк с queue queries — reviewer + * увидит свежий counter при возврате на app даже если /reviews не открыт. + * + *

Gated на authenticated — anonymous не должен дёргать admin endpoint'ы + * (вернут 401, шум в логах). Когда Phase 1d Keycloak roles подключатся, + * этот gate можно сузить до approver-role (useCanReviewSchema). + */ +export const useReviewsBadgeCount = (enabled = true) => { + const recordQ = useQuery({ + ...reviewQueueQuery(0, 1), + enabled, + // Override interval: badge ОК с 60s — это не deep view, lower frequency + // снижает background load когда tab idle. + refetchInterval: 60_000, + }) + const schemaQ = useQuery({ + ...schemaReviewQueueQuery(0, 1), + enabled, + refetchInterval: 60_000, + }) + const recordCount = recordQ.data?.totalElements ?? 0 + const schemaCount = schemaQ.data?.total ?? 0 + return { + total: recordCount + schemaCount, + recordCount, + schemaCount, + isLoading: recordQ.isLoading || schemaQ.isLoading, + } +} + /** * Pending drafts на конкретный dict — для бейджа "На review" в records list. * Лёгкий poll каждые 30s. Backend возвращает только PENDING (фильтрация в SQL). diff --git a/ordinis-admin-ui/src/components/layout/Sidebar.tsx b/ordinis-admin-ui/src/components/layout/Sidebar.tsx index 4bf6ef4..84fa3be 100644 --- a/ordinis-admin-ui/src/components/layout/Sidebar.tsx +++ b/ordinis-admin-ui/src/components/layout/Sidebar.tsx @@ -14,7 +14,7 @@ import { } from 'lucide-react' import { useAuth } from 'react-oidc-context' import { useCanMutate } from '@/auth/useCanMutate' -import { useDictionaries, useMyDrafts } from '@/api/queries' +import { useDictionaries, useMyDrafts, useReviewsBadgeCount } from '@/api/queries' import { Sheet, SheetContent } from '@/ui' import { ThemeSwitch } from '@/components/layout/ThemeSwitch' import { cn } from '@/lib/utils' @@ -55,13 +55,14 @@ export function Sidebar() { const dictsQuery = useDictionaries() // Loaded just for сидбар counter — light request, кэшируется. const myDrafts = useMyDrafts(0, 1) + // Reviews badge — суммирует record-reviews + schema-reviews pending queues + // через size=1 paged запросы (cheap: одна запись + metadata). Gated на + // authenticated, чтобы anonymous не пытался admin endpoint'ы. + const reviews = useReviewsBadgeCount(auth.isAuthenticated) const dictsCount = dictsQuery.data?.length ?? 0 const myDraftsCount = myDrafts.data?.totalElements ?? 0 - // Reviews count бэйдж — TODO когда будет cheap endpoint /drafts/pending/count. - // Сейчас GET /drafts/pending возвращает page, paged запрос на каждый mount - // sidebar'а — overkill. Skip badge. - const reviewsCount = 0 + const reviewsCount = reviews.total // Flat list per redesign/compact.html — no section labels, /graph removed // (now reachable via catalog toolbar "Граф ⇄" button). Order: Главная → @@ -392,10 +393,11 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => const canMutate = useCanMutate() const dictsQuery = useDictionaries() const myDrafts = useMyDrafts(0, 1) + const reviews = useReviewsBadgeCount(auth.isAuthenticated) const dictsCount = dictsQuery.data?.length ?? 0 const myDraftsCount = myDrafts.data?.totalElements ?? 0 - const reviewsCount = 0 + const reviewsCount = reviews.total // Flat list per redesign — same as desktop Sidebar above. const sections: NavSection[] = [