Sidebar: pending-review count badge on the Review inbox item
After dispatching an agent run, its proposal goes to the review inbox, but the board gave no signal — users thought "nothing happened". The Review inbox nav item now shows an amber count of held actions awaiting review (polled), so waiting work is always visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { useEffect, useState, type ReactNode } from 'react'
|
||||
import { Link, useLocation } from 'react-router'
|
||||
import {
|
||||
BookMarked,
|
||||
@@ -21,12 +21,39 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { api } from '@/lib/api'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAuth } from '@/store/auth'
|
||||
|
||||
/** Polls the count of held actions awaiting review, so the nav can flag work is waiting. */
|
||||
function usePendingReviewCount(organizationId: string | null): number {
|
||||
const [count, setCount] = useState(0)
|
||||
useEffect(() => {
|
||||
if (!organizationId) return
|
||||
let cancelled = false
|
||||
const tick = async () => {
|
||||
try {
|
||||
const items = await api.get<unknown[]>(`/api/governance/reviews?organizationId=${organizationId}&status=Pending`)
|
||||
if (!cancelled) setCount(items.length)
|
||||
} catch {
|
||||
// leave the last known count on a transient failure
|
||||
}
|
||||
}
|
||||
void tick()
|
||||
const id = setInterval(tick, 12000)
|
||||
return () => {
|
||||
cancelled = true
|
||||
clearInterval(id)
|
||||
}
|
||||
}, [organizationId])
|
||||
return count
|
||||
}
|
||||
|
||||
export function AppShell({ children }: { children: ReactNode }) {
|
||||
const email = useAuth((s) => s.email)
|
||||
const logout = useAuth((s) => s.logout)
|
||||
const organizationId = useAuth((s) => s.organizationId)
|
||||
const reviewCount = usePendingReviewCount(organizationId)
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen text-foreground">
|
||||
@@ -59,7 +86,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
<NavItem icon={LayoutDashboard} label="Board" to="/" color="#38bdf8" />
|
||||
<NavItem icon={Sparkles} label="Team" to="/team" color="#38bdf8" />
|
||||
<NavItem icon={Inbox} label="Cartable" to="/cartable" color="#38bdf8" />
|
||||
<NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" color="#38bdf8" />
|
||||
<NavItem icon={ShieldCheck} label="Review inbox" to="/reviews" color="#38bdf8" badge={reviewCount} />
|
||||
|
||||
<NavSection label="Organization" />
|
||||
<NavItem icon={Boxes} label="Structure" to="/structure" color="#34d399" />
|
||||
@@ -115,12 +142,14 @@ function NavItem({
|
||||
to,
|
||||
muted,
|
||||
color,
|
||||
badge,
|
||||
}: {
|
||||
icon: LucideIcon
|
||||
label: string
|
||||
to?: string
|
||||
muted?: boolean
|
||||
color?: string
|
||||
badge?: number
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const active = to ? location.pathname === to : false
|
||||
@@ -142,6 +171,11 @@ function NavItem({
|
||||
<Icon className="size-3.5" />
|
||||
</span>
|
||||
{label}
|
||||
{!!badge && badge > 0 && (
|
||||
<span className="ml-auto grid h-5 min-w-5 place-items-center rounded-full bg-amber-400 px-1.5 text-[11px] font-semibold text-amber-950">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
{muted && <span className="ml-auto text-[10px] uppercase tracking-wide opacity-70">soon</span>}
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user