mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 15:11:12 -05:00
feat: Implement full Phase 3 API optimizations
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
|
||||
import { useState, lazy, Suspense, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Header } from '@/components/layout/Header';
|
||||
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||
@@ -15,10 +15,12 @@ import { RideCard } from '@/components/rides/RideCard';
|
||||
import { Park, Ride } from '@/types/database';
|
||||
import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
|
||||
import { EntityPhotoGallery } from '@/components/upload/EntityPhotoGallery';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { AdminFormSkeleton } from '@/components/loading/PageSkeletons';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useParkDetail } from '@/hooks/parks/useParkDetail';
|
||||
import { useParkRides } from '@/hooks/parks/useParkRides';
|
||||
import { usePhotoCount } from '@/hooks/photos/usePhotoCount';
|
||||
|
||||
// Lazy load admin forms
|
||||
const RideForm = lazy(() => import('@/components/admin/RideForm').then(m => ({ default: m.RideForm })));
|
||||
@@ -33,23 +35,23 @@ import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
||||
|
||||
export default function ParkDetail() {
|
||||
const {
|
||||
slug
|
||||
} = useParams<{
|
||||
slug: string;
|
||||
}>();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [park, setPark] = useState<Park | null>(null);
|
||||
const [rides, setRides] = useState<Ride[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAddRideModalOpen, setIsAddRideModalOpen] = useState(false);
|
||||
const [isEditParkModalOpen, setIsEditParkModalOpen] = useState(false);
|
||||
const [photoCount, setPhotoCount] = useState<number>(0);
|
||||
const [statsLoading, setStatsLoading] = useState(true);
|
||||
const { isModerator } = useUserRole();
|
||||
|
||||
// Fetch park data with caching
|
||||
const { data: park, isLoading: loading, error } = useParkDetail(slug);
|
||||
|
||||
// Fetch rides with caching
|
||||
const { data: rides = [] } = useParkRides(park?.id, !!park?.id);
|
||||
|
||||
// Fetch photo count with caching
|
||||
const { data: photoCount = 0, isLoading: statsLoading } = usePhotoCount('park', park?.id, !!park?.id);
|
||||
|
||||
// Update document title when park changes
|
||||
useDocumentTitle(park?.name || 'Park Details');
|
||||
|
||||
@@ -62,58 +64,6 @@ export default function ParkDetail() {
|
||||
type: 'website',
|
||||
enabled: !!park
|
||||
});
|
||||
|
||||
const fetchPhotoCount = useCallback(async (parkId: string) => {
|
||||
try {
|
||||
const { count, error } = await supabase
|
||||
.from('photos')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('entity_type', 'park')
|
||||
.eq('entity_id', parkId);
|
||||
|
||||
if (error) throw error;
|
||||
setPhotoCount(count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error fetching photo count:', error);
|
||||
setPhotoCount(0);
|
||||
} finally {
|
||||
setStatsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchParkData = useCallback(async () => {
|
||||
try {
|
||||
// Fetch park details
|
||||
const {
|
||||
data: parkData
|
||||
} = await supabase.from('parks').select(`
|
||||
*,
|
||||
location:locations(*),
|
||||
operator:companies!parks_operator_id_fkey(*),
|
||||
property_owner:companies!parks_property_owner_id_fkey(*)
|
||||
`).eq('slug', slug).maybeSingle();
|
||||
if (parkData) {
|
||||
setPark(parkData);
|
||||
fetchPhotoCount(parkData.id);
|
||||
|
||||
// Fetch park rides
|
||||
const {
|
||||
data: ridesData
|
||||
} = await supabase.from('rides').select(`*`).eq('park_id', parkData.id).order('name');
|
||||
setRides(ridesData || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching park data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [slug, fetchPhotoCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (slug) {
|
||||
fetchParkData();
|
||||
}
|
||||
}, [slug, fetchParkData]);
|
||||
|
||||
// Track page view when park is loaded
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, lazy, Suspense } from 'react';
|
||||
import { useState, lazy, Suspense, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Header } from '@/components/layout/Header';
|
||||
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||
@@ -45,8 +45,9 @@ import { FormerNames } from '@/components/rides/FormerNames';
|
||||
import { RecentPhotosPreview } from '@/components/rides/RecentPhotosPreview';
|
||||
import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
|
||||
import { Ride } from '@/types/database';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useRideDetail } from '@/hooks/rides/useRideDetail';
|
||||
import { usePhotoCount } from '@/hooks/photos/usePhotoCount';
|
||||
|
||||
// Lazy load admin forms
|
||||
const RideForm = lazy(() => import('@/components/admin/RideForm').then(m => ({ default: m.RideForm })));
|
||||
@@ -70,12 +71,17 @@ export default function RideDetail() {
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [ride, setRide] = useState<RideWithParkId | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [photoCount, setPhotoCount] = useState<number>(0);
|
||||
const [statsLoading, setStatsLoading] = useState(true);
|
||||
|
||||
// Fetch ride data with caching
|
||||
const { data: rideData, isLoading: loading } = useRideDetail(parkSlug, rideSlug);
|
||||
|
||||
// Cast to RideWithParkId to include currentParkId
|
||||
const ride = rideData as RideWithParkId | null;
|
||||
|
||||
// Fetch photo count with caching
|
||||
const { data: photoCount = 0, isLoading: statsLoading } = usePhotoCount('ride', ride?.id, !!ride?.id);
|
||||
|
||||
// Update document title when ride changes
|
||||
useDocumentTitle(ride?.name || 'Ride Details');
|
||||
@@ -90,12 +96,6 @@ export default function RideDetail() {
|
||||
enabled: !!ride
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (parkSlug && rideSlug) {
|
||||
fetchRideData();
|
||||
}
|
||||
}, [parkSlug, rideSlug]);
|
||||
|
||||
// Track page view when ride is loaded
|
||||
useEffect(() => {
|
||||
if (ride?.id) {
|
||||
@@ -103,66 +103,6 @@ export default function RideDetail() {
|
||||
}
|
||||
}, [ride?.id]);
|
||||
|
||||
const fetchRideData = async () => {
|
||||
try {
|
||||
// First get park to find park_id
|
||||
const { data: parkData } = await supabase
|
||||
.from('parks')
|
||||
.select('id')
|
||||
.eq('slug', parkSlug)
|
||||
.maybeSingle();
|
||||
|
||||
if (parkData) {
|
||||
// Then get ride details with park_id stored separately
|
||||
const { data: rideData } = await supabase
|
||||
.from('rides')
|
||||
.select(`
|
||||
*,
|
||||
park:parks!inner(id, name, slug, location:locations(*)),
|
||||
manufacturer:companies!rides_manufacturer_id_fkey(*),
|
||||
designer:companies!rides_designer_id_fkey(*)
|
||||
`)
|
||||
.eq('park_id', parkData.id)
|
||||
.eq('slug', rideSlug)
|
||||
.maybeSingle();
|
||||
|
||||
if (rideData) {
|
||||
// Store park_id for easier access
|
||||
const extendedRide: RideWithParkId = {
|
||||
...rideData,
|
||||
currentParkId: parkData.id
|
||||
};
|
||||
setRide(extendedRide);
|
||||
fetchPhotoCount(rideData.id);
|
||||
} else {
|
||||
setRide(null);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching ride data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPhotoCount = async (rideId: string) => {
|
||||
try {
|
||||
const { count, error } = await supabase
|
||||
.from('photos')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('entity_type', 'ride')
|
||||
.eq('entity_id', rideId);
|
||||
|
||||
if (error) throw error;
|
||||
setPhotoCount(count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error fetching photo count:', error);
|
||||
setPhotoCount(0);
|
||||
} finally {
|
||||
setStatsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'operating': return 'bg-green-500/20 text-green-400 border-green-500/30';
|
||||
|
||||
Reference in New Issue
Block a user