fix(bb-ui2): integrate ThreadView into detail panels with sample data
- Wired ThreadView component into SocialDetail and SwarmDetail - Added sample thread items for demo purposes - Removed thread placeholders Beads: bb-ui2.13 closed
This commit is contained in:
parent
8dd2d01686
commit
f6c5398f0c
21 changed files with 2630 additions and 58 deletions
|
|
@ -7,3 +7,4 @@ export { statusGradient, statusBorder, statusDotColor, sessionStateGlow } from '
|
|||
export { EpicChipStrip } from './epic-chip-strip';
|
||||
export { WorkspaceHero } from './workspace-hero';
|
||||
export { ProjectScopeControls } from './project-scope-controls';
|
||||
export { ThreadView, type ThreadItem } from './thread-view';
|
||||
|
|
|
|||
159
src/components/shared/thread-view.tsx
Normal file
159
src/components/shared/thread-view.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
'use client';
|
||||
|
||||
import { ArrowRight, Ban, CheckCircle2, MessageSquare, UserMinus } from 'lucide-react';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export type ThreadItemType = 'comment' | 'status_change' | 'protocol_event';
|
||||
|
||||
export interface ThreadItem {
|
||||
id: string;
|
||||
type: ThreadItemType;
|
||||
author?: string;
|
||||
content?: string;
|
||||
from?: string;
|
||||
to?: string;
|
||||
event?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
interface ThreadViewProps {
|
||||
items: ThreadItem[];
|
||||
onAddComment?: (text: string) => void;
|
||||
}
|
||||
|
||||
function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map((part) => part[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
}
|
||||
|
||||
function formatRelativeTime(date: Date): string {
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffSecs = Math.floor(diffMs / 1000);
|
||||
const diffMins = Math.floor(diffSecs / 60);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffSecs < 60) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function getProtocolIcon(event?: string) {
|
||||
switch (event?.toUpperCase()) {
|
||||
case 'HANDOFF':
|
||||
return <UserMinus className="w-4 h-4 text-amber-400" />;
|
||||
case 'BLOCKED':
|
||||
return <Ban className="w-4 h-4 text-rose-400" />;
|
||||
case 'CLOSED':
|
||||
return <CheckCircle2 className="w-4 h-4 text-emerald-400" />;
|
||||
default:
|
||||
return <MessageSquare className="w-4 h-4 text-text-muted" />;
|
||||
}
|
||||
}
|
||||
|
||||
function getProtocolLabel(event?: string): string {
|
||||
switch (event?.toUpperCase()) {
|
||||
case 'HANDOFF':
|
||||
return 'Handoff';
|
||||
case 'BLOCKED':
|
||||
return 'Blocked';
|
||||
case 'CLOSED':
|
||||
return 'Closed';
|
||||
default:
|
||||
return 'Event';
|
||||
}
|
||||
}
|
||||
|
||||
function CommentItem({ item }: { item: ThreadItem }) {
|
||||
return (
|
||||
<div className="flex gap-3 py-3">
|
||||
<Avatar className="h-8 w-8 flex-shrink-0">
|
||||
<AvatarImage src={undefined} alt={item.author} />
|
||||
<AvatarFallback className="bg-surface-muted text-text-body text-xs font-semibold">
|
||||
{item.author ? getInitials(item.author) : '??'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-text-primary text-sm font-medium">
|
||||
{item.author || 'Unknown'}
|
||||
</span>
|
||||
<span className="text-text-muted text-xs">
|
||||
{formatRelativeTime(item.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-text-secondary text-sm whitespace-pre-wrap break-words">
|
||||
{item.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusChangeItem({ item }: { item: ThreadItem }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 py-2 text-sm">
|
||||
<ArrowRight className="w-4 h-4 text-text-muted flex-shrink-0" />
|
||||
<span className="text-text-muted">
|
||||
Status: <span className="text-text-primary font-medium">{item.from || 'unknown'}</span>
|
||||
<ArrowRight className="w-3 h-3 inline mx-1 text-text-muted" />
|
||||
<span className="text-text-primary font-medium">{item.to || 'unknown'}</span>
|
||||
</span>
|
||||
<span className="text-text-muted text-xs ml-auto">
|
||||
{formatRelativeTime(item.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProtocolEventItem({ item }: { item: ThreadItem }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 py-2">
|
||||
<div className="flex-shrink-0">{getProtocolIcon(item.event)}</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-text-primary text-sm font-medium">
|
||||
{getProtocolLabel(item.event)}
|
||||
</span>
|
||||
{item.content && (
|
||||
<span className="text-text-secondary text-sm ml-2">{item.content}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-text-muted text-xs">
|
||||
{formatRelativeTime(item.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ThreadView({ items, onAddComment }: ThreadViewProps) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{items.length === 0 ? (
|
||||
<p className="text-text-muted text-sm italic py-4">No activity yet</p>
|
||||
) : (
|
||||
<div className="divide-y divide-white/5">
|
||||
{items.map((item) => {
|
||||
switch (item.type) {
|
||||
case 'comment':
|
||||
return <CommentItem key={item.id} item={item} />;
|
||||
case 'status_change':
|
||||
return <StatusChangeItem key={item.id} item={item} />;
|
||||
case 'protocol_event':
|
||||
return <ProtocolEventItem key={item.id} item={item} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,8 +3,34 @@
|
|||
import type { SocialCard as SocialCardData, AgentStatus } from '../../lib/social-cards';
|
||||
import { StatusBadge } from '../shared/status-badge';
|
||||
import { AgentAvatar } from '../shared/agent-avatar';
|
||||
import { ThreadView, type ThreadItem } from '../shared/thread-view';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
// Sample data for demo - remove when real data connected
|
||||
const SAMPLE_THREAD_ITEMS: ThreadItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'status_change',
|
||||
from: 'backlog',
|
||||
to: 'in_progress',
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'comment',
|
||||
author: 'zenchantlive',
|
||||
content: 'Started working on this task. Will need input from the API team.',
|
||||
timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'protocol_event',
|
||||
event: 'HANDOFF',
|
||||
content: 'Handed off to bb-agent-1 for implementation',
|
||||
timestamp: new Date(Date.now() - 30 * 60 * 1000),
|
||||
},
|
||||
];
|
||||
|
||||
interface SocialDetailProps {
|
||||
data: SocialCardData;
|
||||
}
|
||||
|
|
@ -37,13 +63,11 @@ export function SocialDetail({ data }: SocialDetailProps) {
|
|||
<StatusBadge status={data.status} size="sm" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-text-muted text-xs font-semibold uppercase tracking-wider">
|
||||
Thread
|
||||
</h3>
|
||||
<p className="text-text-muted text-sm italic">
|
||||
Thread placeholder (bb-ui2.13)
|
||||
</p>
|
||||
<ThreadView items={SAMPLE_THREAD_ITEMS} />
|
||||
</div>
|
||||
|
||||
{data.blocks.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import type { SwarmCard as SwarmCardType } from '../../lib/swarm-cards';
|
||||
import { Badge } from '../../../components/ui/badge';
|
||||
import { AgentAvatar } from '../shared/agent-avatar';
|
||||
import { ThreadView, type ThreadItem } from '../shared/thread-view';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { AlertTriangle, Clock, MessageSquare, Users } from 'lucide-react';
|
||||
import { AlertTriangle, Clock, Users } from 'lucide-react';
|
||||
|
||||
interface SwarmDetailProps {
|
||||
card: SwarmCardType;
|
||||
|
|
@ -148,21 +149,38 @@ function LastActivitySection({ date }: { date: Date }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ThreadPlaceholder() {
|
||||
// Sample data for demo - remove when real data connected
|
||||
const SAMPLE_SWARM_THREAD: ThreadItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'status_change',
|
||||
from: 'planning',
|
||||
to: 'in_progress',
|
||||
timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'comment',
|
||||
author: 'bb-agent-1',
|
||||
content: 'Starting work on the first batch of tasks.',
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'protocol_event',
|
||||
event: 'CLOSED',
|
||||
content: 'Task bb-buff.1 completed',
|
||||
timestamp: new Date(Date.now() - 30 * 60 * 1000),
|
||||
},
|
||||
];
|
||||
|
||||
function ThreadSection() {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MessageSquare className="h-3.5 w-3.5" style={{ color: 'var(--color-text-muted)' }} />
|
||||
<span className="text-xs font-semibold uppercase tracking-wider" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Thread
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="p-3 rounded-md text-center text-xs"
|
||||
style={{ backgroundColor: 'var(--color-bg-elevated)', color: 'var(--color-text-muted)' }}
|
||||
>
|
||||
Thread coming soon
|
||||
</div>
|
||||
<span className="text-xs font-semibold uppercase tracking-wider" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Thread
|
||||
</span>
|
||||
<ThreadView items={SAMPLE_SWARM_THREAD} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -200,8 +218,8 @@ export function SwarmDetail({ card }: SwarmDetailProps) {
|
|||
{/* Last Activity */}
|
||||
<LastActivitySection date={card.lastActivity} />
|
||||
|
||||
{/* Thread Placeholder */}
|
||||
<ThreadPlaceholder />
|
||||
{/* Thread */}
|
||||
<ThreadSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue