245 lines
7.1 KiB
TypeScript
245 lines
7.1 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { planningService } from '@/services/planningService';
|
|
import { Sprint, Task, TaskStatus } from '@/types/planning';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
|
|
const QUERY_KEYS = {
|
|
sprints: ['planning', 'sprints'],
|
|
tasks: ['planning', 'tasks'],
|
|
task: (id: string) => ['planning', 'task', id],
|
|
backlog: ['planning', 'backlog'],
|
|
sprintTasks: (sprintId: string) => ['planning', 'sprint-tasks', sprintId],
|
|
allData: ['planning', 'all'],
|
|
};
|
|
|
|
// ==================== SPRINT HOOKS ====================
|
|
|
|
export const useSprints = () => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.sprints,
|
|
queryFn: planningService.getSprints,
|
|
});
|
|
};
|
|
|
|
export const useCreateSprint = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.createSprint,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.sprints });
|
|
toast({ title: 'Sprint created successfully' });
|
|
},
|
|
onError: (error: Error) => {
|
|
toast({ title: 'Failed to create sprint', description: error.message, variant: 'destructive' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useUpdateSprint = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, updates }: { id: string; updates: Partial<Sprint> }) =>
|
|
planningService.updateSprint(id, updates),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.sprints });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useDeleteSprint = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.deleteSprint,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.sprints });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
toast({ title: 'Sprint deleted' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useStartSprint = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.startSprint,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.sprints });
|
|
toast({ title: 'Sprint started!' });
|
|
},
|
|
onError: (error: Error) => {
|
|
toast({ title: 'Cannot start sprint', description: error.message, variant: 'destructive' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useCompleteSprint = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.completeSprint,
|
|
onSuccess: ({ returnedTasks }) => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.sprints });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
if (returnedTasks.length > 0) {
|
|
toast({
|
|
title: 'Sprint completed',
|
|
description: `${returnedTasks.length} unfinished task(s) moved to backlog`
|
|
});
|
|
} else {
|
|
toast({ title: 'Sprint completed successfully!' });
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
// ==================== TASK HOOKS ====================
|
|
|
|
export const useTasks = () => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.tasks,
|
|
queryFn: planningService.getTasks,
|
|
});
|
|
};
|
|
|
|
export const useTask = (id: string) => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.task(id),
|
|
queryFn: () => planningService.getTask(id),
|
|
enabled: !!id,
|
|
});
|
|
};
|
|
|
|
export const useBacklogTasks = () => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.backlog,
|
|
queryFn: planningService.getBacklogTasks,
|
|
});
|
|
};
|
|
|
|
export const useSprintTasks = (sprintId: string) => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.sprintTasks(sprintId),
|
|
queryFn: () => planningService.getSprintTasks(sprintId),
|
|
enabled: !!sprintId,
|
|
});
|
|
};
|
|
|
|
export const useCreateTask = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.createTask,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.backlog });
|
|
toast({ title: 'Task created' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useUpdateTask = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, updates }: { id: string; updates: Partial<Task> }) =>
|
|
planningService.updateTask(id, updates),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.backlog });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useDeleteTask = () => {
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation({
|
|
mutationFn: planningService.deleteTask,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.backlog });
|
|
toast({ title: 'Task deleted' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useMoveTask = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ taskId, sprintId }: { taskId: string; sprintId: string | null }) =>
|
|
planningService.moveTaskToSprint(taskId, sprintId),
|
|
onMutate: async ({ taskId, sprintId }) => {
|
|
// Cancel outgoing refetches
|
|
await queryClient.cancelQueries({ queryKey: QUERY_KEYS.tasks });
|
|
|
|
// Snapshot previous value
|
|
const previousTasks = queryClient.getQueryData<Task[]>(QUERY_KEYS.tasks);
|
|
|
|
// Optimistically update
|
|
queryClient.setQueryData<Task[]>(QUERY_KEYS.tasks, (old) =>
|
|
old?.map(task => task.id === taskId ? { ...task, sprintId } : task) ?? []
|
|
);
|
|
|
|
return { previousTasks };
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
// Rollback on error
|
|
if (context?.previousTasks) {
|
|
queryClient.setQueryData(QUERY_KEYS.tasks, context.previousTasks);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.backlog });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useUpdateTaskStatus = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ taskId, status }: { taskId: string; status: TaskStatus }) =>
|
|
planningService.updateTaskStatus(taskId, status),
|
|
onMutate: async ({ taskId, status }) => {
|
|
await queryClient.cancelQueries({ queryKey: QUERY_KEYS.tasks });
|
|
|
|
const previousTasks = queryClient.getQueryData<Task[]>(QUERY_KEYS.tasks);
|
|
|
|
queryClient.setQueryData<Task[]>(QUERY_KEYS.tasks, (old) =>
|
|
old?.map(task => task.id === taskId ? { ...task, status } : task) ?? []
|
|
);
|
|
|
|
return { previousTasks };
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
if (context?.previousTasks) {
|
|
queryClient.setQueryData(QUERY_KEYS.tasks, context.previousTasks);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.tasks });
|
|
},
|
|
});
|
|
};
|
|
|
|
// ==================== COMBINED DATA ====================
|
|
|
|
export const usePlanningData = () => {
|
|
return useQuery({
|
|
queryKey: QUERY_KEYS.allData,
|
|
queryFn: planningService.getAllData,
|
|
});
|
|
};
|