Add comprehensive Issue management functionality with drag-and-drop Kanban board. Changes: - Created Issue API client (issues.ts) with CRUD operations - Implemented React Query hooks for Issue data management - Added IssueCard component with drag-and-drop support using @dnd-kit - Created KanbanColumn component with droppable zones - Built CreateIssueDialog with form validation using zod - Implemented Kanban page at /projects/[id]/kanban with DnD status changes - Added missing UI components (textarea, select, skeleton) - Enhanced API client with helper methods (get, post, put, patch, delete) - Installed dependencies: @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, @radix-ui/react-select, sonner - Fixed SignalR ConnectionManager TypeScript error - Preserved legacy KanbanBoard component for backward compatibility Features: - Drag and drop issues between Backlog, Todo, InProgress, and Done columns - Real-time status updates via API - Issue creation with type (Story, Task, Bug, Epic) and priority - Visual feedback with priority colors and type icons - Toast notifications for user actions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
import { useSortable } from '@dnd-kit/sortable';
|
|
import { CSS } from '@dnd-kit/utilities';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Issue } from '@/lib/api/issues';
|
|
|
|
interface IssueCardProps {
|
|
issue: Issue;
|
|
}
|
|
|
|
export function IssueCard({ issue }: IssueCardProps) {
|
|
const { attributes, listeners, setNodeRef, transform, transition } =
|
|
useSortable({ id: issue.id });
|
|
|
|
const style = {
|
|
transform: CSS.Transform.toString(transform),
|
|
transition,
|
|
};
|
|
|
|
const priorityColors = {
|
|
Low: 'bg-gray-100 text-gray-700',
|
|
Medium: 'bg-blue-100 text-blue-700',
|
|
High: 'bg-orange-100 text-orange-700',
|
|
Critical: 'bg-red-100 text-red-700',
|
|
};
|
|
|
|
const typeIcons = {
|
|
Story: '📖',
|
|
Task: '✓',
|
|
Bug: '🐛',
|
|
Epic: '🚀',
|
|
};
|
|
|
|
return (
|
|
<Card
|
|
ref={setNodeRef}
|
|
style={style}
|
|
{...attributes}
|
|
{...listeners}
|
|
className="cursor-grab active:cursor-grabbing hover:shadow-md transition-shadow"
|
|
>
|
|
<CardContent className="p-3 space-y-2">
|
|
<div className="flex items-start gap-2">
|
|
<span>{typeIcons[issue.type]}</span>
|
|
<h3 className="text-sm font-medium flex-1">{issue.title}</h3>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline" className={priorityColors[issue.priority]}>
|
|
{issue.priority}
|
|
</Badge>
|
|
<Badge variant="secondary">{issue.type}</Badge>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|