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 }) => 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 }) => 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(QUERY_KEYS.tasks); // Optimistically update queryClient.setQueryData(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(QUERY_KEYS.tasks); queryClient.setQueryData(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, }); };