Fix: Implement location data storage fix

This commit is contained in:
gpt-engineer-app[bot]
2025-10-03 15:08:31 +00:00
parent 4493b0824e
commit 404444d5b4
4 changed files with 123 additions and 62 deletions

View File

@@ -23,7 +23,6 @@ interface LocationResult {
}
interface SelectedLocation {
id?: string;
name: string;
city?: string;
state_province?: string;
@@ -32,6 +31,7 @@ interface SelectedLocation {
latitude: number;
longitude: number;
timezone?: string;
display_name: string; // Full OSM display name for reference
}
interface LocationSearchProps {
@@ -61,11 +61,10 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
.from('locations')
.select('*')
.eq('id', locationId)
.single();
.maybeSingle();
if (data && !error) {
setSelectedLocation({
id: data.id,
name: data.name,
city: data.city || undefined,
state_province: data.state_province || undefined,
@@ -74,6 +73,7 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
latitude: parseFloat(data.latitude?.toString() || '0'),
longitude: parseFloat(data.longitude?.toString() || '0'),
timezone: data.timezone || undefined,
display_name: data.name, // Use name as display for existing locations
});
}
};
@@ -94,12 +94,30 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
},
}
);
// Check if response is OK and content-type is JSON
if (!response.ok) {
console.error('OpenStreetMap API error:', response.status);
setResults([]);
setShowResults(false);
return;
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
console.error('Invalid response format from OpenStreetMap');
setResults([]);
setShowResults(false);
return;
}
const data = await response.json();
setResults(data);
setShowResults(true);
} catch (error) {
console.error('Error searching locations:', error);
setResults([]);
setShowResults(false);
} finally {
setIsSearching(false);
}
@@ -123,61 +141,18 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
? `${city}, ${result.address.state || ''} ${result.address.country}`.trim()
: result.display_name;
// Check if location exists in database
const { data: existingLocation } = await supabase
.from('locations')
.select('*')
.eq('latitude', latitude)
.eq('longitude', longitude)
.maybeSingle();
let locationData: SelectedLocation;
if (existingLocation) {
locationData = {
id: existingLocation.id,
name: existingLocation.name,
city: existingLocation.city || undefined,
state_province: existingLocation.state_province || undefined,
country: existingLocation.country,
postal_code: existingLocation.postal_code || undefined,
latitude: parseFloat(existingLocation.latitude?.toString() || '0'),
longitude: parseFloat(existingLocation.longitude?.toString() || '0'),
timezone: existingLocation.timezone || undefined,
};
} else {
// Create new location
const { data: newLocation, error } = await supabase
.from('locations')
.insert({
name: locationName,
city: city || null,
state_province: result.address.state || null,
country: result.address.country || '',
postal_code: result.address.postcode || null,
latitude,
longitude,
})
.select()
.single();
if (error || !newLocation) {
console.error('Error creating location:', error);
return;
}
locationData = {
id: newLocation.id,
name: newLocation.name,
city: newLocation.city || undefined,
state_province: newLocation.state_province || undefined,
country: newLocation.country,
postal_code: newLocation.postal_code || undefined,
latitude: parseFloat(newLocation.latitude?.toString() || '0'),
longitude: parseFloat(newLocation.longitude?.toString() || '0'),
timezone: newLocation.timezone || undefined,
};
}
// Build location data object (no database operations)
const locationData: SelectedLocation = {
name: locationName,
city: city || undefined,
state_province: result.address.state || undefined,
country: result.address.country || '',
postal_code: result.address.postcode || undefined,
latitude,
longitude,
timezone: undefined, // Will be set by server during approval if needed
display_name: result.display_name,
};
setSelectedLocation(locationData);
setSearchQuery('');

View File

@@ -28,6 +28,17 @@ const parkSchema = z.object({
status: z.string().min(1, 'Status is required'),
opening_date: z.string().optional(),
closing_date: z.string().optional(),
location: z.object({
name: z.string(),
city: z.string().optional(),
state_province: z.string().optional(),
country: z.string(),
postal_code: z.string().optional(),
latitude: z.number(),
longitude: z.number(),
timezone: z.string().optional(),
display_name: z.string(),
}).optional(),
location_id: z.string().uuid().optional(),
website_url: z.string().url().optional().or(z.literal('')),
phone: z.string().optional(),
@@ -289,12 +300,12 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
<Label>Location</Label>
<LocationSearch
onLocationSelect={(location) => {
setValue('location_id', location.id);
setValue('location', location);
}}
initialLocationId={watch('location_id')}
/>
<p className="text-sm text-muted-foreground">
Search for the park's location using OpenStreetMap
Search for the park's location using OpenStreetMap. Location will be created when submission is approved.
</p>
</div>

View File

@@ -61,7 +61,21 @@ export interface ParkFormData {
email?: string;
operator_id?: string;
property_owner_id?: string;
// Location can be stored as object for new submissions or ID for editing
location?: {
name: string;
city?: string;
state_province?: string;
country: string;
postal_code?: string;
latitude: number;
longitude: number;
timezone?: string;
display_name: string;
};
location_id?: string;
images?: ImageAssignments;
banner_image_url?: string;
banner_image_id?: string;

View File

@@ -294,6 +294,12 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
// Handle park edit
const resolvedData = resolveDependencies(data, dependencyMap);
// Resolve location_id if location data is provided
let locationId = resolvedData.location_id;
if (resolvedData.location && !locationId) {
locationId = await resolveLocationId(resolvedData.location);
}
// Extract image assignments from ImageAssignments structure
const imageData = extractImageAssignments(resolvedData.images);
@@ -311,7 +317,7 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
email: resolvedData.email || null,
operator_id: resolvedData.operator_id || null,
property_owner_id: resolvedData.property_owner_id || null,
location_id: resolvedData.location_id || null,
location_id: locationId || null,
...imageData,
updated_at: new Date().toISOString()
};
@@ -333,6 +339,12 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
validateSubmissionData(data, 'Park');
const resolvedData = resolveDependencies(data, dependencyMap);
// Resolve location_id if location data is provided
let locationId = resolvedData.location_id;
if (resolvedData.location && !locationId) {
locationId = await resolveLocationId(resolvedData.location);
}
// Ensure unique slug
const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'parks');
resolvedData.slug = uniqueSlug;
@@ -341,7 +353,11 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
const imageData = extractImageAssignments(resolvedData.images);
// Transform to database format
const parkData = { ...transformParkData(resolvedData), ...imageData };
const parkData = {
...transformParkData(resolvedData),
...imageData,
location_id: locationId || null,
};
// Insert into database
const { data: park, error } = await supabase
@@ -358,6 +374,51 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
return park.id;
}
/**
* Resolve location data to a location_id
* Checks for existing locations by coordinates, creates new ones if needed
*/
async function resolveLocationId(locationData: any): Promise<string | null> {
if (!locationData || !locationData.latitude || !locationData.longitude) {
return null;
}
// Check if location already exists by coordinates
const { data: existingLocation } = await supabase
.from('locations')
.select('id')
.eq('latitude', locationData.latitude)
.eq('longitude', locationData.longitude)
.maybeSingle();
if (existingLocation) {
return existingLocation.id;
}
// Create new location (moderator has permission via RLS)
const { data: newLocation, error } = await supabase
.from('locations')
.insert({
name: locationData.name,
city: locationData.city || null,
state_province: locationData.state_province || null,
country: locationData.country,
postal_code: locationData.postal_code || null,
latitude: locationData.latitude,
longitude: locationData.longitude,
timezone: locationData.timezone || null,
})
.select('id')
.single();
if (error) {
console.error('Error creating location:', error);
throw new Error(`Failed to create location: ${error.message}`);
}
return newLocation.id;
}
async function createRide(data: any, dependencyMap: Map<string, string>): Promise<string> {
const { transformRideData, validateSubmissionData } = await import('./entityTransformers');
const { ensureUniqueSlug } = await import('./slugUtils');