Approve database migration

This commit is contained in:
gpt-engineer-app[bot]
2025-10-10 13:04:36 +00:00
parent 26e6200bc1
commit 61c7a551c2
7 changed files with 293 additions and 11 deletions

View File

@@ -24,6 +24,9 @@ export default function DesignerDetail() {
const [designer, setDesigner] = useState<Company | null>(null);
const [loading, setLoading] = useState(true);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [totalRides, setTotalRides] = useState<number>(0);
const [totalPhotos, setTotalPhotos] = useState<number>(0);
const [statsLoading, setStatsLoading] = useState(true);
const { user } = useAuth();
const { isModerator } = useUserRole();
@@ -44,6 +47,9 @@ export default function DesignerDetail() {
if (error) throw error;
setDesigner(data);
if (data) {
fetchStatistics(data.id);
}
} catch (error) {
console.error('Error fetching designer:', error);
} finally {
@@ -51,6 +57,33 @@ export default function DesignerDetail() {
}
};
const fetchStatistics = async (designerId: string) => {
try {
// Count rides
const { count: ridesCount, error: ridesError } = await supabase
.from('rides')
.select('id', { count: 'exact', head: true })
.eq('designer_id', designerId);
if (ridesError) throw ridesError;
setTotalRides(ridesCount || 0);
// Count photos
const { count: photosCount, error: photosError } = await supabase
.from('photos')
.select('id', { count: 'exact', head: true })
.eq('entity_type', 'designer')
.eq('entity_id', designerId);
if (photosError) throw photosError;
setTotalPhotos(photosCount || 0);
} catch (error) {
console.error('Error fetching statistics:', error);
} finally {
setStatsLoading(false);
}
};
const handleEditSubmit = async (data: any) => {
try {
await submitCompanyUpdate(
@@ -229,8 +262,12 @@ export default function DesignerDetail() {
<Tabs defaultValue="overview" className="w-full">
<TabsList className="grid w-full grid-cols-2 md:grid-cols-4">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="rides">Rides</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="rides">
Rides {!statsLoading && totalRides > 0 && `(${totalRides})`}
</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && totalPhotos > 0 && `(${totalPhotos})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -24,6 +24,10 @@ export default function ManufacturerDetail() {
const [manufacturer, setManufacturer] = useState<Company | null>(null);
const [loading, setLoading] = useState(true);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [totalRides, setTotalRides] = useState<number>(0);
const [totalModels, setTotalModels] = useState<number>(0);
const [totalPhotos, setTotalPhotos] = useState<number>(0);
const [statsLoading, setStatsLoading] = useState(true);
const { user } = useAuth();
const { isModerator } = useUserRole();
@@ -44,6 +48,9 @@ export default function ManufacturerDetail() {
if (error) throw error;
setManufacturer(data);
if (data) {
fetchStatistics(data.id);
}
} catch (error) {
console.error('Error fetching manufacturer:', error);
} finally {
@@ -51,6 +58,42 @@ export default function ManufacturerDetail() {
}
};
const fetchStatistics = async (manufacturerId: string) => {
try {
// Count rides
const { count: ridesCount, error: ridesError } = await supabase
.from('rides')
.select('id', { count: 'exact', head: true })
.eq('manufacturer_id', manufacturerId);
if (ridesError) throw ridesError;
setTotalRides(ridesCount || 0);
// Count models
const { count: modelsCount, error: modelsError } = await supabase
.from('ride_models')
.select('id', { count: 'exact', head: true })
.eq('manufacturer_id', manufacturerId);
if (modelsError) throw modelsError;
setTotalModels(modelsCount || 0);
// Count photos
const { count: photosCount, error: photosError } = await supabase
.from('photos')
.select('id', { count: 'exact', head: true })
.eq('entity_type', 'manufacturer')
.eq('entity_id', manufacturerId);
if (photosError) throw photosError;
setTotalPhotos(photosCount || 0);
} catch (error) {
console.error('Error fetching statistics:', error);
} finally {
setStatsLoading(false);
}
};
const handleEditSubmit = async (data: any) => {
try {
await submitCompanyUpdate(
@@ -231,9 +274,15 @@ export default function ManufacturerDetail() {
<Tabs defaultValue="overview" className="w-full">
<TabsList className="grid w-full grid-cols-2 md:grid-cols-5">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="rides">Rides</TabsTrigger>
<TabsTrigger value="models">Models</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="rides">
Rides {!statsLoading && totalRides > 0 && `(${totalRides})`}
</TabsTrigger>
<TabsTrigger value="models">
Models {!statsLoading && totalModels > 0 && `(${totalModels})`}
</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && totalPhotos > 0 && `(${totalPhotos})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -30,6 +30,7 @@ export default function OperatorDetail() {
const [totalParks, setTotalParks] = useState<number>(0);
const [operatingRides, setOperatingRides] = useState<number>(0);
const [statsLoading, setStatsLoading] = useState(true);
const [totalPhotos, setTotalPhotos] = useState<number>(0);
const { user } = useAuth();
const { isModerator } = useUserRole();
@@ -39,6 +40,57 @@ export default function OperatorDetail() {
}
}, [slug]);
// Real-time subscription for parks, rides, and photos changes
useEffect(() => {
if (!operator?.id) return;
const channel = supabase
.channel('operator-stats-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'parks',
filter: `operator_id=eq.${operator.id}`
},
(payload) => {
console.log('Park change detected for operator:', payload);
fetchStatistics(operator.id);
}
)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'rides'
},
(payload) => {
console.log('Ride status change detected:', payload);
fetchStatistics(operator.id);
}
)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'photos',
filter: `entity_type=eq.operator,entity_id=eq.${operator.id}`
},
(payload) => {
console.log('Photo change detected for operator:', payload);
fetchPhotoCount(operator.id);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [operator?.id]);
const fetchOperatorData = async () => {
try {
const { data, error } = await supabase
@@ -55,6 +107,7 @@ export default function OperatorDetail() {
if (data) {
fetchParks(data.id);
fetchStatistics(data.id);
fetchPhotoCount(data.id);
}
} catch (error) {
console.error('Error fetching operator:', error);
@@ -111,6 +164,22 @@ export default function OperatorDetail() {
}
};
const fetchPhotoCount = async (operatorId: string) => {
try {
const { count, error } = await supabase
.from('photos')
.select('id', { count: 'exact', head: true })
.eq('entity_type', 'operator')
.eq('entity_id', operatorId);
if (error) throw error;
setTotalPhotos(count || 0);
} catch (error) {
console.error('Error fetching photo count:', error);
setTotalPhotos(0);
}
};
const handleEditSubmit = async (data: any) => {
try {
await submitCompanyUpdate(
@@ -314,8 +383,12 @@ export default function OperatorDetail() {
<Tabs defaultValue="overview" className="w-full">
<TabsList className="grid w-full grid-cols-2 md:grid-cols-4">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="parks">Parks</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="parks">
Parks {!statsLoading && totalParks > 0 && `(${totalParks})`}
</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && totalPhotos > 0 && `(${totalPhotos})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -36,6 +36,8 @@ export default function ParkDetail() {
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();
useEffect(() => {
if (slug) {
@@ -55,6 +57,7 @@ export default function ParkDetail() {
`).eq('slug', slug).maybeSingle();
if (parkData) {
setPark(parkData);
fetchPhotoCount(parkData.id);
// Fetch park rides
const {
@@ -68,6 +71,24 @@ export default function ParkDetail() {
setLoading(false);
}
};
const fetchPhotoCount = 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 getStatusColor = (status: string) => {
switch (status) {
case 'operating':
@@ -361,7 +382,9 @@ export default function ParkDetail() {
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="rides">Rides ({rides.length})</TabsTrigger>
<TabsTrigger value="reviews">Reviews</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && photoCount > 0 && `(${photoCount})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -30,6 +30,7 @@ export default function PropertyOwnerDetail() {
const [totalParks, setTotalParks] = useState<number>(0);
const [operatingRides, setOperatingRides] = useState<number>(0);
const [statsLoading, setStatsLoading] = useState(true);
const [totalPhotos, setTotalPhotos] = useState<number>(0);
const { user } = useAuth();
const { isModerator } = useUserRole();
@@ -39,6 +40,57 @@ export default function PropertyOwnerDetail() {
}
}, [slug]);
// Real-time subscription for parks and photos changes
useEffect(() => {
if (!owner?.id) return;
const channel = supabase
.channel('owner-stats-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'parks',
filter: `property_owner_id=eq.${owner.id}`
},
(payload) => {
console.log('Park change detected for owner:', payload);
fetchStatistics(owner.id);
}
)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'rides'
},
(payload) => {
console.log('Ride status change detected:', payload);
fetchStatistics(owner.id);
}
)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'photos',
filter: `entity_type=eq.property_owner,entity_id=eq.${owner.id}`
},
(payload) => {
console.log('Photo change detected for owner:', payload);
fetchPhotoCount(owner.id);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [owner?.id]);
const fetchOwnerData = async () => {
try {
const { data, error } = await supabase
@@ -55,6 +107,7 @@ export default function PropertyOwnerDetail() {
if (data) {
fetchParks(data.id);
fetchStatistics(data.id);
fetchPhotoCount(data.id);
}
} catch (error) {
console.error('Error fetching property owner:', error);
@@ -111,6 +164,22 @@ export default function PropertyOwnerDetail() {
}
};
const fetchPhotoCount = async (ownerId: string) => {
try {
const { count, error } = await supabase
.from('photos')
.select('id', { count: 'exact', head: true })
.eq('entity_type', 'property_owner')
.eq('entity_id', ownerId);
if (error) throw error;
setTotalPhotos(count || 0);
} catch (error) {
console.error('Error fetching photo count:', error);
setTotalPhotos(0);
}
};
const handleEditSubmit = async (data: any) => {
try {
await submitCompanyUpdate(
@@ -314,8 +383,12 @@ export default function PropertyOwnerDetail() {
<Tabs defaultValue="overview" className="w-full">
<TabsList className="grid w-full grid-cols-2 md:grid-cols-4">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="parks">Parks</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="parks">
Parks {!statsLoading && totalParks > 0 && `(${totalParks})`}
</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && totalPhotos > 0 && `(${totalPhotos})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -53,6 +53,8 @@ export default function RideDetail() {
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);
const { user } = useAuth();
const { isModerator } = useUserRole();
@@ -88,6 +90,7 @@ export default function RideDetail() {
if (rideData) {
// Store park_id for easier access
(rideData as any).currentParkId = parkData.id;
fetchPhotoCount(rideData.id);
}
setRide(rideData);
@@ -99,6 +102,24 @@ export default function RideDetail() {
}
};
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';
@@ -387,7 +408,9 @@ export default function RideDetail() {
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="specs">Specifications</TabsTrigger>
<TabsTrigger value="reviews">Reviews</TabsTrigger>
<TabsTrigger value="photos">Photos</TabsTrigger>
<TabsTrigger value="photos">
Photos {!statsLoading && photoCount > 0 && `(${photoCount})`}
</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
</TabsList>

View File

@@ -0,0 +1,4 @@
-- Enable replica identity for real-time change tracking
ALTER TABLE parks REPLICA IDENTITY FULL;
ALTER TABLE rides REPLICA IDENTITY FULL;
ALTER TABLE photos REPLICA IDENTITY FULL;