mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 01:11:13 -05:00
Fix: Implement location data storage fix
This commit is contained in:
@@ -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('');
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user