From d48e95ee7cc53d3e84881a8f8ec096f7a3fe5ee4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:40:48 +0000 Subject: [PATCH] Connect to Park Location Backfill UI --- src/components/admin/ParkLocationBackfill.tsx | 100 ++++++++++++++++++ src/pages/AdminSettings.tsx | 3 + ...0_088c612b-4053-4cf4-aa62-05c648fa6275.sql | 78 ++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/components/admin/ParkLocationBackfill.tsx create mode 100644 supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql diff --git a/src/components/admin/ParkLocationBackfill.tsx b/src/components/admin/ParkLocationBackfill.tsx new file mode 100644 index 00000000..42eff262 --- /dev/null +++ b/src/components/admin/ParkLocationBackfill.tsx @@ -0,0 +1,100 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { supabase } from '@/lib/supabaseClient'; +import { MapPin, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; + +export function ParkLocationBackfill() { + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState<{ + success: boolean; + parks_updated: number; + locations_created: number; + } | null>(null); + const [error, setError] = useState(null); + const { toast } = useToast(); + + const handleBackfill = async () => { + setIsRunning(true); + setError(null); + setResult(null); + + try { + const { data, error: invokeError } = await supabase.functions.invoke( + 'backfill-park-locations' + ); + + if (invokeError) throw invokeError; + + setResult(data); + toast({ + title: 'Backfill Complete', + description: `Updated ${data.parks_updated} parks with ${data.locations_created} new locations`, + }); + } catch (err: any) { + const errorMessage = err.message || 'Failed to run backfill'; + setError(errorMessage); + toast({ + title: 'Backfill Failed', + description: errorMessage, + variant: 'destructive', + }); + } finally { + setIsRunning(false); + } + }; + + return ( + + + + + Park Location Backfill + + + Backfill missing location data for approved parks from their submission data + + + + + + + This tool will find parks without location data and populate them using the location information from their approved submissions. This is useful for fixing parks that were approved before the location creation fix was implemented. + + + + {result && ( + + + +
Backfill completed successfully!
+
+
Parks updated: {result.parks_updated}
+
Locations created: {result.locations_created}
+
+
+
+ )} + + {error && ( + + + {error} + + )} + + +
+
+ ); +} diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index 0409d3d4..65ac86f0 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -14,6 +14,7 @@ import { useAdminSettings } from '@/hooks/useAdminSettings'; import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility'; import { TestDataGenerator } from '@/components/admin/TestDataGenerator'; import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner'; +import { ParkLocationBackfill } from '@/components/admin/ParkLocationBackfill'; import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock, TestTube, RefreshCw, Info, AlertCircle, Database } from 'lucide-react'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; @@ -935,6 +936,8 @@ export default function AdminSettings() { )} + + diff --git a/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql b/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql new file mode 100644 index 00000000..5f994ac0 --- /dev/null +++ b/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql @@ -0,0 +1,78 @@ +-- Fix search_path for backfill_park_locations function +CREATE OR REPLACE FUNCTION backfill_park_locations() +RETURNS jsonb +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_parks_updated INTEGER := 0; + v_locations_created INTEGER := 0; + v_park RECORD; + v_submission RECORD; + v_location_id UUID; +BEGIN + -- Find parks without locations that have approved submissions with location data + FOR v_park IN + SELECT DISTINCT p.id, p.name, p.slug + FROM parks p + WHERE p.location_id IS NULL + LOOP + -- Find the most recent approved submission for this park with location data + SELECT + psl.country, + psl.state_province, + psl.city, + psl.street_address, + psl.postal_code, + psl.latitude, + psl.longitude + INTO v_submission + FROM park_submissions ps + JOIN park_submission_locations psl ON ps.id = psl.park_submission_id + WHERE ps.park_id = v_park.id + AND ps.status = 'approved' + AND psl.country IS NOT NULL + ORDER BY ps.created_at DESC + LIMIT 1; + + -- If we found location data, create a location record + IF FOUND THEN + INSERT INTO locations ( + country, + state_province, + city, + street_address, + postal_code, + latitude, + longitude + ) VALUES ( + v_submission.country, + v_submission.state_province, + v_submission.city, + v_submission.street_address, + v_submission.postal_code, + v_submission.latitude, + v_submission.longitude + ) + RETURNING id INTO v_location_id; + + -- Update the park with the new location + UPDATE parks + SET location_id = v_location_id + WHERE id = v_park.id; + + v_parks_updated := v_parks_updated + 1; + v_locations_created := v_locations_created + 1; + + RAISE NOTICE 'Backfilled location for park: % (id: %)', v_park.name, v_park.id; + END IF; + END LOOP; + + RETURN jsonb_build_object( + 'success', true, + 'parks_updated', v_parks_updated, + 'locations_created', v_locations_created + ); +END; +$$; \ No newline at end of file