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>
204 lines
6.9 KiB
TypeScript
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>
|
|
);
|
|
}
|