forked from cardosofelipe/fast-next-template
feat(frontend): Add main dashboard prototype for #47
- Create interactive main dashboard / projects list page prototype - Add grid and list view modes for projects with toggle - Implement real-time activity feed with simulated SSE events - Add project status badges (Active, Paused, Completed, Archived) - Add complexity indicator (3-dot system) - Include quick stats cards (active projects, agents, issues, approvals) - Add filter by status and sort controls - Implement empty state for new users (with toggle for demo) - Add notifications dropdown with pending approvals - Add user menu dropdown - Include performance summary sidebar card - Responsive layout (4-col desktop, 3-col tablet, 1-col mobile) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
225
frontend/src/app/[locale]/prototypes/main-dashboard/README.md
Normal file
225
frontend/src/app/[locale]/prototypes/main-dashboard/README.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Main Dashboard / Projects List - Design Prototype
|
||||
|
||||
## Overview
|
||||
|
||||
The Main Dashboard is the landing page users see after logging in to Syndarix. It provides a comprehensive overview of all their projects, real-time activity across the platform, and quick access to create new projects. This is the central hub from which users navigate to individual project dashboards.
|
||||
|
||||
## User Stories
|
||||
|
||||
- As a user, I want to see all my projects at a glance so I can quickly assess overall status
|
||||
- As a user, I want to see real-time activity from my agents so I stay informed of progress
|
||||
- As a user, I want to quickly create a new project without leaving the dashboard
|
||||
- As a user, I want to filter and sort my projects to find specific ones easily
|
||||
- As a user, I want to switch between grid and list views based on my preference
|
||||
- As a new user, I want a clear call-to-action to create my first project
|
||||
|
||||
## Key Screens
|
||||
|
||||
### 1. Navigation Header
|
||||
- Syndarix logo with link to prototypes index
|
||||
- Global search bar (placeholder) for searching projects, issues, and agents
|
||||
- Notifications bell with badge count for pending approvals
|
||||
- User menu dropdown with profile, settings, and logout options
|
||||
|
||||
### 2. Welcome Section
|
||||
- Personalized welcome message with user name
|
||||
- Quick summary stats (active projects, working agents)
|
||||
- Prominent "New Project" button
|
||||
|
||||
### 3. Quick Stats Cards
|
||||
- Active Projects count with icon
|
||||
- Agents Active count
|
||||
- Open Issues total across all projects
|
||||
- Pending Approvals requiring user action
|
||||
|
||||
### 4. Projects Section
|
||||
- **Grid View**: Card-based layout showing project details
|
||||
- Project icon with status-based coloring
|
||||
- Status badge (Active, Paused, Completed, Archived)
|
||||
- Complexity indicator (3-dot system)
|
||||
- Quick stats grid (issues, agents, current sprint)
|
||||
- Progress bar with percentage
|
||||
- Last activity timestamp
|
||||
- **List View**: Compact row-based layout for data-dense viewing
|
||||
- All key information in a single scannable row
|
||||
- Responsive - hides some columns on smaller screens
|
||||
- **Controls**: Filter by status, sort options, view toggle
|
||||
- **Empty State**: For new users with no projects (toggle available for demo)
|
||||
|
||||
### 5. Activity Feed (Right Sidebar)
|
||||
- Real-time connection indicator (Live/Reconnecting)
|
||||
- Chronological list of events across all projects
|
||||
- Event types: agent messages, issue updates, sprint events, approvals
|
||||
- Project attribution for each event
|
||||
- Action buttons for pending approvals
|
||||
- "View All Activity" link
|
||||
|
||||
### 6. Performance Summary
|
||||
- Average sprint velocity
|
||||
- Issues resolved (7-day window)
|
||||
- Agent uptime percentage
|
||||
- Average approval response time
|
||||
|
||||
## User Flow
|
||||
|
||||
1. User logs in and lands on the main dashboard
|
||||
2. User scans quick stats to get an overview of platform status
|
||||
3. User reviews the projects list to see current work
|
||||
4. User optionally switches between grid/list views or applies filters
|
||||
5. User monitors activity feed for real-time updates
|
||||
6. If notifications badge shows pending items, user reviews approvals
|
||||
7. User clicks a project card to navigate to the project dashboard
|
||||
8. Alternatively, user clicks "New Project" to start a new project
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Layout
|
||||
- Responsive 4-column grid on desktop: 3 columns for main content, 1 for sidebar
|
||||
- Sidebar collapses below main content on mobile
|
||||
- Sticky navigation header for easy access to search and notifications
|
||||
|
||||
### Project Cards (Grid View)
|
||||
- Fixed height cards for visual consistency in grid
|
||||
- Three-column quick stats (issues, agents, sprint) for rapid scanning
|
||||
- Progress bar as the main visual indicator of project health
|
||||
- Complexity indicator uses a 3-dot system (low/medium/high)
|
||||
|
||||
### Project Rows (List View)
|
||||
- All critical information visible without scrolling
|
||||
- Progressive disclosure: less info on smaller screens
|
||||
- Clear click affordance with hover states and chevron
|
||||
|
||||
### Status Colors
|
||||
- Active: Green (positive, working)
|
||||
- Paused: Yellow (attention, on hold)
|
||||
- Completed: Blue (success, finished)
|
||||
- Archived: Gray (inactive, historical)
|
||||
|
||||
### Activity Feed
|
||||
- Live connection indicator gives confidence in real-time updates
|
||||
- Events grouped by time (relative timestamps)
|
||||
- Action items (approvals) highlighted with yellow background
|
||||
- Limited to 8 items with "View All" for full history
|
||||
|
||||
### Empty State
|
||||
- Friendly message for new users
|
||||
- Clear call-to-action to create first project
|
||||
- Dashed border to indicate "fill me" visually
|
||||
|
||||
## States
|
||||
|
||||
### Loading
|
||||
- Skeleton cards in grid view
|
||||
- Skeleton rows in list view
|
||||
- Activity feed shows loading placeholders
|
||||
- Stats cards show placeholder numbers
|
||||
|
||||
### Empty
|
||||
- "No projects yet" message with illustration
|
||||
- Encouragement text explaining the value proposition
|
||||
- Prominent "Create Your First Project" button
|
||||
- Toggle available in prototype to switch between empty/populated
|
||||
|
||||
### Error
|
||||
- Toast notification for failed operations
|
||||
- Retry buttons where applicable
|
||||
- Activity feed shows "Connection lost" with reconnection status
|
||||
|
||||
### Real-time Updates
|
||||
- Activity feed updates every 30 seconds (simulated in prototype)
|
||||
- Connection status indicator shows Live/Reconnecting
|
||||
- New events animate into the feed at the top
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
### Desktop (lg: 1024px+)
|
||||
- 4-column layout (3 main + 1 sidebar)
|
||||
- Grid view shows 3 project cards per row
|
||||
- Full activity feed visible
|
||||
- All stats and filters inline
|
||||
|
||||
### Tablet (md: 768px)
|
||||
- 3-column layout (main takes full width, sidebar below)
|
||||
- Grid view shows 2 project cards per row
|
||||
- Search visible in header
|
||||
- Some list view columns hidden
|
||||
|
||||
### Mobile (< 768px)
|
||||
- Single column, vertical stack
|
||||
- Grid view shows 1 project card per row
|
||||
- Search hidden (accessible via icon)
|
||||
- Compact quick stats (2x2 grid)
|
||||
- Activity feed collapsible
|
||||
|
||||
## Accessibility Notes
|
||||
|
||||
- All interactive elements are keyboard navigable
|
||||
- Status badges use both color AND text labels
|
||||
- Complexity indicator has title attribute for screen readers
|
||||
- Activity feed connection status announced to screen readers
|
||||
- Focus states visible on all buttons and cards
|
||||
- Color contrast meets WCAG AA standards
|
||||
- View toggle buttons have aria-labels
|
||||
|
||||
## Components Used
|
||||
|
||||
- Card, CardHeader, CardTitle, CardContent, CardDescription
|
||||
- Button (default, outline, ghost, secondary variants)
|
||||
- Badge (default, secondary, outline variants)
|
||||
- Input (for search)
|
||||
- Avatar, AvatarFallback
|
||||
- DropdownMenu components
|
||||
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue
|
||||
- Separator
|
||||
- Lucide icons for visual indicators
|
||||
|
||||
## Interactive Features (Prototype)
|
||||
|
||||
1. **View Toggle**: Switch between grid and list views
|
||||
2. **Filter Dropdown**: Filter projects by status (all, active, paused, completed, archived)
|
||||
3. **Sort Dropdown**: Sort by recent, name, progress, or issues
|
||||
4. **Empty State Toggle**: Button to switch between empty and populated states for demo
|
||||
5. **Simulated Activity Feed**: New events appear every 30 seconds
|
||||
6. **Connection Status**: Randomly disconnects/reconnects to show the indicator
|
||||
7. **Notifications Dropdown**: Shows pending approval items
|
||||
8. **User Menu**: Dropdown with profile options
|
||||
|
||||
## Questions for Review
|
||||
|
||||
1. Is the 4-column layout optimal or should the activity feed take more/less space?
|
||||
2. Should project cards show more or fewer quick stats?
|
||||
3. Is the complexity indicator (3 dots) intuitive enough?
|
||||
4. Should the activity feed show events from ALL projects or have a filter?
|
||||
5. Is the empty state message compelling enough for new users?
|
||||
6. Should there be a "pinned projects" feature for frequent access?
|
||||
7. Is the search bar placement optimal or should it be more prominent?
|
||||
|
||||
## How to View
|
||||
|
||||
Navigate to: `/prototypes/main-dashboard`
|
||||
|
||||
## Mock Data Summary
|
||||
|
||||
### Projects (5 total)
|
||||
- E-Commerce Platform Redesign (Active, High complexity)
|
||||
- Mobile Banking App (Active, High complexity)
|
||||
- Internal HR Portal (Paused, Medium complexity)
|
||||
- API Gateway Modernization (Completed, High complexity)
|
||||
- Customer Support Chatbot (Archived, Low complexity)
|
||||
|
||||
### Activity Events (10 initial)
|
||||
- Mix of agent messages, issue updates, sprint events, and approval requests
|
||||
- Timestamps range from 2 minutes ago to 5 hours ago
|
||||
- One pending approval request highlighted
|
||||
|
||||
## Next Steps
|
||||
|
||||
After approval:
|
||||
1. Connect to real user authentication for personalized welcome
|
||||
2. Implement actual project list API integration
|
||||
3. Connect to SSE/WebSocket for real-time activity feed
|
||||
4. Add project creation modal/wizard
|
||||
5. Implement global search functionality
|
||||
6. Add loading skeletons for all data sections
|
||||
7. Implement persistent view preference (grid/list)
|
||||
8. Add project quick actions (pause, archive) from dashboard
|
||||
927
frontend/src/app/[locale]/prototypes/main-dashboard/page.tsx
Normal file
927
frontend/src/app/[locale]/prototypes/main-dashboard/page.tsx
Normal file
@@ -0,0 +1,927 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
Plus,
|
||||
Search,
|
||||
Bell,
|
||||
LayoutGrid,
|
||||
List,
|
||||
ChevronRight,
|
||||
Bot,
|
||||
CircleDot,
|
||||
Clock,
|
||||
Activity,
|
||||
Settings,
|
||||
User,
|
||||
LogOut,
|
||||
GitBranch,
|
||||
MessageSquare,
|
||||
PlayCircle,
|
||||
CheckCircle2,
|
||||
AlertTriangle,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
Zap,
|
||||
Gauge,
|
||||
Archive,
|
||||
Pause,
|
||||
Filter,
|
||||
ArrowUpDown,
|
||||
} from 'lucide-react';
|
||||
|
||||
// Mock data for projects
|
||||
const mockProjects = [
|
||||
{
|
||||
id: 'proj-001',
|
||||
name: 'E-Commerce Platform Redesign',
|
||||
description: 'Complete redesign of the e-commerce platform with modern UI/UX',
|
||||
status: 'active',
|
||||
complexity: 'high',
|
||||
openIssues: 12,
|
||||
activeAgents: 4,
|
||||
currentSprint: 'Sprint 3',
|
||||
lastActivity: '2 min ago',
|
||||
progress: 67,
|
||||
},
|
||||
{
|
||||
id: 'proj-002',
|
||||
name: 'Mobile Banking App',
|
||||
description: 'Native mobile app for banking services with biometric authentication',
|
||||
status: 'active',
|
||||
complexity: 'high',
|
||||
openIssues: 8,
|
||||
activeAgents: 5,
|
||||
currentSprint: 'Sprint 2',
|
||||
lastActivity: '15 min ago',
|
||||
progress: 45,
|
||||
},
|
||||
{
|
||||
id: 'proj-003',
|
||||
name: 'Internal HR Portal',
|
||||
description: 'Employee self-service portal for HR operations',
|
||||
status: 'paused',
|
||||
complexity: 'medium',
|
||||
openIssues: 5,
|
||||
activeAgents: 0,
|
||||
currentSprint: 'Sprint 1',
|
||||
lastActivity: '2 days ago',
|
||||
progress: 23,
|
||||
},
|
||||
{
|
||||
id: 'proj-004',
|
||||
name: 'API Gateway Modernization',
|
||||
description: 'Migrate legacy API gateway to modern microservices architecture',
|
||||
status: 'completed',
|
||||
complexity: 'high',
|
||||
openIssues: 0,
|
||||
activeAgents: 0,
|
||||
currentSprint: 'Sprint 5',
|
||||
lastActivity: '1 week ago',
|
||||
progress: 100,
|
||||
},
|
||||
{
|
||||
id: 'proj-005',
|
||||
name: 'Customer Support Chatbot',
|
||||
description: 'AI-powered chatbot for customer support automation',
|
||||
status: 'archived',
|
||||
complexity: 'low',
|
||||
openIssues: 0,
|
||||
activeAgents: 0,
|
||||
currentSprint: 'Sprint 3',
|
||||
lastActivity: '1 month ago',
|
||||
progress: 100,
|
||||
},
|
||||
];
|
||||
|
||||
// Mock activity events (simulated real-time)
|
||||
const mockActivityEvents = [
|
||||
{
|
||||
id: 'evt-001',
|
||||
type: 'agent_message',
|
||||
project: 'E-Commerce Platform Redesign',
|
||||
projectId: 'proj-001',
|
||||
agent: 'Product Owner',
|
||||
message: 'Approved user story #42: Cart checkout flow',
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 1000),
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
id: 'evt-002',
|
||||
type: 'issue_update',
|
||||
project: 'Mobile Banking App',
|
||||
projectId: 'proj-002',
|
||||
agent: 'Backend Engineer',
|
||||
message: 'Moved issue #38 to "In Review"',
|
||||
timestamp: new Date(Date.now() - 8 * 60 * 1000),
|
||||
icon: GitBranch,
|
||||
},
|
||||
{
|
||||
id: 'evt-003',
|
||||
type: 'agent_status',
|
||||
project: 'E-Commerce Platform Redesign',
|
||||
projectId: 'proj-001',
|
||||
agent: 'Frontend Engineer',
|
||||
message: 'Started working on issue #45',
|
||||
timestamp: new Date(Date.now() - 15 * 60 * 1000),
|
||||
icon: PlayCircle,
|
||||
},
|
||||
{
|
||||
id: 'evt-004',
|
||||
type: 'sprint_event',
|
||||
project: 'Mobile Banking App',
|
||||
projectId: 'proj-002',
|
||||
agent: 'System',
|
||||
message: 'Sprint 2 daily standup completed',
|
||||
timestamp: new Date(Date.now() - 45 * 60 * 1000),
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
{
|
||||
id: 'evt-005',
|
||||
type: 'approval_request',
|
||||
project: 'E-Commerce Platform Redesign',
|
||||
projectId: 'proj-001',
|
||||
agent: 'Architect',
|
||||
message: 'Requesting approval for API design document',
|
||||
timestamp: new Date(Date.now() - 60 * 60 * 1000),
|
||||
icon: AlertTriangle,
|
||||
requiresAction: true,
|
||||
},
|
||||
{
|
||||
id: 'evt-006',
|
||||
type: 'agent_message',
|
||||
project: 'Mobile Banking App',
|
||||
projectId: 'proj-002',
|
||||
agent: 'QA Engineer',
|
||||
message: 'Test suite passed: 142 tests, 0 failures',
|
||||
timestamp: new Date(Date.now() - 90 * 60 * 1000),
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
{
|
||||
id: 'evt-007',
|
||||
type: 'issue_update',
|
||||
project: 'E-Commerce Platform Redesign',
|
||||
projectId: 'proj-001',
|
||||
agent: 'Backend Engineer',
|
||||
message: 'Created PR #78 for payment integration',
|
||||
timestamp: new Date(Date.now() - 120 * 60 * 1000),
|
||||
icon: GitBranch,
|
||||
},
|
||||
{
|
||||
id: 'evt-008',
|
||||
type: 'sprint_event',
|
||||
project: 'E-Commerce Platform Redesign',
|
||||
projectId: 'proj-001',
|
||||
agent: 'System',
|
||||
message: 'Sprint 3 started with 15 issues',
|
||||
timestamp: new Date(Date.now() - 180 * 60 * 1000),
|
||||
icon: PlayCircle,
|
||||
},
|
||||
{
|
||||
id: 'evt-009',
|
||||
type: 'agent_status',
|
||||
project: 'Mobile Banking App',
|
||||
projectId: 'proj-002',
|
||||
agent: 'Architect',
|
||||
message: 'Completed security audit review',
|
||||
timestamp: new Date(Date.now() - 240 * 60 * 1000),
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
{
|
||||
id: 'evt-010',
|
||||
type: 'issue_update',
|
||||
project: 'Mobile Banking App',
|
||||
projectId: 'proj-002',
|
||||
agent: 'Frontend Engineer',
|
||||
message: 'Resolved issue #32: Login screen layout',
|
||||
timestamp: new Date(Date.now() - 300 * 60 * 1000),
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
];
|
||||
|
||||
// Helper function to format relative time
|
||||
function formatRelativeTime(date: Date): string {
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins} min ago`;
|
||||
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
|
||||
// Project status badge component
|
||||
function ProjectStatusBadge({ status }: { status: string }) {
|
||||
const variants: Record<string, { label: string; className: string; icon: React.ElementType }> = {
|
||||
active: {
|
||||
label: 'Active',
|
||||
className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
icon: PlayCircle,
|
||||
},
|
||||
paused: {
|
||||
label: 'Paused',
|
||||
className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
||||
icon: Pause,
|
||||
},
|
||||
completed: {
|
||||
label: 'Completed',
|
||||
className: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
||||
icon: CheckCircle2,
|
||||
},
|
||||
archived: {
|
||||
label: 'Archived',
|
||||
className: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300',
|
||||
icon: Archive,
|
||||
},
|
||||
};
|
||||
|
||||
const variant = variants[status] || variants.active;
|
||||
const Icon = variant.icon;
|
||||
|
||||
return (
|
||||
<Badge className={`gap-1 ${variant.className}`} variant="outline">
|
||||
<Icon className="h-3 w-3" />
|
||||
{variant.label}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
// Complexity indicator component
|
||||
function ComplexityIndicator({ complexity }: { complexity: string }) {
|
||||
const variants: Record<string, { label: string; dots: number; color: string }> = {
|
||||
low: { label: 'Low', dots: 1, color: 'bg-green-500' },
|
||||
medium: { label: 'Medium', dots: 2, color: 'bg-yellow-500' },
|
||||
high: { label: 'High', dots: 3, color: 'bg-red-500' },
|
||||
};
|
||||
|
||||
const variant = variants[complexity] || variants.medium;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1" title={`Complexity: ${variant.label}`}>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`h-2 w-2 rounded-full ${i <= variant.dots ? variant.color : 'bg-muted'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Progress bar component
|
||||
function ProgressBar({ value, size = 'default' }: { value: number; size?: 'default' | 'sm' }) {
|
||||
const height = size === 'sm' ? 'h-1.5' : 'h-2';
|
||||
return (
|
||||
<div className={`w-full rounded-full bg-muted ${height}`}>
|
||||
<div
|
||||
className={`rounded-full bg-primary transition-all ${height}`}
|
||||
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Project card component (Grid view)
|
||||
function ProjectCardGrid({
|
||||
project,
|
||||
}: {
|
||||
project: (typeof mockProjects)[0];
|
||||
}) {
|
||||
return (
|
||||
<Link href={`/prototypes/project-dashboard`}>
|
||||
<Card className="h-full cursor-pointer transition-all hover:border-primary hover:shadow-lg">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
{project.status === 'archived' ? (
|
||||
<Archive className="h-5 w-5 text-muted-foreground" />
|
||||
) : project.status === 'completed' ? (
|
||||
<FolderOpen className="h-5 w-5 text-primary" />
|
||||
) : (
|
||||
<Folder className="h-5 w-5 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<ProjectStatusBadge status={project.status} />
|
||||
<ComplexityIndicator complexity={project.complexity} />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="mt-3 line-clamp-1">{project.name}</CardTitle>
|
||||
<CardDescription className="line-clamp-2">{project.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{/* Quick stats */}
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
<div className="rounded-md bg-muted/50 p-2">
|
||||
<div className="flex items-center justify-center gap-1 text-lg font-semibold">
|
||||
<CircleDot className="h-4 w-4 text-blue-500" />
|
||||
{project.openIssues}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">Issues</div>
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/50 p-2">
|
||||
<div className="flex items-center justify-center gap-1 text-lg font-semibold">
|
||||
<Bot className="h-4 w-4 text-green-500" />
|
||||
{project.activeAgents}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">Agents</div>
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/50 p-2">
|
||||
<div className="text-xs font-medium text-primary">{project.currentSprint}</div>
|
||||
<div className="text-xs text-muted-foreground">Current</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Progress</span>
|
||||
<span className="font-medium">{project.progress}%</span>
|
||||
</div>
|
||||
<ProgressBar value={project.progress} />
|
||||
</div>
|
||||
|
||||
{/* Last activity */}
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
Last activity
|
||||
</div>
|
||||
<span>{project.lastActivity}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Project row component (List view)
|
||||
function ProjectRowList({
|
||||
project,
|
||||
}: {
|
||||
project: (typeof mockProjects)[0];
|
||||
}) {
|
||||
return (
|
||||
<Link href={`/prototypes/project-dashboard`}>
|
||||
<div className="flex items-center gap-4 rounded-lg border p-4 transition-all hover:border-primary hover:bg-muted/50">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
|
||||
{project.status === 'archived' ? (
|
||||
<Archive className="h-5 w-5 text-muted-foreground" />
|
||||
) : project.status === 'completed' ? (
|
||||
<FolderOpen className="h-5 w-5 text-primary" />
|
||||
) : (
|
||||
<Folder className="h-5 w-5 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium">{project.name}</h3>
|
||||
<ProjectStatusBadge status={project.status} />
|
||||
</div>
|
||||
<p className="mt-0.5 line-clamp-1 text-sm text-muted-foreground">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="hidden items-center gap-6 md:flex">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<CircleDot className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium">{project.openIssues}</span>
|
||||
<span className="text-muted-foreground">issues</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<Bot className="h-4 w-4 text-green-500" />
|
||||
<span className="font-medium">{project.activeAgents}</span>
|
||||
<span className="text-muted-foreground">agents</span>
|
||||
</div>
|
||||
<div className="w-24">
|
||||
<div className="text-right text-xs text-muted-foreground">{project.progress}%</div>
|
||||
<ProgressBar value={project.progress} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden flex-col items-end gap-1 lg:flex">
|
||||
<ComplexityIndicator complexity={project.complexity} />
|
||||
<span className="text-xs text-muted-foreground">{project.lastActivity}</span>
|
||||
</div>
|
||||
|
||||
<ChevronRight className="h-5 w-5 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty state component
|
||||
function EmptyState() {
|
||||
return (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
||||
<Folder className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-semibold">No projects yet</h3>
|
||||
<p className="mt-2 max-w-sm text-center text-sm text-muted-foreground">
|
||||
Get started by creating your first project. Syndarix will help you assemble a team of AI
|
||||
agents to build your software.
|
||||
</p>
|
||||
<Button className="mt-6">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create Your First Project
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Activity event component
|
||||
function ActivityEvent({
|
||||
event,
|
||||
}: {
|
||||
event: (typeof mockActivityEvents)[0];
|
||||
}) {
|
||||
const Icon = event.icon;
|
||||
|
||||
return (
|
||||
<div className="flex gap-3 py-2">
|
||||
<div
|
||||
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${
|
||||
event.requiresAction
|
||||
? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900 dark:text-yellow-400'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm">
|
||||
<span className="font-medium">{event.agent}</span>{' '}
|
||||
<span className="text-muted-foreground">{event.message}</span>
|
||||
</p>
|
||||
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>{event.project}</span>
|
||||
<span>-</span>
|
||||
<span>{formatRelativeTime(event.timestamp)}</span>
|
||||
</div>
|
||||
{event.requiresAction && (
|
||||
<Button variant="outline" size="sm" className="mt-2 h-7 text-xs">
|
||||
Review Request
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MainDashboardPrototype() {
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||||
const [showEmptyState, setShowEmptyState] = useState(false);
|
||||
const [statusFilter, setStatusFilter] = useState<string>('all');
|
||||
const [sortBy, setSortBy] = useState<string>('recent');
|
||||
const [activityEvents, setActivityEvents] = useState(mockActivityEvents);
|
||||
const [isConnected, setIsConnected] = useState(true);
|
||||
|
||||
// Simulate real-time activity feed updates
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
// Randomly add a new event every 30 seconds
|
||||
const eventTypes = ['agent_message', 'issue_update', 'agent_status'];
|
||||
const agents = ['Product Owner', 'Backend Engineer', 'Frontend Engineer', 'QA Engineer'];
|
||||
const messages = [
|
||||
'Updated documentation for module #12',
|
||||
'Completed code review for PR #89',
|
||||
'Started implementing feature #34',
|
||||
'Resolved merge conflict in main branch',
|
||||
'Added unit tests for auth module',
|
||||
];
|
||||
const projects = mockProjects.filter((p) => p.status === 'active');
|
||||
|
||||
if (projects.length > 0 && Math.random() > 0.7) {
|
||||
const newEvent = {
|
||||
id: `evt-${Date.now()}`,
|
||||
type: eventTypes[Math.floor(Math.random() * eventTypes.length)],
|
||||
project: projects[Math.floor(Math.random() * projects.length)].name,
|
||||
projectId: projects[Math.floor(Math.random() * projects.length)].id,
|
||||
agent: agents[Math.floor(Math.random() * agents.length)],
|
||||
message: messages[Math.floor(Math.random() * messages.length)],
|
||||
timestamp: new Date(),
|
||||
icon: MessageSquare,
|
||||
requiresAction: false,
|
||||
};
|
||||
|
||||
setActivityEvents((prev) => [newEvent, ...prev.slice(0, 9)]);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Simulate connection status
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
// Randomly disconnect for demo purposes (rare)
|
||||
if (Math.random() > 0.95) {
|
||||
setIsConnected(false);
|
||||
setTimeout(() => setIsConnected(true), 3000);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Filter and sort projects
|
||||
const filteredProjects = mockProjects
|
||||
.filter((project) => {
|
||||
if (statusFilter === 'all') return true;
|
||||
return project.status === statusFilter;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name);
|
||||
case 'progress':
|
||||
return b.progress - a.progress;
|
||||
case 'issues':
|
||||
return b.openIssues - a.openIssues;
|
||||
default: // recent
|
||||
return 0; // Keep original order (already sorted by recent)
|
||||
}
|
||||
});
|
||||
|
||||
const activeProjectsCount = mockProjects.filter((p) => p.status === 'active').length;
|
||||
const totalAgentsActive = mockProjects.reduce((sum, p) => sum + p.activeAgents, 0);
|
||||
const pendingApprovals = activityEvents.filter((e) => e.requiresAction).length;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Navigation Header */}
|
||||
<nav className="sticky top-0 z-50 border-b bg-card">
|
||||
<div className="container mx-auto flex h-14 items-center justify-between px-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/prototypes" className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary">
|
||||
<Zap className="h-5 w-5 text-primary-foreground" />
|
||||
</div>
|
||||
<span className="font-semibold text-primary">Syndarix</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Global Search */}
|
||||
<div className="hidden max-w-md flex-1 px-8 md:block">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search projects, issues, agents..."
|
||||
className="w-full pl-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Notifications */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="relative">
|
||||
<Bell className="h-5 w-5" />
|
||||
{pendingApprovals > 0 && (
|
||||
<span className="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground">
|
||||
{pendingApprovals}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-80">
|
||||
<DropdownMenuLabel>Notifications</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{activityEvents
|
||||
.filter((e) => e.requiresAction)
|
||||
.slice(0, 3)
|
||||
.map((event) => (
|
||||
<DropdownMenuItem key={event.id} className="flex-col items-start p-3">
|
||||
<div className="font-medium">{event.agent}</div>
|
||||
<div className="text-sm text-muted-foreground">{event.message}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{formatRelativeTime(event.timestamp)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
{pendingApprovals === 0 && (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
No pending notifications
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<Separator orientation="vertical" className="mx-2 h-6" />
|
||||
|
||||
{/* User Menu */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="gap-2">
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarFallback>JD</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="hidden md:inline">John Doe</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-destructive">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
<div className="grid gap-6 lg:grid-cols-4">
|
||||
{/* Main Content */}
|
||||
<div className="space-y-6 lg:col-span-3">
|
||||
{/* Welcome Header */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Welcome back, John</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{activeProjectsCount} active project{activeProjectsCount !== 1 ? 's' : ''},{' '}
|
||||
{totalAgentsActive} agent{totalAgentsActive !== 1 ? 's' : ''} working
|
||||
</p>
|
||||
</div>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
New Project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900">
|
||||
<PlayCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{activeProjectsCount}</div>
|
||||
<div className="text-xs text-muted-foreground">Active Projects</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900">
|
||||
<Bot className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{totalAgentsActive}</div>
|
||||
<div className="text-xs text-muted-foreground">Agents Active</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900">
|
||||
<CircleDot className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold">
|
||||
{mockProjects.reduce((sum, p) => sum + p.openIssues, 0)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">Open Issues</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="flex items-center gap-3 p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-yellow-100 dark:bg-yellow-900">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{pendingApprovals}</div>
|
||||
<div className="text-xs text-muted-foreground">Pending Approvals</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Projects Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<CardTitle>Projects</CardTitle>
|
||||
<CardDescription>
|
||||
Manage your software projects and track AI agent progress
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* Demo toggle for empty state */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowEmptyState(!showEmptyState)}
|
||||
className="text-xs"
|
||||
>
|
||||
{showEmptyState ? 'Show Projects' : 'Show Empty State'}
|
||||
</Button>
|
||||
|
||||
<Separator orientation="vertical" className="hidden h-6 sm:block" />
|
||||
|
||||
{/* Filter */}
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-32">
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Status</SelectItem>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="paused">Paused</SelectItem>
|
||||
<SelectItem value="completed">Completed</SelectItem>
|
||||
<SelectItem value="archived">Archived</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Sort */}
|
||||
<Select value={sortBy} onValueChange={setSortBy}>
|
||||
<SelectTrigger className="w-32">
|
||||
<ArrowUpDown className="mr-2 h-4 w-4" />
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="recent">Recent</SelectItem>
|
||||
<SelectItem value="name">Name</SelectItem>
|
||||
<SelectItem value="progress">Progress</SelectItem>
|
||||
<SelectItem value="issues">Issues</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* View Toggle */}
|
||||
<div className="flex rounded-md border">
|
||||
<Button
|
||||
variant={viewMode === 'grid' ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
className="rounded-r-none"
|
||||
onClick={() => setViewMode('grid')}
|
||||
aria-label="Grid view"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
className="rounded-l-none"
|
||||
onClick={() => setViewMode('list')}
|
||||
aria-label="List view"
|
||||
>
|
||||
<List className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{showEmptyState ? (
|
||||
<EmptyState />
|
||||
) : viewMode === 'grid' ? (
|
||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{filteredProjects.map((project) => (
|
||||
<ProjectCardGrid key={project.id} project={project} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{filteredProjects.map((project) => (
|
||||
<ProjectRowList key={project.id} project={project} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredProjects.length === 0 && !showEmptyState && (
|
||||
<div className="py-8 text-center text-muted-foreground">
|
||||
No projects match the selected filters
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar - Activity Feed */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Activity className="h-5 w-5" />
|
||||
Activity Feed
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
{isConnected ? (
|
||||
<Badge variant="outline" className="gap-1 text-xs">
|
||||
<span className="h-2 w-2 animate-pulse rounded-full bg-green-500" />
|
||||
Live
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="gap-1 text-xs text-muted-foreground">
|
||||
<span className="h-2 w-2 rounded-full bg-gray-400" />
|
||||
Reconnecting...
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription>Real-time updates from all projects</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="divide-y">
|
||||
{activityEvents.slice(0, 8).map((event) => (
|
||||
<ActivityEvent key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
<Button variant="ghost" className="mt-4 w-full text-sm">
|
||||
View All Activity
|
||||
<ChevronRight className="ml-1 h-4 w-4" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Quick Performance Stats */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Gauge className="h-5 w-5" />
|
||||
Performance
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Avg. Sprint Velocity</span>
|
||||
<span className="font-medium">24 pts</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Issues Resolved (7d)</span>
|
||||
<span className="font-medium">42</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Agent Uptime</span>
|
||||
<span className="font-medium text-green-600">99.8%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Approval Response</span>
|
||||
<span className="font-medium">2.3 hrs avg</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,9 +15,26 @@ import {
|
||||
CircleDot,
|
||||
Activity,
|
||||
ChevronRight,
|
||||
Home,
|
||||
} from 'lucide-react';
|
||||
|
||||
const prototypes = [
|
||||
{
|
||||
id: 'main-dashboard',
|
||||
title: 'Main Dashboard',
|
||||
description: 'Landing page with projects list, activity feed, and quick stats overview',
|
||||
icon: Home,
|
||||
issue: '#47',
|
||||
status: 'ready',
|
||||
features: [
|
||||
'Projects grid/list view with status badges',
|
||||
'Real-time activity feed',
|
||||
'Quick stats overview',
|
||||
'Filter and sort controls',
|
||||
'Empty state for new users',
|
||||
'Responsive layout',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'project-dashboard',
|
||||
title: 'Project Dashboard',
|
||||
|
||||
Reference in New Issue
Block a user