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 { interface SelectedLocation {
id?: string;
name: string; name: string;
city?: string; city?: string;
state_province?: string; state_province?: string;
@@ -32,6 +31,7 @@ interface SelectedLocation {
latitude: number; latitude: number;
longitude: number; longitude: number;
timezone?: string; timezone?: string;
display_name: string; // Full OSM display name for reference
} }
interface LocationSearchProps { interface LocationSearchProps {
@@ -61,11 +61,10 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
.from('locations') .from('locations')
.select('*') .select('*')
.eq('id', locationId) .eq('id', locationId)
.single(); .maybeSingle();
if (data && !error) { if (data && !error) {
setSelectedLocation({ setSelectedLocation({
id: data.id,
name: data.name, name: data.name,
city: data.city || undefined, city: data.city || undefined,
state_province: data.state_province || undefined, state_province: data.state_province || undefined,
@@ -74,6 +73,7 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
latitude: parseFloat(data.latitude?.toString() || '0'), latitude: parseFloat(data.latitude?.toString() || '0'),
longitude: parseFloat(data.longitude?.toString() || '0'), longitude: parseFloat(data.longitude?.toString() || '0'),
timezone: data.timezone || undefined, 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(); const data = await response.json();
setResults(data); setResults(data);
setShowResults(true); setShowResults(true);
} catch (error) { } catch (error) {
console.error('Error searching locations:', error); console.error('Error searching locations:', error);
setResults([]); setResults([]);
setShowResults(false);
} finally { } finally {
setIsSearching(false); setIsSearching(false);
} }
@@ -123,61 +141,18 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
? `${city}, ${result.address.state || ''} ${result.address.country}`.trim() ? `${city}, ${result.address.state || ''} ${result.address.country}`.trim()
: result.display_name; : result.display_name;
// Check if location exists in database // Build location data object (no database operations)
const { data: existingLocation } = await supabase const locationData: SelectedLocation = {
.from('locations') name: locationName,
.select('*') city: city || undefined,
.eq('latitude', latitude) state_province: result.address.state || undefined,
.eq('longitude', longitude) country: result.address.country || '',
.maybeSingle(); postal_code: result.address.postcode || undefined,
latitude,
let locationData: SelectedLocation; longitude,
timezone: undefined, // Will be set by server during approval if needed
if (existingLocation) { display_name: result.display_name,
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,
};
}
setSelectedLocation(locationData); setSelectedLocation(locationData);
setSearchQuery(''); setSearchQuery('');

View File

@@ -28,6 +28,17 @@ const parkSchema = z.object({
status: z.string().min(1, 'Status is required'), status: z.string().min(1, 'Status is required'),
opening_date: z.string().optional(), opening_date: z.string().optional(),
closing_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(), location_id: z.string().uuid().optional(),
website_url: z.string().url().optional().or(z.literal('')), website_url: z.string().url().optional().or(z.literal('')),
phone: z.string().optional(), phone: z.string().optional(),
@@ -289,12 +300,12 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
<Label>Location</Label> <Label>Location</Label>
<LocationSearch <LocationSearch
onLocationSelect={(location) => { onLocationSelect={(location) => {
setValue('location_id', location.id); setValue('location', location);
}} }}
initialLocationId={watch('location_id')} initialLocationId={watch('location_id')}
/> />
<p className="text-sm text-muted-foreground"> <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> </p>
</div> </div>

View File

@@ -61,7 +61,21 @@ export interface ParkFormData {
email?: string; email?: string;
operator_id?: string; operator_id?: string;
property_owner_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; location_id?: string;
images?: ImageAssignments; images?: ImageAssignments;
banner_image_url?: string; banner_image_url?: string;
banner_image_id?: string; banner_image_id?: string;

View File

@@ -294,6 +294,12 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
// Handle park edit // Handle park edit
const resolvedData = resolveDependencies(data, dependencyMap); 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 // Extract image assignments from ImageAssignments structure
const imageData = extractImageAssignments(resolvedData.images); const imageData = extractImageAssignments(resolvedData.images);
@@ -311,7 +317,7 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
email: resolvedData.email || null, email: resolvedData.email || null,
operator_id: resolvedData.operator_id || null, operator_id: resolvedData.operator_id || null,
property_owner_id: resolvedData.property_owner_id || null, property_owner_id: resolvedData.property_owner_id || null,
location_id: resolvedData.location_id || null, location_id: locationId || null,
...imageData, ...imageData,
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}; };
@@ -333,6 +339,12 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
validateSubmissionData(data, 'Park'); validateSubmissionData(data, 'Park');
const resolvedData = resolveDependencies(data, dependencyMap); 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 // Ensure unique slug
const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'parks'); const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'parks');
resolvedData.slug = uniqueSlug; resolvedData.slug = uniqueSlug;
@@ -341,7 +353,11 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
const imageData = extractImageAssignments(resolvedData.images); const imageData = extractImageAssignments(resolvedData.images);
// Transform to database format // Transform to database format
const parkData = { ...transformParkData(resolvedData), ...imageData }; const parkData = {
...transformParkData(resolvedData),
...imageData,
location_id: locationId || null,
};
// Insert into database // Insert into database
const { data: park, error } = await supabase const { data: park, error } = await supabase
@@ -358,6 +374,51 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
return park.id; 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> { async function createRide(data: any, dependencyMap: Map<string, string>): Promise<string> {
const { transformRideData, validateSubmissionData } = await import('./entityTransformers'); const { transformRideData, validateSubmissionData } = await import('./entityTransformers');
const { ensureUniqueSlug } = await import('./slugUtils'); const { ensureUniqueSlug } = await import('./slugUtils');