diff --git a/src/components/analytics/AnalyticsWrapper.tsx b/src/components/analytics/AnalyticsWrapper.tsx index db65341d..fe4252df 100644 --- a/src/components/analytics/AnalyticsWrapper.tsx +++ b/src/components/analytics/AnalyticsWrapper.tsx @@ -29,6 +29,11 @@ class AnalyticsErrorBoundary extends Component< } export function AnalyticsWrapper() { + // Disable analytics in development to reduce console noise + if (import.meta.env.DEV) { + return null; + } + return ( diff --git a/src/hooks/useAdminDatabaseStats.ts b/src/hooks/useAdminDatabaseStats.ts index 5b3fb83b..03ab57fb 100644 --- a/src/hooks/useAdminDatabaseStats.ts +++ b/src/hooks/useAdminDatabaseStats.ts @@ -1,9 +1,13 @@ import { useQuery } from '@tanstack/react-query'; +import { useLocation } from 'react-router-dom'; import { supabase } from '@/integrations/supabase/client'; import { queryKeys } from '@/lib/queryKeys'; import type { DatabaseStatistics } from '@/types/database-stats'; export function useAdminDatabaseStats() { + const location = useLocation(); + const isAdminPage = location.pathname.startsWith('/admin'); + return useQuery({ queryKey: queryKeys.admin.databaseStats(), queryFn: async () => { @@ -15,7 +19,8 @@ export function useAdminDatabaseStats() { return data as unknown as DatabaseStatistics; }, + enabled: isAdminPage, // Only run query on admin pages staleTime: 5 * 60 * 1000, // 5 minutes - refetchInterval: 60 * 1000, // Auto-refetch every 60 seconds + refetchInterval: isAdminPage ? 60 * 1000 : false, // Only refetch on admin pages }); } diff --git a/src/hooks/useRecentAdditions.ts b/src/hooks/useRecentAdditions.ts index 6f0d2a2a..a34855cc 100644 --- a/src/hooks/useRecentAdditions.ts +++ b/src/hooks/useRecentAdditions.ts @@ -1,10 +1,14 @@ import { useQuery } from '@tanstack/react-query'; +import { useLocation } from 'react-router-dom'; import { supabase } from '@/integrations/supabase/client'; import { queryKeys } from '@/lib/queryKeys'; import type { RecentAddition } from '@/types/database-stats'; import { useEffect } from 'react'; export function useRecentAdditions(limit: number = 50, entityTypeFilter?: string) { + const location = useLocation(); + const isAdminPage = location.pathname.startsWith('/admin'); + const query = useQuery({ queryKey: queryKeys.admin.recentAdditions(limit), queryFn: async () => { @@ -18,8 +22,9 @@ export function useRecentAdditions(limit: number = 50, entityTypeFilter?: string return data as unknown as RecentAddition[]; }, + enabled: isAdminPage, // Only run query on admin pages staleTime: 2 * 60 * 1000, // 2 minutes - refetchInterval: 30 * 1000, // Auto-refetch every 30 seconds + refetchInterval: isAdminPage ? 30 * 1000 : false, // Only refetch on admin pages }); // Set up real-time subscriptions @@ -51,7 +56,7 @@ export function useRecentAdditions(limit: number = 50, entityTypeFilter?: string .subscribe(), supabase .channel('recent_additions_photos') - .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'entity_photos' }, () => { + .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'photos' }, () => { query.refetch(); }) .subscribe(), diff --git a/supabase/migrations/20251111234907_b173cb53-af3b-4e20-94a5-4b0a42e00be7.sql b/supabase/migrations/20251111234907_b173cb53-af3b-4e20-94a5-4b0a42e00be7.sql new file mode 100644 index 00000000..34b27765 --- /dev/null +++ b/supabase/migrations/20251111234907_b173cb53-af3b-4e20-94a5-4b0a42e00be7.sql @@ -0,0 +1,272 @@ +-- Fix get_database_statistics function to use correct table name 'photos' instead of 'entity_photos' +CREATE OR REPLACE FUNCTION public.get_database_statistics() + RETURNS jsonb + LANGUAGE plpgsql + STABLE SECURITY DEFINER + SET search_path TO 'public' +AS $function$ +DECLARE + v_stats jsonb; + v_parks_total integer; + v_parks_active integer; + v_parks_historical integer; + v_parks_7d integer; + v_parks_30d integer; + v_rides_total integer; + v_rides_active integer; + v_rides_historical integer; + v_rides_7d integer; + v_rides_30d integer; + v_companies_total integer; + v_manufacturers integer; + v_operators integer; + v_designers integer; + v_companies_7d integer; + v_companies_30d integer; + v_ride_models_total integer; + v_ride_models_7d integer; + v_ride_models_30d integer; + v_locations_total integer; + v_timeline_events_total integer; + v_photos_total integer; + v_photos_7d integer; + v_photos_30d integer; + v_users_total integer; + v_users_active_30d integer; + v_submissions_pending integer; + v_submissions_approved integer; + v_submissions_rejected integer; +BEGIN + -- Parks statistics + SELECT COUNT(*) INTO v_parks_total FROM parks; + SELECT COUNT(*) INTO v_parks_active FROM parks WHERE status = 'operating'; + SELECT COUNT(*) INTO v_parks_historical FROM parks WHERE status IN ('closed', 'historical'); + SELECT COUNT(*) INTO v_parks_7d FROM parks WHERE created_at > NOW() - INTERVAL '7 days'; + SELECT COUNT(*) INTO v_parks_30d FROM parks WHERE created_at > NOW() - INTERVAL '30 days'; + + -- Rides statistics + SELECT COUNT(*) INTO v_rides_total FROM rides; + SELECT COUNT(*) INTO v_rides_active FROM rides WHERE status = 'operating'; + SELECT COUNT(*) INTO v_rides_historical FROM rides WHERE status IN ('closed', 'removed', 'relocated'); + SELECT COUNT(*) INTO v_rides_7d FROM rides WHERE created_at > NOW() - INTERVAL '7 days'; + SELECT COUNT(*) INTO v_rides_30d FROM rides WHERE created_at > NOW() - INTERVAL '30 days'; + + -- Companies statistics + SELECT COUNT(*) INTO v_companies_total FROM companies; + SELECT COUNT(*) INTO v_manufacturers FROM companies WHERE company_type = 'manufacturer'; + SELECT COUNT(*) INTO v_operators FROM companies WHERE company_type = 'operator'; + SELECT COUNT(*) INTO v_designers FROM companies WHERE company_type = 'designer'; + SELECT COUNT(*) INTO v_companies_7d FROM companies WHERE created_at > NOW() - INTERVAL '7 days'; + SELECT COUNT(*) INTO v_companies_30d FROM companies WHERE created_at > NOW() - INTERVAL '30 days'; + + -- Ride models statistics + SELECT COUNT(*) INTO v_ride_models_total FROM ride_models; + SELECT COUNT(*) INTO v_ride_models_7d FROM ride_models WHERE created_at > NOW() - INTERVAL '7 days'; + SELECT COUNT(*) INTO v_ride_models_30d FROM ride_models WHERE created_at > NOW() - INTERVAL '30 days'; + + -- Locations statistics + SELECT COUNT(*) INTO v_locations_total FROM locations; + + -- Timeline events statistics + SELECT COUNT(*) INTO v_timeline_events_total FROM entity_timeline_events; + + -- Photos statistics - FIXED: using 'photos' table instead of 'entity_photos' + SELECT COUNT(*) INTO v_photos_total FROM photos; + SELECT COUNT(*) INTO v_photos_7d FROM photos WHERE created_at > NOW() - INTERVAL '7 days'; + SELECT COUNT(*) INTO v_photos_30d FROM photos WHERE created_at > NOW() - INTERVAL '30 days'; + + -- Users statistics + SELECT COUNT(*) INTO v_users_total FROM profiles; + SELECT COUNT(*) INTO v_users_active_30d FROM profiles WHERE updated_at > NOW() - INTERVAL '30 days'; + + -- Submissions statistics + SELECT COUNT(*) INTO v_submissions_pending FROM content_submissions WHERE status = 'pending'; + SELECT COUNT(*) INTO v_submissions_approved FROM content_submissions WHERE status = 'approved'; + SELECT COUNT(*) INTO v_submissions_rejected FROM content_submissions WHERE status = 'rejected'; + + -- Build result JSON + v_stats := jsonb_build_object( + 'parks', jsonb_build_object( + 'total', v_parks_total, + 'active', v_parks_active, + 'historical', v_parks_historical, + 'added_7d', v_parks_7d, + 'added_30d', v_parks_30d + ), + 'rides', jsonb_build_object( + 'total', v_rides_total, + 'active', v_rides_active, + 'historical', v_rides_historical, + 'added_7d', v_rides_7d, + 'added_30d', v_rides_30d + ), + 'companies', jsonb_build_object( + 'total', v_companies_total, + 'manufacturers', v_manufacturers, + 'operators', v_operators, + 'designers', v_designers, + 'added_7d', v_companies_7d, + 'added_30d', v_companies_30d + ), + 'ride_models', jsonb_build_object( + 'total', v_ride_models_total, + 'added_7d', v_ride_models_7d, + 'added_30d', v_ride_models_30d + ), + 'locations', jsonb_build_object( + 'total', v_locations_total + ), + 'timeline_events', jsonb_build_object( + 'total', v_timeline_events_total + ), + 'photos', jsonb_build_object( + 'total', v_photos_total, + 'added_7d', v_photos_7d, + 'added_30d', v_photos_30d + ), + 'users', jsonb_build_object( + 'total', v_users_total, + 'active_30d', v_users_active_30d + ), + 'submissions', jsonb_build_object( + 'pending', v_submissions_pending, + 'approved', v_submissions_approved, + 'rejected', v_submissions_rejected + ) + ); + + RETURN v_stats; +END; +$function$; + +-- Fix get_recent_additions function to use correct table and column names +CREATE OR REPLACE FUNCTION public.get_recent_additions(limit_count integer DEFAULT 50) + RETURNS TABLE(entity_id uuid, entity_type text, entity_name text, entity_slug text, park_slug text, image_url text, created_at timestamp with time zone, created_by_id uuid, created_by_username text, created_by_avatar text) + LANGUAGE plpgsql + STABLE SECURITY DEFINER + SET search_path TO 'public' +AS $function$ +BEGIN + RETURN QUERY + SELECT * FROM ( + -- Parks + SELECT + p.id as entity_id, + 'park'::text as entity_type, + p.name as entity_name, + p.slug as entity_slug, + NULL::text as park_slug, + p.card_image_url as image_url, + p.created_at, + p.created_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM parks p + LEFT JOIN profiles prof ON prof.user_id = p.created_by + + UNION ALL + + -- Rides + SELECT + r.id as entity_id, + 'ride'::text as entity_type, + r.name as entity_name, + r.slug as entity_slug, + pk.slug as park_slug, + r.card_image_url as image_url, + r.created_at, + r.created_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM rides r + LEFT JOIN parks pk ON pk.id = r.park_id + LEFT JOIN profiles prof ON prof.user_id = r.created_by + + UNION ALL + + -- Companies + SELECT + c.id as entity_id, + 'company'::text as entity_type, + c.name as entity_name, + c.slug as entity_slug, + NULL::text as park_slug, + c.card_image_url as image_url, + c.created_at, + c.created_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM companies c + LEFT JOIN profiles prof ON prof.user_id = c.created_by + + UNION ALL + + -- Ride Models + SELECT + rm.id as entity_id, + 'ride_model'::text as entity_type, + rm.name as entity_name, + rm.slug as entity_slug, + NULL::text as park_slug, + rm.card_image_url as image_url, + rm.created_at, + rm.created_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM ride_models rm + LEFT JOIN profiles prof ON prof.user_id = rm.created_by + + UNION ALL + + -- Locations + SELECT + l.id as entity_id, + 'location'::text as entity_type, + COALESCE(l.city || ', ' || l.country, l.country, 'Location') as entity_name, + NULL::text as entity_slug, + NULL::text as park_slug, + NULL::text as image_url, + l.created_at, + NULL::uuid as created_by_id, + NULL::text as created_by_username, + NULL::text as created_by_avatar + FROM locations l + + UNION ALL + + -- Timeline Events + SELECT + te.id as entity_id, + 'timeline_event'::text as entity_type, + te.event_title as entity_name, + NULL::text as entity_slug, + NULL::text as park_slug, + NULL::text as image_url, + te.created_at, + te.created_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM entity_timeline_events te + LEFT JOIN profiles prof ON prof.user_id = te.created_by + + UNION ALL + + -- Photos - FIXED: using 'photos' table and correct column names + SELECT + p.id as entity_id, + 'photo'::text as entity_type, + COALESCE(p.title, 'Photo') as entity_name, + NULL::text as entity_slug, + NULL::text as park_slug, + p.cloudflare_image_url as image_url, + p.created_at as created_at, + p.submitted_by as created_by_id, + prof.username as created_by_username, + prof.avatar_url as created_by_avatar + FROM photos p + LEFT JOIN profiles prof ON prof.user_id = p.submitted_by + ) combined + ORDER BY created_at DESC + LIMIT limit_count; +END; +$function$; \ No newline at end of file