import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { epicsApi } from '@/lib/api/pm'; import type { Epic, CreateEpicDto, UpdateEpicDto, WorkItemStatus } from '@/types/project'; import { toast } from 'sonner'; import { logger } from '@/lib/utils/logger'; import { ApiError, getErrorMessage } from '@/lib/types/errors'; // ==================== Query Hooks ==================== export function useEpics(projectId?: string) { return useQuery({ queryKey: ['epics', projectId], queryFn: async () => { logger.debug('[useEpics] Fetching epics', { projectId }); try { const result = await epicsApi.list(projectId); logger.debug('[useEpics] Fetch successful', result); return result; } catch (error) { logger.error('[useEpics] Fetch failed', error); throw error; } }, staleTime: 5 * 60 * 1000, // 5 minutes retry: 1, }); } export function useEpic(id: string) { return useQuery({ queryKey: ['epics', id], queryFn: () => epicsApi.get(id), enabled: !!id, }); } // ==================== Mutation Hooks ==================== export function useCreateEpic() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateEpicDto) => epicsApi.create(data), onSuccess: (newEpic) => { // Invalidate all epic queries (including filtered by projectId) queryClient.invalidateQueries({ queryKey: ['epics'] }); // Also invalidate project details if exists queryClient.invalidateQueries({ queryKey: ['projects', newEpic.projectId] }); toast.success('Epic created successfully!'); }, onError: (error: ApiError) => { logger.error('[useCreateEpic] Error', error); toast.error(getErrorMessage(error)); }, }); } export function useUpdateEpic() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateEpicDto }) => epicsApi.update(id, data), onMutate: async ({ id, data }) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['epics', id] }); // Snapshot previous value const previousEpic = queryClient.getQueryData(['epics', id]); // Optimistically update queryClient.setQueryData(['epics', id], (old) => ({ ...old!, ...data, })); return { previousEpic }; }, onError: (error: ApiError, variables, context) => { logger.error('[useUpdateEpic] Error', error); // Rollback if (context?.previousEpic) { queryClient.setQueryData(['epics', variables.id], context.previousEpic); } toast.error(getErrorMessage(error)); }, onSuccess: (updatedEpic) => { toast.success('Epic updated successfully!'); }, onSettled: (_, __, variables) => { queryClient.invalidateQueries({ queryKey: ['epics', variables.id] }); queryClient.invalidateQueries({ queryKey: ['epics'] }); }, }); } export function useDeleteEpic() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => epicsApi.delete(id), onSuccess: (_, id) => { queryClient.invalidateQueries({ queryKey: ['epics'] }); queryClient.removeQueries({ queryKey: ['epics', id] }); toast.success('Epic deleted successfully!'); }, onError: (error: ApiError) => { logger.error('[useDeleteEpic] Error', error); toast.error(getErrorMessage(error)); }, }); } export function useChangeEpicStatus() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, status }: { id: string; status: WorkItemStatus }) => epicsApi.changeStatus(id, status), onMutate: async ({ id, status }) => { await queryClient.cancelQueries({ queryKey: ['epics', id] }); const previousEpic = queryClient.getQueryData(['epics', id]); queryClient.setQueryData(['epics', id], (old) => ({ ...old!, status, })); return { previousEpic }; }, onError: (error: ApiError, variables, context) => { logger.error('[useChangeEpicStatus] Error', error); if (context?.previousEpic) { queryClient.setQueryData(['epics', variables.id], context.previousEpic); } toast.error(getErrorMessage(error)); }, onSuccess: () => { toast.success('Epic status changed successfully!'); }, onSettled: (_, __, variables) => { queryClient.invalidateQueries({ queryKey: ['epics', variables.id] }); queryClient.invalidateQueries({ queryKey: ['epics'] }); }, }); } export function useAssignEpic() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, assigneeId }: { id: string; assigneeId: string }) => epicsApi.assign(id, assigneeId), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['epics', variables.id] }); queryClient.invalidateQueries({ queryKey: ['epics'] }); toast.success('Epic assigned successfully!'); }, onError: (error: ApiError) => { logger.error('[useAssignEpic] Error', error); toast.error(getErrorMessage(error)); }, }); }