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>
103 lines
3.0 KiB
TypeScript
103 lines
3.0 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { issuesApi, Issue, CreateIssueDto, UpdateIssueDto } from '@/lib/api/issues';
|
|
import { toast } from 'sonner';
|
|
|
|
export function useIssues(projectId: string, status?: string) {
|
|
return useQuery({
|
|
queryKey: ['issues', projectId, status],
|
|
queryFn: () => issuesApi.list(projectId, status),
|
|
enabled: !!projectId,
|
|
});
|
|
}
|
|
|
|
export function useIssue(projectId: string, issueId: string) {
|
|
return useQuery({
|
|
queryKey: ['issue', projectId, issueId],
|
|
queryFn: () => issuesApi.getById(projectId, issueId),
|
|
enabled: !!projectId && !!issueId,
|
|
});
|
|
}
|
|
|
|
export function useCreateIssue(projectId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (data: CreateIssueDto) => issuesApi.create(projectId, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['issues', projectId] });
|
|
toast.success('Issue created successfully');
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to create issue');
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateIssue(projectId: string, issueId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (data: UpdateIssueDto) =>
|
|
issuesApi.update(projectId, issueId, data),
|
|
onSuccess: (updatedIssue) => {
|
|
queryClient.setQueryData(['issue', projectId, issueId], updatedIssue);
|
|
queryClient.invalidateQueries({ queryKey: ['issues', projectId] });
|
|
toast.success('Issue updated successfully');
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to update issue');
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useChangeIssueStatus(projectId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ issueId, status }: { issueId: string; status: string }) =>
|
|
issuesApi.changeStatus(projectId, issueId, status),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['issues', projectId] });
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to change issue status');
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useAssignIssue(projectId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({
|
|
issueId,
|
|
assigneeId,
|
|
}: {
|
|
issueId: string;
|
|
assigneeId: string | null;
|
|
}) => issuesApi.assign(projectId, issueId, assigneeId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['issues', projectId] });
|
|
toast.success('Issue assigned successfully');
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to assign issue');
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteIssue(projectId: string) {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (issueId: string) => issuesApi.delete(projectId, issueId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['issues', projectId] });
|
|
toast.success('Issue deleted successfully');
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to delete issue');
|
|
},
|
|
});
|
|
}
|