Connect to Park Location Backfill UI

This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 15:40:48 +00:00
parent 054348b9c4
commit d48e95ee7c
3 changed files with 181 additions and 0 deletions

View File

@@ -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<string | null>(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 (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MapPin className="w-5 h-5" />
Park Location Backfill
</CardTitle>
<CardDescription>
Backfill missing location data for approved parks from their submission data
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
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.
</AlertDescription>
</Alert>
{result && (
<Alert className="border-green-200 bg-green-50 dark:bg-green-950 dark:border-green-800">
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
<AlertDescription className="text-green-900 dark:text-green-100">
<div className="font-medium">Backfill completed successfully!</div>
<div className="mt-2 space-y-1">
<div>Parks updated: {result.parks_updated}</div>
<div>Locations created: {result.locations_created}</div>
</div>
</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
onClick={handleBackfill}
disabled={isRunning}
className="w-full"
trackingLabel="run-park-location-backfill"
>
<MapPin className="w-4 h-4 mr-2" />
{isRunning ? 'Running Backfill...' : 'Run Location Backfill'}
</Button>
</CardContent>
</Card>
);
}

View File

@@ -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() {
)}
</CardContent>
</Card>
<ParkLocationBackfill />
</div>
</TabsContent>

View File

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