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:
271
frontend/src/lib/api/hooks/useProjects.ts
Normal file
271
frontend/src/lib/api/hooks/useProjects.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user