mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 14:06:58 -05:00
Compare commits
2 Commits
c1683f9b02
...
fc7c2d5adc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc7c2d5adc | ||
|
|
98fbc94476 |
@@ -14,17 +14,27 @@ interface LocationResult {
|
||||
lat: string;
|
||||
lon: string;
|
||||
address: {
|
||||
house_number?: string;
|
||||
road?: string;
|
||||
city?: string;
|
||||
town?: string;
|
||||
village?: string;
|
||||
municipality?: string;
|
||||
state?: string;
|
||||
province?: string;
|
||||
state_district?: string;
|
||||
county?: string;
|
||||
region?: string;
|
||||
territory?: string;
|
||||
country?: string;
|
||||
country_code?: string;
|
||||
postcode?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SelectedLocation {
|
||||
name: string;
|
||||
street_address?: string;
|
||||
city?: string;
|
||||
state_province?: string;
|
||||
country: string;
|
||||
@@ -61,13 +71,14 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
|
||||
const loadInitialLocation = async (locationId: string): Promise<void> => {
|
||||
const { data, error } = await supabase
|
||||
.from('locations')
|
||||
.select('id, name, city, state_province, country, postal_code, latitude, longitude, timezone')
|
||||
.select('id, name, street_address, city, state_province, country, postal_code, latitude, longitude, timezone')
|
||||
.eq('id', locationId)
|
||||
.maybeSingle();
|
||||
|
||||
if (data && !error) {
|
||||
setSelectedLocation({
|
||||
name: data.name,
|
||||
street_address: data.street_address || undefined,
|
||||
city: data.city || undefined,
|
||||
state_province: data.state_province || undefined,
|
||||
country: data.country,
|
||||
@@ -150,21 +161,38 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
|
||||
|
||||
// Safely access address properties with fallback
|
||||
const address = result.address || {};
|
||||
const city = address.city || address.town || address.village;
|
||||
const state = address.state || '';
|
||||
const country = address.country || 'Unknown';
|
||||
|
||||
const locationName = city
|
||||
? `${city}, ${state} ${country}`.trim()
|
||||
: result.display_name;
|
||||
// Extract street address components
|
||||
const houseNumber = address.house_number || '';
|
||||
const road = address.road || '';
|
||||
const streetAddress = [houseNumber, road].filter(Boolean).join(' ').trim() || undefined;
|
||||
|
||||
// Extract city
|
||||
const city = address.city || address.town || address.village || address.municipality;
|
||||
|
||||
// Extract state/province (try multiple fields for international support)
|
||||
const state = address.state ||
|
||||
address.province ||
|
||||
address.state_district ||
|
||||
address.county ||
|
||||
address.region ||
|
||||
address.territory;
|
||||
|
||||
const country = address.country || 'Unknown';
|
||||
const postalCode = address.postcode;
|
||||
|
||||
// Build location name
|
||||
const locationParts = [streetAddress, city, state, country].filter(Boolean);
|
||||
const locationName = locationParts.join(', ');
|
||||
|
||||
// Build location data object (no database operations)
|
||||
const locationData: SelectedLocation = {
|
||||
name: locationName,
|
||||
street_address: streetAddress,
|
||||
city: city || undefined,
|
||||
state_province: state || undefined,
|
||||
country: country,
|
||||
postal_code: address.postcode || undefined,
|
||||
postal_code: postalCode || undefined,
|
||||
latitude,
|
||||
longitude,
|
||||
timezone: undefined, // Will be set by server during approval if needed
|
||||
@@ -249,6 +277,7 @@ export function LocationSearch({ onLocationSelect, initialLocationId, className
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium">{selectedLocation.name}</p>
|
||||
<div className="text-sm text-muted-foreground space-y-1 mt-1">
|
||||
{selectedLocation.street_address && <p>Street: {selectedLocation.street_address}</p>}
|
||||
{selectedLocation.city && <p>City: {selectedLocation.city}</p>}
|
||||
{selectedLocation.state_province && <p>State/Province: {selectedLocation.state_province}</p>}
|
||||
<p>Country: {selectedLocation.country}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent, LucideIcon } from 'lucide-react';
|
||||
import { formatLocationShort } from '@/lib/locationFormatter';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -82,7 +83,7 @@ export function FeaturedParks() {
|
||||
|
||||
{park.location && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{park.location.city}, {park.location.country}
|
||||
{formatLocationShort(park.location)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -109,9 +109,11 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
||||
<span className="text-sm font-semibold text-foreground">Location</span>
|
||||
</div>
|
||||
<div className="text-sm space-y-1 ml-6">
|
||||
{location.street_address && <div><span className="text-muted-foreground">Street:</span> <span className="font-medium">{location.street_address}</span></div>}
|
||||
{location.city && <div><span className="text-muted-foreground">City:</span> <span className="font-medium">{location.city}</span></div>}
|
||||
{location.state_province && <div><span className="text-muted-foreground">State/Province:</span> <span className="font-medium">{location.state_province}</span></div>}
|
||||
{location.country && <div><span className="text-muted-foreground">Country:</span> <span className="font-medium">{location.country}</span></div>}
|
||||
{location.postal_code && <div><span className="text-muted-foreground">Postal Code:</span> <span className="font-medium">{location.postal_code}</span></div>}
|
||||
{location.formatted_address && (
|
||||
<div className="text-xs text-muted-foreground mt-2">{location.formatted_address}</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MapPin, Star, Users, Clock, Castle, FerrisWheel, Waves, Tent } from 'lucide-react';
|
||||
import { formatLocationShort } from '@/lib/locationFormatter';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -102,7 +103,7 @@ export function ParkCard({ park }: ParkCardProps) {
|
||||
<div className="flex items-center gap-1 text-sm text-muted-foreground min-w-0">
|
||||
<MapPin className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="truncate">
|
||||
{park.location.city && `${park.location.city}, `}{park.location.country}
|
||||
{formatLocationShort(park.location)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { useGlobalSearch } from '@/hooks/search/useGlobalSearch';
|
||||
import { formatLocationShort } from '@/lib/locationFormatter';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -87,7 +88,7 @@ export function SearchResults({ query, onClose }: SearchResultsProps) {
|
||||
switch (result.type) {
|
||||
case 'park':
|
||||
const park = result.data as Park;
|
||||
return park.location ? `${park.location.city}, ${park.location.country}` : 'Theme Park';
|
||||
return park.location ? formatLocationShort(park.location) : 'Theme Park';
|
||||
case 'ride':
|
||||
const ride = result.data as Ride;
|
||||
return ride.park && typeof ride.park === 'object' && 'name' in ride.park
|
||||
|
||||
@@ -1615,6 +1615,7 @@ export type Database = {
|
||||
name: string
|
||||
postal_code: string | null
|
||||
state_province: string | null
|
||||
street_address: string | null
|
||||
timezone: string | null
|
||||
}
|
||||
Insert: {
|
||||
@@ -1627,6 +1628,7 @@ export type Database = {
|
||||
name: string
|
||||
postal_code?: string | null
|
||||
state_province?: string | null
|
||||
street_address?: string | null
|
||||
timezone?: string | null
|
||||
}
|
||||
Update: {
|
||||
@@ -1639,6 +1641,7 @@ export type Database = {
|
||||
name?: string
|
||||
postal_code?: string | null
|
||||
state_province?: string | null
|
||||
street_address?: string | null
|
||||
timezone?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
|
||||
64
src/lib/locationFormatter.ts
Normal file
64
src/lib/locationFormatter.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Location Formatting Utilities
|
||||
*
|
||||
* Centralized utilities for formatting location data consistently across the app.
|
||||
*/
|
||||
|
||||
export interface LocationData {
|
||||
street_address?: string | null;
|
||||
city?: string | null;
|
||||
state_province?: string | null;
|
||||
country?: string | null;
|
||||
postal_code?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format location for display
|
||||
* @param location - Location data object
|
||||
* @param includeStreet - Whether to include street address in the output
|
||||
* @returns Formatted location string or null if no location data
|
||||
*/
|
||||
export function formatLocationDisplay(
|
||||
location: LocationData | null | undefined,
|
||||
includeStreet: boolean = false
|
||||
): string | null {
|
||||
if (!location) return null;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (includeStreet && location.street_address) {
|
||||
parts.push(location.street_address);
|
||||
}
|
||||
|
||||
if (location.city) {
|
||||
parts.push(location.city);
|
||||
}
|
||||
|
||||
if (location.state_province) {
|
||||
parts.push(location.state_province);
|
||||
}
|
||||
|
||||
if (location.country) {
|
||||
parts.push(location.country);
|
||||
}
|
||||
|
||||
return parts.length > 0 ? parts.join(', ') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format full address including street
|
||||
* @param location - Location data object
|
||||
* @returns Formatted full address or null if no location data
|
||||
*/
|
||||
export function formatFullAddress(location: LocationData | null | undefined): string | null {
|
||||
return formatLocationDisplay(location, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format location without street address (city, state, country only)
|
||||
* @param location - Location data object
|
||||
* @returns Formatted location without street or null if no location data
|
||||
*/
|
||||
export function formatLocationShort(location: LocationData | null | undefined): string | null {
|
||||
return formatLocationDisplay(location, false);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { MapPin, Star, Clock, Phone, Globe, Calendar, ArrowLeft, Users, Zap, Camera, Castle, FerrisWheel, Waves, Tent, Plus } from 'lucide-react';
|
||||
import { formatLocationShort } from '@/lib/locationFormatter';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { ReviewsSection } from '@/components/reviews/ReviewsSection';
|
||||
import { RideCard } from '@/components/rides/RideCard';
|
||||
@@ -248,7 +249,7 @@ export default function ParkDetail() {
|
||||
</h1>
|
||||
{park.location && <div className="flex items-center text-white/90 text-lg">
|
||||
<MapPin className="w-5 h-5 mr-2" />
|
||||
{park.location.city && `${park.location.city}, `}{park.location.country}
|
||||
{formatLocationShort(park.location)}
|
||||
</div>}
|
||||
<div className="mt-3">
|
||||
<VersionIndicator
|
||||
@@ -470,11 +471,25 @@ export default function ParkDetail() {
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="font-medium text-foreground mb-1">Address:</div>
|
||||
<div className="space-y-1">
|
||||
{park.location.name && <div>{park.location.name}</div>}
|
||||
{park.location.city && <div>{park.location.city}</div>}
|
||||
{park.location.state_province && <div>{park.location.state_province}</div>}
|
||||
{park.location.postal_code && <div>{park.location.postal_code}</div>}
|
||||
<div>{park.location.country}</div>
|
||||
{/* Street Address on its own line if it exists */}
|
||||
{park.location.street_address && (
|
||||
<div>{park.location.street_address}</div>
|
||||
)}
|
||||
|
||||
{/* City, State Postal on same line */}
|
||||
{(park.location.city || park.location.state_province || park.location.postal_code) && (
|
||||
<div>
|
||||
{park.location.city}
|
||||
{park.location.city && park.location.state_province && ', '}
|
||||
{park.location.state_province}
|
||||
{park.location.postal_code && ` ${park.location.postal_code}`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Country on its own line */}
|
||||
{park.location.country && (
|
||||
<div>{park.location.country}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -57,11 +57,13 @@ export interface LocationInfoSettings {
|
||||
* Location data structure
|
||||
*/
|
||||
export interface LocationData {
|
||||
street_address?: string;
|
||||
country?: string;
|
||||
state_province?: string;
|
||||
city?: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
postal_code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +73,12 @@ export function isLocationData(data: unknown): data is LocationData {
|
||||
if (typeof data !== 'object' || data === null) return false;
|
||||
const loc = data as Record<string, unknown>;
|
||||
return (
|
||||
(loc.street_address === undefined || typeof loc.street_address === 'string') &&
|
||||
(loc.country === undefined || typeof loc.country === 'string') &&
|
||||
(loc.state_province === undefined || typeof loc.state_province === 'string') &&
|
||||
(loc.city === undefined || typeof loc.city === 'string') &&
|
||||
(loc.latitude === undefined || typeof loc.latitude === 'number') &&
|
||||
(loc.longitude === undefined || typeof loc.longitude === 'number')
|
||||
(loc.longitude === undefined || typeof loc.longitude === 'number') &&
|
||||
(loc.postal_code === undefined || typeof loc.postal_code === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Add street_address column to locations table
|
||||
ALTER TABLE locations
|
||||
ADD COLUMN street_address TEXT;
|
||||
|
||||
-- Add comment explaining the column
|
||||
COMMENT ON COLUMN locations.street_address IS 'Street address including house number and road name (e.g., "375 North Lagoon Drive")';
|
||||
|
||||
-- Add index for potential searches
|
||||
CREATE INDEX idx_locations_street_address ON locations(street_address);
|
||||
|
||||
-- Update existing records: extract from name if it looks like an address
|
||||
-- (This is best-effort cleanup for existing data)
|
||||
UPDATE locations
|
||||
SET street_address = CASE
|
||||
WHEN name ~ '^\d+\s+.*' THEN
|
||||
regexp_replace(name, ',.*$', '') -- Extract everything before first comma
|
||||
ELSE NULL
|
||||
END
|
||||
WHERE street_address IS NULL;
|
||||
Reference in New Issue
Block a user