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,
|
CircleDot,
|
||||||
Activity,
|
Activity,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Home,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const prototypes = [
|
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',
|
id: 'project-dashboard',
|
||||||
title: 'Project Dashboard',
|
title: 'Project Dashboard',
|
||||||
|
|||||||
Reference in New Issue
Block a user