fix(geocoding): fix query param string parsing, location field mismatch, and distance_km normalization

- controller: @Query() params arrive as strings in NestJS - now explicitly
  parseFloat() all numeric params (lat, lng, radius) before passing to service.
  Also validates against NaN before hitting PostGIS.
- controller: reverse geocode now validates and throws 400 on invalid lat/lng.
- service: searchPlaces now normalizes all results to include:
    - location: { lat, lng } nested object (frontend was crashing on place.location.lat)
    - distance_km: pre-computed string field (frontend was reading undefined distance_km)
    - latitude/longitude as actual floats (not decimal strings from DB)
- frontend (App.tsx): fixed map.flyTo() to read place.latitude/place.longitude
  instead of the non-existent place.location.lat/lng.
- frontend (App.tsx): fixed search result click handler same way.
- frontend (App.tsx): fixed distance display to compute from res.distance (meters).
- entity: added missing source column to BasePlace entity.
This commit is contained in:
Hamza-Ayed
2026-03-29 23:30:12 +03:00
parent ca043b67a8
commit c136cee04f
4 changed files with 84 additions and 31 deletions
+8 -3
View File
@@ -65,7 +65,12 @@ function App() {
if (data.results && data.results.length > 0 && map) {
const place = data.results[0];
map.flyTo({ center: [place.location.lng, place.location.lat], zoom: 15 });
// API returns flat latitude/longitude fields (not a nested location object)
const placeLng = parseFloat(place.longitude);
const placeLat = parseFloat(place.latitude);
if (!isNaN(placeLat) && !isNaN(placeLng)) {
map.flyTo({ center: [placeLng, placeLat], zoom: 15 });
}
}
} catch (e) {
console.error("Search failed", e);
@@ -208,14 +213,14 @@ function App() {
{searchResults.map((res) => (
<div
key={res.id}
onClick={() => map?.flyTo({ center: [res.location.lng, res.location.lat], zoom: 16 })}
onClick={() => { const rLng = parseFloat(res.longitude); const rLat = parseFloat(res.latitude); if (!isNaN(rLat) && !isNaN(rLng)) map?.flyTo({ center: [rLng, rLat], zoom: 16 }); }}
style={{ padding: '8px', borderBottom: '1px solid var(--glass-border)', cursor: 'pointer', fontSize: '0.85rem' }}
className="search-result-item"
>
<div style={{ fontWeight: 600 }}>{res.name_ar || res.name}</div>
{res.address && <div style={{ fontSize: '0.75rem', opacity: 0.7 }}>{res.address}</div>}
<div style={{ fontSize: '0.7rem', color: '#3b82f6', marginTop: '2px' }}>
{res.distance_km} km away | {res.source.replace('_', ' ')}
{res.distance ? (Number(res.distance) / 1000).toFixed(1) + ' km away' : ''} | {(res.source || '').replace('_', ' ')}
</div>
</div>
))}