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:
2026-01-03 02:22:44 +01:00
parent fe2104822e
commit 731a188a76
8 changed files with 4881 additions and 165 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -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,