feat(frontend): wire useProjects hook to SDK and enhance MSW handlers
- Regenerate API SDK with 77 endpoints (up from 61) - Update useProjects hook to use SDK's listProjects function - Add comprehensive project mock data for demo mode - Add project CRUD handlers to MSW overrides - Map API response to frontend ProjectListItem format - Fix test files with required slug and autonomyLevel properties 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,14 @@
|
||||
* Provides data for the projects list page with filtering,
|
||||
* sorting, and pagination.
|
||||
*
|
||||
* Uses mock data until backend endpoints are available.
|
||||
* Uses SDK to call API (intercepted by MSW in demo mode).
|
||||
*
|
||||
* @see Issue #54
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { listProjects as listProjectsApi } from '@/lib/api/generated';
|
||||
import type { ProjectStatus as ApiProjectStatus, ProjectResponse } from '@/lib/api/generated';
|
||||
import type { ProjectStatus } from '@/components/projects/types';
|
||||
|
||||
// ============================================================================
|
||||
@@ -19,6 +21,7 @@ import type { ProjectStatus } from '@/components/projects/types';
|
||||
export interface ProjectListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
status: ProjectStatus;
|
||||
complexity: 'low' | 'medium' | 'high';
|
||||
@@ -33,6 +36,7 @@ export interface ProjectListItem {
|
||||
name: string;
|
||||
};
|
||||
tags?: string[];
|
||||
autonomyLevel: string;
|
||||
}
|
||||
|
||||
export interface ProjectsListParams {
|
||||
@@ -56,134 +60,64 @@ export interface ProjectsListResponse {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
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'],
|
||||
},
|
||||
];
|
||||
/**
|
||||
* Maps API ProjectResponse to our ProjectListItem format
|
||||
*/
|
||||
function mapProjectResponse(project: ProjectResponse & Record<string, unknown>): ProjectListItem {
|
||||
// Map complexity from mock data format to UI format
|
||||
const rawComplexity = project.complexity as string;
|
||||
let complexity: 'low' | 'medium' | 'high' = 'medium';
|
||||
if (rawComplexity === 'script' || rawComplexity === 'simple' || rawComplexity === 'low') {
|
||||
complexity = 'low';
|
||||
} else if (rawComplexity === 'complex' || rawComplexity === 'high') {
|
||||
complexity = 'high';
|
||||
}
|
||||
|
||||
return {
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
slug: project.slug || project.name.toLowerCase().replace(/\s+/g, '-'),
|
||||
description: project.description || undefined,
|
||||
status: project.status as ProjectStatus,
|
||||
complexity,
|
||||
progress: (project.progress as number) || 0,
|
||||
openIssues: (project.openIssues as number) || project.issue_count || 0,
|
||||
activeAgents: (project.activeAgents as number) || project.agent_count || 0,
|
||||
currentSprint: project.active_sprint_name || undefined,
|
||||
lastActivity: (project.lastActivity as string) || formatRelativeTime(project.updated_at),
|
||||
createdAt: project.created_at,
|
||||
owner: {
|
||||
id: project.owner_id || 'unknown',
|
||||
name: (project.ownerName as string) || 'Unknown',
|
||||
},
|
||||
tags: (project.tags as string[]) || [],
|
||||
autonomyLevel: project.autonomy_level || 'milestone',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date string as relative time (e.g., "2 minutes ago")
|
||||
*/
|
||||
function formatRelativeTime(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
const diffWeeks = Math.floor(diffDays / 7);
|
||||
const diffMonths = Math.floor(diffDays / 30);
|
||||
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
||||
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
||||
if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks > 1 ? 's' : ''} ago`;
|
||||
return `${diffMonths} month${diffMonths > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hook
|
||||
@@ -206,35 +140,33 @@ export function useProjects(params: ProjectsListParams = {}) {
|
||||
return useQuery<ProjectsListResponse>({
|
||||
queryKey: ['projects', { search, status, complexity, sortBy, sortOrder, page, limit }],
|
||||
queryFn: async () => {
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
// Call API via SDK (MSW intercepts in demo mode)
|
||||
const response = await listProjectsApi({
|
||||
query: {
|
||||
status: status !== 'all' ? (status as ApiProjectStatus) : undefined,
|
||||
search: search || undefined,
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
|
||||
// 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))
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error('Failed to fetch projects');
|
||||
}
|
||||
|
||||
// Status filter
|
||||
if (status !== 'all') {
|
||||
filtered = filtered.filter((p) => p.status === status);
|
||||
}
|
||||
// Get raw response data
|
||||
const apiData = response.data;
|
||||
let projects = apiData.data.map((p) =>
|
||||
mapProjectResponse(p as ProjectResponse & Record<string, unknown>)
|
||||
);
|
||||
|
||||
// Complexity filter
|
||||
// Client-side complexity filter (not supported by API)
|
||||
if (complexity !== 'all') {
|
||||
filtered = filtered.filter((p) => p.complexity === complexity);
|
||||
projects = projects.filter((p) => p.complexity === complexity);
|
||||
}
|
||||
|
||||
// Sort
|
||||
filtered.sort((a, b) => {
|
||||
// Client-side sorting
|
||||
projects.sort((a, b) => {
|
||||
let comparison = 0;
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
@@ -254,15 +186,12 @@ export function useProjects(params: ProjectsListParams = {}) {
|
||||
return sortOrder === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
|
||||
// Pagination
|
||||
const total = filtered.length;
|
||||
// Calculate pagination
|
||||
const total = apiData.pagination.total;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
const paginatedData = filtered.slice(start, end);
|
||||
|
||||
return {
|
||||
data: paginatedData,
|
||||
data: projects,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
|
||||
Reference in New Issue
Block a user