feat(frontend): add Projects list page and components for #54

Implement the projects CRUD page with:
- ProjectCard: Card component with status badge, progress, metrics, actions
- ProjectFilters: Search, status filter, complexity, sort controls
- ProjectsGrid: Grid/list view toggle with loading and empty states
- useProjects hook: Mock data with filtering, sorting, pagination

Features include:
- Debounced search (300ms)
- Quick filters (status) and extended filters (complexity, sort)
- Grid and list view toggle
- Click navigation to project detail

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 17:20:17 +01:00
parent 6f5dd58b54
commit 50b865b23b
5 changed files with 1006 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
/**
* Projects List Hook
*
* Provides data for the projects list page with filtering,
* sorting, and pagination.
*
* Uses mock data until backend endpoints are available.
*
* @see Issue #54
*/
import { useQuery } from '@tanstack/react-query';
import type { ProjectStatus } from '@/components/projects/types';
// ============================================================================
// Types
// ============================================================================
export interface ProjectListItem {
id: string;
name: string;
description?: string;
status: ProjectStatus;
complexity: 'low' | 'medium' | 'high';
progress: number;
openIssues: number;
activeAgents: number;
currentSprint?: string;
lastActivity: string;
createdAt: string;
owner: {
id: string;
name: string;
};
tags?: string[];
}
export interface ProjectsListParams {
search?: string;
status?: ProjectStatus | 'all';
complexity?: 'low' | 'medium' | 'high' | 'all';
sortBy?: 'recent' | 'name' | 'progress' | 'issues';
sortOrder?: 'asc' | 'desc';
page?: number;
limit?: number;
}
export interface ProjectsListResponse {
data: ProjectListItem[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
// ============================================================================
// Mock Data
// ============================================================================
const mockProjects: ProjectListItem[] = [
{
id: 'proj-001',
name: 'E-Commerce Platform Redesign',
description: 'Complete redesign of the e-commerce platform with modern UI/UX and improved checkout flow',
status: 'active',
complexity: 'high',
progress: 67,
openIssues: 12,
activeAgents: 4,
currentSprint: 'Sprint 3',
lastActivity: '2 minutes ago',
createdAt: '2025-11-15T10:00:00Z',
owner: { id: 'user-001', name: 'Felipe Cardoso' },
tags: ['e-commerce', 'frontend', 'ux'],
},
{
id: 'proj-002',
name: 'Mobile Banking App',
description: 'Native mobile app for banking services with biometric authentication and real-time notifications',
status: 'active',
complexity: 'high',
progress: 45,
openIssues: 8,
activeAgents: 5,
currentSprint: 'Sprint 2',
lastActivity: '15 minutes ago',
createdAt: '2025-11-20T09:00:00Z',
owner: { id: 'user-001', name: 'Felipe Cardoso' },
tags: ['mobile', 'fintech', 'security'],
},
{
id: 'proj-003',
name: 'Internal HR Portal',
description: 'Employee self-service portal for HR operations including leave requests and performance reviews',
status: 'paused',
complexity: 'medium',
progress: 23,
openIssues: 5,
activeAgents: 0,
currentSprint: 'Sprint 1',
lastActivity: '2 days ago',
createdAt: '2025-10-01T08:00:00Z',
owner: { id: 'user-002', name: 'Maria Santos' },
tags: ['internal', 'hr', 'portal'],
},
{
id: 'proj-004',
name: 'API Gateway Modernization',
description: 'Migrate legacy API gateway to cloud-native architecture with improved rate limiting and caching',
status: 'active',
complexity: 'high',
progress: 82,
openIssues: 3,
activeAgents: 2,
currentSprint: 'Sprint 4',
lastActivity: '1 hour ago',
createdAt: '2025-12-01T11:00:00Z',
owner: { id: 'user-001', name: 'Felipe Cardoso' },
tags: ['api', 'cloud', 'infrastructure'],
},
{
id: 'proj-005',
name: 'Customer Analytics Dashboard',
description: 'Real-time analytics dashboard for customer behavior insights with ML-powered predictions',
status: 'completed',
complexity: 'medium',
progress: 100,
openIssues: 0,
activeAgents: 0,
lastActivity: '2 weeks ago',
createdAt: '2025-09-01T10:00:00Z',
owner: { id: 'user-003', name: 'Alex Johnson' },
tags: ['analytics', 'ml', 'dashboard'],
},
{
id: 'proj-006',
name: 'DevOps Pipeline Automation',
description: 'Automate CI/CD pipelines with AI-assisted deployments and rollback capabilities',
status: 'active',
complexity: 'medium',
progress: 35,
openIssues: 6,
activeAgents: 3,
currentSprint: 'Sprint 1',
lastActivity: '30 minutes ago',
createdAt: '2025-12-10T14:00:00Z',
owner: { id: 'user-001', name: 'Felipe Cardoso' },
tags: ['devops', 'automation', 'ci-cd'],
},
{
id: 'proj-007',
name: 'Inventory Management System',
description: 'Warehouse inventory tracking with barcode scanning and automated reordering',
status: 'archived',
complexity: 'low',
progress: 100,
openIssues: 0,
activeAgents: 0,
lastActivity: '1 month ago',
createdAt: '2025-06-15T08:00:00Z',
owner: { id: 'user-002', name: 'Maria Santos' },
tags: ['inventory', 'warehouse', 'logistics'],
},
{
id: 'proj-008',
name: 'Customer Support Chatbot',
description: 'AI-powered chatbot for 24/7 customer support with sentiment analysis',
status: 'active',
complexity: 'medium',
progress: 58,
openIssues: 4,
activeAgents: 2,
currentSprint: 'Sprint 2',
lastActivity: '45 minutes ago',
createdAt: '2025-12-05T09:00:00Z',
owner: { id: 'user-003', name: 'Alex Johnson' },
tags: ['ai', 'chatbot', 'support'],
},
];
// ============================================================================
// Hook
// ============================================================================
/**
* Fetches projects list with filtering, sorting, and pagination
*/
export function useProjects(params: ProjectsListParams = {}) {
const {
search = '',
status = 'all',
complexity = 'all',
sortBy = 'recent',
sortOrder = 'desc',
page = 1,
limit = 50,
} = params;
return useQuery<ProjectsListResponse>({
queryKey: ['projects', { search, status, complexity, sortBy, sortOrder, page, limit }],
queryFn: async () => {
// Simulate network delay
await new Promise((resolve) => setTimeout(resolve, 400));
// Filter projects
let filtered = [...mockProjects];
// Search filter
if (search) {
const searchLower = search.toLowerCase();
filtered = filtered.filter(
(p) =>
p.name.toLowerCase().includes(searchLower) ||
p.description?.toLowerCase().includes(searchLower) ||
p.tags?.some((t) => t.toLowerCase().includes(searchLower))
);
}
// Status filter
if (status !== 'all') {
filtered = filtered.filter((p) => p.status === status);
}
// Complexity filter
if (complexity !== 'all') {
filtered = filtered.filter((p) => p.complexity === complexity);
}
// Sort
filtered.sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'name':
comparison = a.name.localeCompare(b.name);
break;
case 'progress':
comparison = a.progress - b.progress;
break;
case 'issues':
comparison = a.openIssues - b.openIssues;
break;
case 'recent':
default:
comparison = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
break;
}
return sortOrder === 'asc' ? comparison : -comparison;
});
// Pagination
const total = filtered.length;
const totalPages = Math.ceil(total / limit);
const start = (page - 1) * limit;
const end = start + limit;
const paginatedData = filtered.slice(start, end);
return {
data: paginatedData,
pagination: {
page,
limit,
total,
totalPages,
},
};
},
staleTime: 30000,
});
}