feat: verplaats Live + agent-status indicators naar NavBar
Live-dot (SSE-status) en "Agent verbonden / Geen agent" indicator zijn verhuisd van de SoloBoard-header naar de NavBar (rechts, voor de notifications-bell). Data blijft uit useSoloStore komen, gevoed door SoloRealtimeBridge in de (app)-layout. Indicators tonen alleen op /products/[id]/solo — buiten die route is de SSE-stream inactief. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5da94ae688
commit
bf464bfc31
3 changed files with 73 additions and 60 deletions
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { AppIcon } from '@/components/shared/app-icon'
|
||||
import { UserMenu } from '@/components/shared/user-menu'
|
||||
import { NotificationsBell } from '@/components/shared/notifications-bell'
|
||||
import { SoloNavStatusIndicators } from '@/components/solo/nav-status-indicators'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { setActiveProductAction } from '@/actions/active-product'
|
||||
|
||||
|
|
@ -180,8 +181,9 @@ export function NavBar({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Rechts: notifications + account-menu */}
|
||||
{/* Rechts: solo-status + notifications + account-menu */}
|
||||
<div className="flex items-center gap-2 flex-1 justify-end">
|
||||
<SoloNavStatusIndicators />
|
||||
<NotificationsBell currentUserId={userId} isDemo={isDemo} />
|
||||
<UserMenu userId={userId} username={username} email={email} roles={roles} />
|
||||
</div>
|
||||
|
|
|
|||
69
components/solo/nav-status-indicators.tsx
Normal file
69
components/solo/nav-status-indicators.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useSoloStore } from '@/stores/solo-store'
|
||||
import type { RealtimeStatus } from '@/stores/solo-store'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const SOLO_PATH_RE = /^\/products\/[^/]+\/solo$/
|
||||
|
||||
function RealtimeIndicator({
|
||||
status,
|
||||
showConnectingIndicator,
|
||||
}: {
|
||||
status: RealtimeStatus
|
||||
showConnectingIndicator: boolean
|
||||
}) {
|
||||
let color = 'bg-status-done'
|
||||
let label = 'Live'
|
||||
if (showConnectingIndicator) {
|
||||
if (status === 'disconnected') {
|
||||
color = 'bg-priority-critical'
|
||||
label = 'Verbroken — opnieuw proberen…'
|
||||
} else {
|
||||
color = 'bg-muted-foreground'
|
||||
label = 'Verbinden…'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<span
|
||||
aria-label={label}
|
||||
className={cn('inline-block h-2 w-2 rounded-full shrink-0 transition-colors', color)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function SoloNavStatusIndicators() {
|
||||
const pathname = usePathname()
|
||||
const realtimeStatus = useSoloStore((s) => s.realtimeStatus)
|
||||
const showConnectingIndicator = useSoloStore((s) => s.showConnectingIndicator)
|
||||
const connectedWorkers = useSoloStore((s) => s.connectedWorkers)
|
||||
|
||||
if (!pathname || !SOLO_PATH_RE.test(pathname)) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-2">
|
||||
<RealtimeIndicator
|
||||
status={realtimeStatus}
|
||||
showConnectingIndicator={showConnectingIndicator}
|
||||
/>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span className={cn(
|
||||
'size-2 rounded-full',
|
||||
connectedWorkers > 0 ? 'bg-status-done' : 'bg-muted-foreground/40'
|
||||
)} />
|
||||
{connectedWorkers > 0 ? 'Agent verbonden' : 'Geen agent'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -7,55 +7,13 @@ import {
|
|||
} from '@dnd-kit/core'
|
||||
import { toast } from 'sonner'
|
||||
import { useSoloStore } from '@/stores/solo-store'
|
||||
import type { RealtimeStatus } from '@/stores/solo-store'
|
||||
import { taskStatusToApi } from '@/lib/task-status'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SplitPane } from '@/components/split-pane/split-pane'
|
||||
import { SoloColumn, type ColumnStatus } from './solo-column'
|
||||
import { SoloTaskCardOverlay } from './solo-task-card'
|
||||
import { TaskDetailDialog } from './task-detail-dialog'
|
||||
import { UnassignedStoriesSheet, type UnassignedStory } from './unassigned-stories-sheet'
|
||||
|
||||
// ST-805: kleine status-dot in de header — groen wanneer SSE-stream open
|
||||
// is, grijs/rood pas zichtbaar als de connectie >4s niet open is (animatie B
|
||||
// zit in useSoloRealtime). Default groen tijdens de eerste 4s zodat micro-
|
||||
// disconnects geen flikker geven.
|
||||
function RealtimeIndicator({
|
||||
status,
|
||||
showConnectingIndicator,
|
||||
}: {
|
||||
status: RealtimeStatus
|
||||
showConnectingIndicator: boolean
|
||||
}) {
|
||||
let color = 'bg-status-done'
|
||||
let label = 'Live'
|
||||
if (showConnectingIndicator) {
|
||||
if (status === 'disconnected') {
|
||||
color = 'bg-priority-critical'
|
||||
label = 'Verbroken — opnieuw proberen…'
|
||||
} else {
|
||||
color = 'bg-muted-foreground'
|
||||
label = 'Verbinden…'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<span
|
||||
aria-label={label}
|
||||
className={cn('inline-block h-2 w-2 rounded-full shrink-0 transition-colors', color)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export interface SoloTask {
|
||||
id: string
|
||||
title: string
|
||||
|
|
@ -91,9 +49,6 @@ export function SoloBoard({
|
|||
productId, productName, sprintGoal, tasks: initialTasks, unassignedStories: initialUnassigned, isDemo,
|
||||
}: SoloBoardProps) {
|
||||
const { tasks, initTasks, optimisticMove, rollback, markPending, clearPending } = useSoloStore()
|
||||
const realtimeStatus = useSoloStore((s) => s.realtimeStatus)
|
||||
const showConnectingIndicator = useSoloStore((s) => s.showConnectingIndicator)
|
||||
const connectedWorkers = useSoloStore((s) => s.connectedWorkers)
|
||||
const [activeDragId, setActiveDragId] = useState<string | null>(null)
|
||||
const [selectedTask, setSelectedTask] = useState<SoloTask | null>(null)
|
||||
const [sheetOpen, setSheetOpen] = useState(false)
|
||||
|
|
@ -174,20 +129,7 @@ export function SoloBoard({
|
|||
<div className="flex flex-col h-full p-4 gap-4 min-h-0">
|
||||
<div className="flex items-start justify-between gap-4 shrink-0">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-base font-semibold text-foreground truncate">{productName}</h1>
|
||||
<RealtimeIndicator
|
||||
status={realtimeStatus}
|
||||
showConnectingIndicator={showConnectingIndicator}
|
||||
/>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground ml-1">
|
||||
<span className={cn(
|
||||
'size-2 rounded-full',
|
||||
connectedWorkers > 0 ? 'bg-status-done' : 'bg-muted-foreground/40'
|
||||
)} />
|
||||
{connectedWorkers > 0 ? 'Agent verbonden' : 'Geen agent'}
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-base font-semibold text-foreground truncate">{productName}</h1>
|
||||
{sprintGoal && (
|
||||
<p className="text-sm text-muted-foreground mt-0.5 line-clamp-2">{sprintGoal}</p>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue