feat: Implement full Phase 3 API optimizations

This commit is contained in:
gpt-engineer-app[bot]
2025-10-30 23:02:26 +00:00
parent 46ca1c29bc
commit 0091584677
18 changed files with 654 additions and 243 deletions

View File

@@ -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(() => {

View File

@@ -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';