Files
syndarix/frontend/src/features/issues/components/IssueDetailPanel.tsx
Felipe Cardoso 5b1e2852ea feat(frontend): implement main dashboard page (#48)
Implement the main dashboard / projects list page for Syndarix as the landing
page after login. The implementation includes:

Dashboard Components:
- QuickStats: Overview cards showing active projects, agents, issues, approvals
- ProjectsSection: Grid/list view with filtering and sorting controls
- ProjectCardGrid: Rich project cards for grid view
- ProjectRowList: Compact rows for list view
- ActivityFeed: Real-time activity sidebar with connection status
- PerformanceCard: Performance metrics display
- EmptyState: Call-to-action for new users
- ProjectStatusBadge: Status indicator with icons
- ComplexityIndicator: Visual complexity dots
- ProgressBar: Accessible progress bar component

Features:
- Projects grid/list view with view mode toggle
- Filter by status (all, active, paused, completed, archived)
- Sort by recent, name, progress, or issues
- Quick stats overview with counts
- Real-time activity feed sidebar with live/reconnecting status
- Performance metrics card
- Create project button linking to wizard
- Responsive layout for mobile/desktop
- Loading skeleton states
- Empty state for new users

API Integration:
- useProjects hook for fetching projects (mock data until backend ready)
- useDashboardStats hook for statistics
- TanStack Query for caching and data fetching

Testing:
- 37 unit tests covering all dashboard components
- E2E test suite for dashboard functionality
- Accessibility tests (keyboard nav, aria attributes, heading hierarchy)

Technical:
- TypeScript strict mode compliance
- ESLint passing
- WCAG AA accessibility compliance
- Mobile-first responsive design
- Dark mode support via semantic tokens
- Follows design system guidelines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 23:46:50 +01:00

164 lines
5.4 KiB
TypeScript

'use client';
/**
* IssueDetailPanel Component
*
* Side panel showing issue details (assignee, labels, sprint, etc.)
*
* @module features/issues/components/IssueDetailPanel
*/
import { GitBranch, GitPullRequest, Tag, Bot, User } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import type { IssueDetail } from '../types';
interface IssueDetailPanelProps {
issue: IssueDetail;
className?: string;
}
export function IssueDetailPanel({ issue, className }: IssueDetailPanelProps) {
return (
<div className={cn('space-y-6', className)}>
{/* Assignment Panel */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Details</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Assignee */}
<div>
<p className="text-sm text-muted-foreground">Assignee</p>
{issue.assignee ? (
<div className="mt-1 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-sm font-medium text-primary">
{issue.assignee.avatar ||
(issue.assignee.type === 'agent' ? (
<Bot className="h-4 w-4" aria-hidden="true" />
) : (
<User className="h-4 w-4" aria-hidden="true" />
))}
</div>
<div>
<p className="font-medium">{issue.assignee.name}</p>
<p className="text-xs text-muted-foreground capitalize">
{issue.assignee.type}
</p>
</div>
</div>
) : (
<p className="mt-1 text-sm text-muted-foreground">Unassigned</p>
)}
</div>
<Separator />
{/* Reporter */}
<div>
<p className="text-sm text-muted-foreground">Reporter</p>
<div className="mt-1 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-muted text-sm font-medium">
{issue.reporter.avatar ||
(issue.reporter.type === 'agent' ? (
<Bot className="h-4 w-4" aria-hidden="true" />
) : (
<User className="h-4 w-4" aria-hidden="true" />
))}
</div>
<p className="font-medium">{issue.reporter.name}</p>
</div>
</div>
<Separator />
{/* Sprint */}
<div>
<p className="text-sm text-muted-foreground">Sprint</p>
<p className="font-medium">{issue.sprint || 'Backlog'}</p>
</div>
{/* Story Points */}
{issue.story_points !== null && (
<div>
<p className="text-sm text-muted-foreground">Story Points</p>
<p className="font-medium">{issue.story_points}</p>
</div>
)}
{/* Due Date */}
{issue.due_date && (
<div>
<p className="text-sm text-muted-foreground">Due Date</p>
<p className="font-medium">
{new Date(issue.due_date).toLocaleDateString()}
</p>
</div>
)}
<Separator />
{/* Labels */}
<div>
<p className="text-sm text-muted-foreground">Labels</p>
<div className="mt-2 flex flex-wrap gap-1">
{issue.labels.map((label) => (
<Badge
key={label.id}
variant="secondary"
className="text-xs"
style={
label.color
? { backgroundColor: `${label.color}20`, color: label.color }
: undefined
}
>
<Tag className="mr-1 h-3 w-3" aria-hidden="true" />
{label.name}
</Badge>
))}
{issue.labels.length === 0 && (
<span className="text-sm text-muted-foreground">No labels</span>
)}
</div>
</div>
</CardContent>
</Card>
{/* Development */}
{(issue.branch || issue.pull_request) && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Development</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{issue.branch && (
<div className="flex items-center gap-2">
<GitBranch
className="h-4 w-4 text-muted-foreground"
aria-hidden="true"
/>
<span className="font-mono text-sm">{issue.branch}</span>
</div>
)}
{issue.pull_request && (
<div className="flex items-center gap-2">
<GitPullRequest
className="h-4 w-4 text-muted-foreground"
aria-hidden="true"
/>
<span className="text-sm">{issue.pull_request}</span>
<Badge variant="outline" className="text-xs">
Open
</Badge>
</div>
)}
</CardContent>
</Card>
)}
</div>
);
}