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 { Link, useLocation } from 'react-router'
|
||||||
import {
|
import {
|
||||||
BookMarked,
|
BookMarked,
|
||||||
@@ -21,12 +21,39 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { api } from '@/lib/api'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useAuth } from '@/store/auth'
|
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 }) {
|
export function AppShell({ children }: { children: ReactNode }) {
|
||||||
const email = useAuth((s) => s.email)
|
const email = useAuth((s) => s.email)
|
||||||
const logout = useAuth((s) => s.logout)
|
const logout = useAuth((s) => s.logout)
|
||||||
|
const organizationId = useAuth((s) => s.organizationId)
|
||||||
|
const reviewCount = usePendingReviewCount(organizationId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen text-foreground">
|
<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={LayoutDashboard} label="Board" to="/" color="#38bdf8" />
|
||||||
<NavItem icon={Sparkles} label="Team" to="/team" color="#38bdf8" />
|
<NavItem icon={Sparkles} label="Team" to="/team" color="#38bdf8" />
|
||||||
<NavItem icon={Inbox} label="Cartable" to="/cartable" 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" />
|
<NavSection label="Organization" />
|
||||||
<NavItem icon={Boxes} label="Structure" to="/structure" color="#34d399" />
|
<NavItem icon={Boxes} label="Structure" to="/structure" color="#34d399" />
|
||||||
@@ -115,12 +142,14 @@ function NavItem({
|
|||||||
to,
|
to,
|
||||||
muted,
|
muted,
|
||||||
color,
|
color,
|
||||||
|
badge,
|
||||||
}: {
|
}: {
|
||||||
icon: LucideIcon
|
icon: LucideIcon
|
||||||
label: string
|
label: string
|
||||||
to?: string
|
to?: string
|
||||||
muted?: boolean
|
muted?: boolean
|
||||||
color?: string
|
color?: string
|
||||||
|
badge?: number
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const active = to ? location.pathname === to : false
|
const active = to ? location.pathname === to : false
|
||||||
@@ -142,6 +171,11 @@ function NavItem({
|
|||||||
<Icon className="size-3.5" />
|
<Icon className="size-3.5" />
|
||||||
</span>
|
</span>
|
||||||
{label}
|
{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>}
|
{muted && <span className="ml-auto text-[10px] uppercase tracking-wide opacity-70">soon</span>}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user