Files
ColaFlow-Web/app/(dashboard)/api-test/page.tsx
Yaojia Wang e52c8300de feat(frontend): Implement Phase 1 - ProjectManagement API Client & Hooks
Add complete API integration for ProjectManagement module:
- Epics, Stories, Tasks API clients
- React Query hooks for all entities
- Updated type definitions to match backend API
- API test page for connection verification

Changes:
- Update lib/api/config.ts: Add all ProjectManagement endpoints
- Update types/project.ts: Match backend API models (Epic, Story, Task)
- Create lib/api/pm.ts: API clients for Epics, Stories, Tasks
- Create lib/hooks/use-epics.ts: React Query hooks for Epic CRUD
- Create lib/hooks/use-stories.ts: React Query hooks for Story CRUD
- Create lib/hooks/use-tasks.ts: React Query hooks for Task CRUD
- Create app/(dashboard)/api-test/page.tsx: API connection test page

Features:
- Full CRUD operations for Epics, Stories, Tasks
- Status change and assignment operations
- Optimistic updates for better UX
- Error handling with toast notifications
- Query invalidation for cache consistency

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 20:58:59 +01:00

204 lines
6.9 KiB
TypeScript

'use client';
import { useProjects } from '@/lib/hooks/use-projects';
import { useEpics } from '@/lib/hooks/use-epics';
import { useStories } from '@/lib/hooks/use-stories';
import { useTasks } from '@/lib/hooks/use-tasks';
import { Card } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { Badge } from '@/components/ui/badge';
export default function ApiTestPage() {
const { data: projects, isLoading: projectsLoading, error: projectsError } = useProjects();
const { data: epics, isLoading: epicsLoading, error: epicsError } = useEpics();
const { data: stories, isLoading: storiesLoading, error: storiesError } = useStories();
const { data: tasks, isLoading: tasksLoading, error: tasksError } = useTasks();
return (
<div className="container py-6 space-y-8">
<div>
<h1 className="text-3xl font-bold mb-2">API Connection Test</h1>
<p className="text-muted-foreground">
This page tests the connection to ProjectManagement API endpoints
</p>
</div>
{/* Projects Section */}
<Section
title="Projects"
count={projects?.length}
loading={projectsLoading}
error={projectsError}
>
{projects && projects.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{projects.map((project) => (
<Card key={project.id} className="p-4">
<h3 className="font-semibold">{project.name}</h3>
<p className="text-sm text-muted-foreground">{project.key}</p>
{project.description && (
<p className="text-sm text-gray-600 mt-2">{project.description}</p>
)}
<div className="mt-2 flex gap-2">
<Badge variant="outline">ID: {project.id.substring(0, 8)}...</Badge>
</div>
</Card>
))}
</div>
) : (
<EmptyState message="No projects found" />
)}
</Section>
{/* Epics Section */}
<Section
title="Epics"
count={epics?.length}
loading={epicsLoading}
error={epicsError}
>
{epics && epics.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{epics.map((epic) => (
<Card key={epic.id} className="p-4">
<h3 className="font-semibold">{epic.title}</h3>
{epic.description && (
<p className="text-sm text-gray-600 mt-1">{epic.description}</p>
)}
<div className="mt-2 flex gap-2 flex-wrap">
<Badge variant="default">{epic.status}</Badge>
<Badge variant="outline">{epic.priority}</Badge>
{epic.estimatedHours && (
<Badge variant="secondary">{epic.estimatedHours}h</Badge>
)}
</div>
</Card>
))}
</div>
) : (
<EmptyState message="No epics found" />
)}
</Section>
{/* Stories Section */}
<Section
title="Stories"
count={stories?.length}
loading={storiesLoading}
error={storiesError}
>
{stories && stories.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{stories.map((story) => (
<Card key={story.id} className="p-4">
<h3 className="font-semibold">{story.title}</h3>
{story.description && (
<p className="text-sm text-gray-600 mt-1">{story.description}</p>
)}
<div className="mt-2 flex gap-2 flex-wrap">
<Badge variant="default">{story.status}</Badge>
<Badge variant="outline">{story.priority}</Badge>
{story.estimatedHours && (
<Badge variant="secondary">{story.estimatedHours}h</Badge>
)}
</div>
</Card>
))}
</div>
) : (
<EmptyState message="No stories found" />
)}
</Section>
{/* Tasks Section */}
<Section
title="Tasks"
count={tasks?.length}
loading={tasksLoading}
error={tasksError}
>
{tasks && tasks.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{tasks.map((task) => (
<Card key={task.id} className="p-4">
<h3 className="font-semibold">{task.title}</h3>
{task.description && (
<p className="text-sm text-gray-600 mt-1">{task.description}</p>
)}
<div className="mt-2 flex gap-2 flex-wrap">
<Badge variant="default">{task.status}</Badge>
<Badge variant="outline">{task.priority}</Badge>
{task.estimatedHours && (
<Badge variant="secondary">{task.estimatedHours}h</Badge>
)}
</div>
</Card>
))}
</div>
) : (
<EmptyState message="No tasks found" />
)}
</Section>
</div>
);
}
// Helper Components
interface SectionProps {
title: string;
count?: number;
loading: boolean;
error: any;
children: React.ReactNode;
}
function Section({ title, count, loading, error, children }: SectionProps) {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold">
{title}
{count !== undefined && (
<span className="ml-2 text-muted-foreground text-lg">({count})</span>
)}
</h2>
{loading && <Badge variant="secondary">Loading...</Badge>}
{error && <Badge variant="destructive">Error</Badge>}
</div>
{loading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Skeleton className="h-32" />
<Skeleton className="h-32" />
<Skeleton className="h-32" />
</div>
) : error ? (
<Card className="p-6 border-destructive">
<h3 className="text-lg font-semibold text-destructive mb-2">Error Loading {title}</h3>
<p className="text-sm text-muted-foreground">
{error.message || 'Unknown error occurred'}
</p>
{error.response?.status && (
<p className="text-sm text-muted-foreground mt-2">
Status Code: {error.response.status}
</p>
)}
</Card>
) : (
children
)}
</div>
);
}
function EmptyState({ message }: { message: string }) {
return (
<Card className="p-8 text-center">
<p className="text-muted-foreground">{message}</p>
<p className="text-sm text-muted-foreground mt-2">
Try creating some data via the API or check your authentication
</p>
</Card>
);
}