import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { storiesApi } from '@/lib/api/pm'; import { epicsApi } from '@/lib/api/pm'; import type { Story, CreateStoryDto, UpdateStoryDto, WorkItemStatus } from '@/types/project'; import { toast } from 'sonner'; // ==================== Query Hooks ==================== export function useStories(epicId?: string) { return useQuery({ queryKey: ['stories', epicId], queryFn: async () => { console.log('[useStories] Fetching stories...', { epicId }); try { const result = await storiesApi.list(epicId); console.log('[useStories] Fetch successful:', result); return result; } catch (error) { console.error('[useStories] Fetch failed:', error); throw error; } }, staleTime: 5 * 60 * 1000, // 5 minutes retry: 1, }); } // Fetch all stories for a project (by fetching epics first, then all stories) export function useProjectStories(projectId?: string) { return useQuery({ queryKey: ['project-stories', projectId], queryFn: async () => { if (!projectId) { throw new Error('projectId is required'); } console.log('[useProjectStories] Fetching all stories for project...', { projectId }); try { // First fetch all epics for the project const epics = await epicsApi.list(projectId); console.log('[useProjectStories] Epics fetched:', epics.length); // Then fetch stories for each epic const storiesPromises = epics.map((epic) => storiesApi.list(epic.id)); const storiesArrays = await Promise.all(storiesPromises); // Flatten the array of arrays into a single array const allStories = storiesArrays.flat(); console.log('[useProjectStories] Total stories fetched:', allStories.length); return allStories; } catch (error) { console.error('[useProjectStories] Fetch failed:', error); throw error; } }, enabled: !!projectId, staleTime: 5 * 60 * 1000, // 5 minutes retry: 1, }); } export function useStory(id: string) { return useQuery({ queryKey: ['stories', id], queryFn: () => storiesApi.get(id), enabled: !!id, }); } // ==================== Mutation Hooks ==================== export function useCreateStory() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateStoryDto) => storiesApi.create(data), onSuccess: (newStory) => { // Invalidate all story queries queryClient.invalidateQueries({ queryKey: ['stories'] }); // Also invalidate epic details queryClient.invalidateQueries({ queryKey: ['epics', newStory.epicId] }); toast.success('Story created successfully!'); }, onError: (error: any) => { console.error('[useCreateStory] Error:', error); toast.error(error.response?.data?.detail || 'Failed to create story'); }, }); } export function useUpdateStory() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateStoryDto }) => storiesApi.update(id, data), onMutate: async ({ id, data }) => { await queryClient.cancelQueries({ queryKey: ['stories', id] }); const previousStory = queryClient.getQueryData(['stories', id]); queryClient.setQueryData(['stories', id], (old) => ({ ...old!, ...data, })); return { previousStory }; }, onError: (error: any, variables, context) => { console.error('[useUpdateStory] Error:', error); if (context?.previousStory) { queryClient.setQueryData(['stories', variables.id], context.previousStory); } toast.error(error.response?.data?.detail || 'Failed to update story'); }, onSuccess: () => { toast.success('Story updated successfully!'); }, onSettled: (_, __, variables) => { queryClient.invalidateQueries({ queryKey: ['stories', variables.id] }); queryClient.invalidateQueries({ queryKey: ['stories'] }); }, }); } export function useDeleteStory() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => storiesApi.delete(id), onSuccess: (_, id) => { queryClient.invalidateQueries({ queryKey: ['stories'] }); queryClient.removeQueries({ queryKey: ['stories', id] }); toast.success('Story deleted successfully!'); }, onError: (error: any) => { console.error('[useDeleteStory] Error:', error); toast.error(error.response?.data?.detail || 'Failed to delete story'); }, }); } export function useChangeStoryStatus() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, status }: { id: string; status: WorkItemStatus }) => storiesApi.changeStatus(id, status), onMutate: async ({ id, status }) => { await queryClient.cancelQueries({ queryKey: ['stories', id] }); const previousStory = queryClient.getQueryData(['stories', id]); queryClient.setQueryData(['stories', id], (old) => ({ ...old!, status, })); return { previousStory }; }, onError: (error: any, variables, context) => { console.error('[useChangeStoryStatus] Error:', error); if (context?.previousStory) { queryClient.setQueryData(['stories', variables.id], context.previousStory); } toast.error(error.response?.data?.detail || 'Failed to change story status'); }, onSuccess: () => { toast.success('Story status changed successfully!'); }, onSettled: (_, __, variables) => { queryClient.invalidateQueries({ queryKey: ['stories', variables.id] }); queryClient.invalidateQueries({ queryKey: ['stories'] }); }, }); } export function useAssignStory() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, assigneeId }: { id: string; assigneeId: string }) => storiesApi.assign(id, assigneeId), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['stories', variables.id] }); queryClient.invalidateQueries({ queryKey: ['stories'] }); toast.success('Story assigned successfully!'); }, onError: (error: any) => { console.error('[useAssignStory] Error:', error); toast.error(error.response?.data?.detail || 'Failed to assign story'); }, }); }