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>
This commit is contained in:
203
app/(dashboard)/api-test/page.tsx
Normal file
203
app/(dashboard)/api-test/page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user