Files
traceability/src/hooks/usePlanning.ts
2026-02-03 20:48:09 +01:00

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,
});
};