fix: Show conflict resolution agents in HQ dashboard
getHeadquartersDashboard had no section for active conflict agents, so initiatives with a running conflict-* agent disappeared from all HQ sections. Add resolvingConflicts array to surface them.
This commit is contained in:
52
apps/web/src/components/hq/HQResolvingConflictsSection.tsx
Normal file
52
apps/web/src/components/hq/HQResolvingConflictsSection.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { StatusDot } from '@/components/StatusDot'
|
||||
import { formatRelativeTime } from '@/lib/utils'
|
||||
import type { ResolvingConflictsItem } from './types'
|
||||
|
||||
interface Props {
|
||||
items: ResolvingConflictsItem[]
|
||||
}
|
||||
|
||||
export function HQResolvingConflictsSection({ items }: Props) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Resolving Conflicts
|
||||
</h2>
|
||||
<div className="space-y-2">
|
||||
{items.map((item) => (
|
||||
<Card key={item.agentId} className="p-4 flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<StatusDot status="resolving_conflict" variant="urgent" size="sm" pulse />
|
||||
<span className="font-semibold">{item.initiativeName}</span>
|
||||
<Badge variant="urgent" size="xs">{item.agentStatus === 'waiting_for_input' ? 'Needs Input' : 'Running'}</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{item.agentName} · started {formatRelativeTime(item.since)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: '/initiatives/$id',
|
||||
params: { id: item.initiativeId },
|
||||
search: { tab: 'execution' },
|
||||
})
|
||||
}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ vi.mock('@/lib/utils', () => ({
|
||||
import { HQWaitingForInputSection } from './HQWaitingForInputSection'
|
||||
import { HQNeedsReviewSection } from './HQNeedsReviewSection'
|
||||
import { HQNeedsApprovalSection } from './HQNeedsApprovalSection'
|
||||
import { HQResolvingConflictsSection } from './HQResolvingConflictsSection'
|
||||
import { HQBlockedSection } from './HQBlockedSection'
|
||||
import { HQEmptyState } from './HQEmptyState'
|
||||
|
||||
@@ -268,6 +269,77 @@ describe('HQNeedsApprovalSection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ─── HQResolvingConflictsSection ──────────────────────────────────────────────
|
||||
|
||||
describe('HQResolvingConflictsSection', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
|
||||
it('renders "Resolving Conflicts" heading', () => {
|
||||
render(<HQResolvingConflictsSection items={[]} />)
|
||||
expect(screen.getByText('Resolving Conflicts')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows initiative name and "Running" badge for running agent', () => {
|
||||
render(
|
||||
<HQResolvingConflictsSection
|
||||
items={[
|
||||
{
|
||||
initiativeId: 'init-1',
|
||||
initiativeName: 'My Initiative',
|
||||
agentId: 'a1',
|
||||
agentName: 'conflict-1234567890',
|
||||
agentStatus: 'running',
|
||||
since,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
expect(screen.getByText('My Initiative')).toBeInTheDocument()
|
||||
expect(screen.getByText('Running')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows "Needs Input" badge for waiting_for_input agent', () => {
|
||||
render(
|
||||
<HQResolvingConflictsSection
|
||||
items={[
|
||||
{
|
||||
initiativeId: 'init-1',
|
||||
initiativeName: 'My Initiative',
|
||||
agentId: 'a1',
|
||||
agentName: 'conflict-1234567890',
|
||||
agentStatus: 'waiting_for_input',
|
||||
since,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
expect(screen.getByText('Needs Input')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('"View" CTA navigates to /initiatives/$id?tab=execution', () => {
|
||||
render(
|
||||
<HQResolvingConflictsSection
|
||||
items={[
|
||||
{
|
||||
initiativeId: 'init-1',
|
||||
initiativeName: 'My Initiative',
|
||||
agentId: 'a1',
|
||||
agentName: 'conflict-1234567890',
|
||||
agentStatus: 'running',
|
||||
since,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button', { name: /view/i }))
|
||||
expect(mockNavigate).toHaveBeenCalledWith({
|
||||
to: '/initiatives/$id',
|
||||
params: { id: 'init-1' },
|
||||
search: { tab: 'execution' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ─── HQBlockedSection ────────────────────────────────────────────────────────
|
||||
|
||||
describe('HQBlockedSection', () => {
|
||||
|
||||
@@ -5,4 +5,5 @@ export type WaitingForInputItem = HQDashboard['waitingForInput'][number]
|
||||
export type PendingReviewInitiativeItem = HQDashboard['pendingReviewInitiatives'][number]
|
||||
export type PendingReviewPhaseItem = HQDashboard['pendingReviewPhases'][number]
|
||||
export type PlanningInitiativeItem = HQDashboard['planningInitiatives'][number]
|
||||
export type ResolvingConflictsItem = HQDashboard['resolvingConflicts'][number]
|
||||
export type BlockedPhaseItem = HQDashboard['blockedPhases'][number]
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { HQWaitingForInputSection } from "@/components/hq/HQWaitingForInputSection";
|
||||
import { HQNeedsReviewSection } from "@/components/hq/HQNeedsReviewSection";
|
||||
import { HQNeedsApprovalSection } from "@/components/hq/HQNeedsApprovalSection";
|
||||
import { HQResolvingConflictsSection } from "@/components/hq/HQResolvingConflictsSection";
|
||||
import { HQBlockedSection } from "@/components/hq/HQBlockedSection";
|
||||
import { HQEmptyState } from "@/components/hq/HQEmptyState";
|
||||
|
||||
@@ -74,6 +75,7 @@ export function HeadquartersPage() {
|
||||
data.pendingReviewInitiatives.length > 0 ||
|
||||
data.pendingReviewPhases.length > 0 ||
|
||||
data.planningInitiatives.length > 0 ||
|
||||
data.resolvingConflicts.length > 0 ||
|
||||
data.blockedPhases.length > 0;
|
||||
|
||||
return (
|
||||
@@ -107,6 +109,9 @@ export function HeadquartersPage() {
|
||||
{data.planningInitiatives.length > 0 && (
|
||||
<HQNeedsApprovalSection items={data.planningInitiatives} />
|
||||
)}
|
||||
{data.resolvingConflicts.length > 0 && (
|
||||
<HQResolvingConflictsSection items={data.resolvingConflicts} />
|
||||
)}
|
||||
{data.blockedPhases.length > 0 && (
|
||||
<HQBlockedSection items={data.blockedPhases} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user