Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | 3x 29x 18x 25x 24x 12x 3x 17x 8x 12x 5x 11x 4x 3x 1x 11x 11x 6x 4x 11x 11x 6x 4x 4x 15x 15x 8x 5x 5x 5x | /**
* Topics React Query Hooks (Story 5.2 - Task 5b)
*
* Custom hooks for topic data fetching and mutations
* Features:
* - React Query for server state caching
* - Automatic cache invalidation on updates
* - 5 minute stale time for topic list (frequent updates)
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { topicService } from '@/services/topicService';
import type {
CreateTopicRequest,
OverrideStalenesRequest,
TopicFilters,
SelectTopicForEventRequest,
} from '@/types/topic.types';
/**
* Query key factory for topics
* Centralizes query key management for cache invalidation
*/
export const topicKeys = {
all: ['topics'] as const,
lists: () => [...topicKeys.all, 'list'] as const,
list: (filters?: TopicFilters) => [...topicKeys.lists(), filters] as const,
details: () => [...topicKeys.all, 'detail'] as const,
detail: (id: string) => [...topicKeys.details(), id] as const,
similar: (id: string) => [...topicKeys.all, 'similar', id] as const,
usageHistory: (id: string) => [...topicKeys.all, 'usage-history', id] as const,
};
/**
* Hook to fetch topics with filtering and pagination (AC1)
*
* @param filters Optional filters for category, status, pagination, etc.
* @returns Query result with paginated topic list
* @example
* const { data, isLoading } = useTopics({ category: 'Cloud Native', page: 1, limit: 20 });
*/
export function useTopics(filters?: TopicFilters) {
return useQuery({
queryKey: topicKeys.list(filters),
queryFn: () => topicService.getTopics(filters),
staleTime: 1000 * 60 * 5, // 5 minutes - topics change moderately
});
}
/**
* Hook to fetch specific topic by ID
*
* @param id Topic UUID
* @param include Optional comma-separated resources to include
* @returns Query result with topic details
* @example
* const { data: topic } = useTopic('uuid', 'similarity,history');
*/
export function useTopic(id: string, include?: string) {
return useQuery({
queryKey: topicKeys.detail(id),
queryFn: () => topicService.getTopicById(id, include),
enabled: !!id,
staleTime: 1000 * 60 * 5, // 5 minutes
});
}
/**
* Hook to get similar topics for duplicate detection (AC5)
*
* @param id Topic UUID
* @returns Query result with list of similar topics
* @example
* const { data: similarTopics } = useSimilarTopics('uuid');
*/
export function useSimilarTopics(id: string) {
return useQuery({
queryKey: topicKeys.similar(id),
queryFn: () => topicService.getSimilarTopics(id),
enabled: !!id,
staleTime: 1000 * 60 * 60, // 1 hour - similarity scores change rarely
});
}
/**
* Hook to fetch topic usage history for heat map visualization (AC2)
*
* @param id Topic UUID
* @returns Query result with array of usage history records
* @example
* const { data: usageHistory } = useTopicUsageHistory('uuid');
*/
export function useTopicUsageHistory(id: string) {
return useQuery({
queryKey: topicKeys.usageHistory(id),
queryFn: () => topicService.getTopicUsageHistory(id),
enabled: !!id,
staleTime: 1000 * 60 * 60, // 1 hour - usage history changes rarely
});
}
/**
* Hook to create new topic (AC8, ORGANIZER only)
*
* @returns Mutation object with mutate function
* @example
* const createMutation = useCreateTopic();
* createMutation.mutate({ title: 'New Topic', category: 'Technical', description: '...' });
*/
export function useCreateTopic() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (request: CreateTopicRequest) => topicService.createTopic(request),
onSuccess: () => {
// Invalidate all topic list queries to refetch fresh data
queryClient.invalidateQueries({ queryKey: topicKeys.lists() });
},
});
}
/**
* Hook to override staleness score with justification (AC7, ORGANIZER only)
*
* @returns Mutation object with mutate function
* @example
* const overrideMutation = useOverrideStaleness();
* overrideMutation.mutate({ id: 'uuid', request: { stalenessScore: 80, justification: '...' } });
*/
export function useOverrideStaleness() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, request }: { id: string; request: OverrideStalenesRequest }) =>
topicService.overrideStaleness(id, request),
onSuccess: (_data, variables) => {
// Invalidate specific topic and all lists
queryClient.invalidateQueries({ queryKey: topicKeys.detail(variables.id) });
queryClient.invalidateQueries({ queryKey: topicKeys.lists() });
},
});
}
/**
* Hook to select topic for event and transition workflow state (AC14, ORGANIZER only)
*
* @returns Mutation object with mutate function
* @example
* const selectMutation = useSelectTopicForEvent();
* selectMutation.mutate({ eventCode: 'BATbern56', topicId: 'uuid', justification: '...' });
*/
export function useSelectTopicForEvent() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
eventCode,
request,
}: {
eventCode: string;
request: SelectTopicForEventRequest;
}) => topicService.selectTopicForEvent(eventCode, request),
onSuccess: (_data, variables) => {
// Invalidate event detail query to refetch updated event with new topicCode
queryClient.invalidateQueries({ queryKey: ['event', variables.eventCode] });
// Invalidate events list query to update list view
queryClient.invalidateQueries({ queryKey: ['events'] });
// Update topic status if applicable
queryClient.invalidateQueries({ queryKey: topicKeys.detail(variables.request.topicCode) });
},
});
}
|