/** * Entity Photos Hook * * Fetches photos for a specific entity with intelligent caching and sort support. * * Features: * - Caches photos for 5 minutes (staleTime) * - Background refetch every 15 minutes (gcTime) * - Supports 'newest' and 'oldest' sorting without refetching * - Performance monitoring in dev mode * * @param entityType - Type of entity ('park', 'ride', 'company', etc.) * @param entityId - UUID of the entity * @param sortBy - Sort order: 'newest' (default) or 'oldest' * * @returns TanStack Query result with photo array * * @example * ```tsx * const { data: photos, isLoading, refetch } = useEntityPhotos('park', parkId, 'newest'); * * // After uploading new photos: * await uploadPhotos(); * refetch(); // Refresh this component * invalidateEntityPhotos('park', parkId); // Refresh all components * ``` */ import { useQuery, UseQueryResult, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { queryKeys } from '@/lib/queryKeys'; interface EntityPhoto { id: string; url: string; caption?: string; title?: string; user_id: string; created_at: string; } export function useEntityPhotos( entityType: string, entityId: string, sortBy: 'newest' | 'oldest' = 'newest', enableRealtime = false // New parameter for opt-in real-time updates ): UseQueryResult { const queryClient = useQueryClient(); const query = useQuery({ queryKey: queryKeys.photos.entity(entityType, entityId, sortBy), queryFn: async () => { const startTime = performance.now(); const { data, error } = await supabase .from('photos') .select('id, cloudflare_image_url, title, caption, submitted_by, created_at, order_index') .eq('entity_type', entityType) .eq('entity_id', entityId) .order('created_at', { ascending: sortBy === 'oldest' }); if (error) throw error; const result = data?.map((photo) => ({ id: photo.id, url: photo.cloudflare_image_url, caption: photo.caption || undefined, title: photo.title || undefined, user_id: photo.submitted_by, created_at: photo.created_at, })) || []; // Performance monitoring (dev only) if (import.meta.env.DEV) { const duration = performance.now() - startTime; if (duration > 1000) { console.warn(`⚠️ Slow query: useEntityPhotos took ${duration.toFixed(0)}ms`, { entityType, entityId, photoCount: result.length }); } } return result; }, enabled: !!entityType && !!entityId, staleTime: 5 * 60 * 1000, gcTime: 15 * 60 * 1000, refetchOnWindowFocus: false, }); // Real-time subscription for photo uploads (opt-in) useEffect(() => { if (!enableRealtime || !entityType || !entityId) return; const channel = supabase .channel(`photos-${entityType}-${entityId}`) .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'photos', filter: `entity_type=eq.${entityType},entity_id=eq.${entityId}`, }, (payload) => { console.log('📸 New photo uploaded:', payload.new); queryClient.invalidateQueries({ queryKey: queryKeys.photos.entity(entityType, entityId) }); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [enableRealtime, entityType, entityId, queryClient, sortBy]); return query; }