Files
thrilltrack-explorer/src/hooks/photos/useEntityPhotos.ts
gpt-engineer-app[bot] 875d189881 Implement cache management
2025-10-31 00:46:42 +00:00

120 lines
3.5 KiB
TypeScript

/**
* 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<EntityPhoto[]> {
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;
}