11// FindHelpResults.tsx
22'use client' ;
33
4- import { useMemo , useState } from 'react' ;
4+ import { useMemo , useState , useCallback } from 'react' ;
55import { useLocation } from '@/contexts/LocationContext' ;
66import ServiceCard from './ServiceCard' ;
77import FilterPanel from './FilterPanel' ;
@@ -12,6 +12,26 @@ interface Props {
1212 providers : ServiceProvider [ ] ;
1313}
1414
15+ interface FlattenedServiceWithExtras extends FlattenedService {
16+ organisation : string ;
17+ organisationSlug : string ;
18+ lat : number ;
19+ lng : number ;
20+ distance ?: number ;
21+ }
22+
23+ interface MapMarker {
24+ id : string ;
25+ lat : number ;
26+ lng : number ;
27+ title : string ;
28+ organisation ?: string ;
29+ link ?: string ;
30+ serviceName ?: string ;
31+ distanceKm ?: number ;
32+ icon ?: string ;
33+ }
34+
1535export default function FindHelpResults ( { providers } : Props ) {
1636 const { location } = useLocation ( ) ;
1737 const [ showMap , setShowMap ] = useState ( false ) ;
@@ -20,46 +40,70 @@ export default function FindHelpResults({ providers }: Props) {
2040 const [ selectedCategory , setSelectedCategory ] = useState ( '' ) ;
2141 const [ selectedSubCategory , setSelectedSubCategory ] = useState ( '' ) ;
2242
23- const flattenedServices : FlattenedService [ ] = useMemo ( ( ) => {
43+ const getDistanceFromLatLonInKm = useCallback ( ( lat1 : number , lon1 : number , lat2 : number , lon2 : number ) => {
44+ const R = 6371 ;
45+ const dLat = deg2rad ( lat2 - lat1 ) ;
46+ const dLon = deg2rad ( lon2 - lon1 ) ;
47+ const a =
48+ Math . sin ( dLat / 2 ) * Math . sin ( dLat / 2 ) +
49+ Math . cos ( deg2rad ( lat1 ) ) * Math . cos ( deg2rad ( lat2 ) ) *
50+ Math . sin ( dLon / 2 ) * Math . sin ( dLon / 2 ) ;
51+ const c = 2 * Math . atan2 ( Math . sqrt ( a ) , Math . sqrt ( 1 - a ) ) ;
52+ return R * c ;
53+ } , [ ] ) ;
54+
55+ function deg2rad ( deg : number ) {
56+ return deg * ( Math . PI / 180 ) ;
57+ }
58+
59+ const flattenedServices : FlattenedServiceWithExtras [ ] = useMemo ( ( ) => {
2460 if ( ! providers || providers . length === 0 ) return [ ] ;
2561
2662 return providers . flatMap ( ( org ) => {
2763 if ( ! org . services || ! Array . isArray ( org . services ) ) return [ ] ;
2864
29- return org . services . map ( ( service ) => {
30- if ( ! service . latitude || ! service . longitude ) return null ;
31- return {
65+ return org . services . flatMap ( ( service ) => {
66+ if (
67+ typeof service . latitude !== 'number' ||
68+ typeof service . longitude !== 'number'
69+ ) {
70+ return [ ] ;
71+ }
72+
73+ return [ {
3274 id : service . id ,
3375 name : service . name ,
3476 description : service . description ,
3577 category : service . category ,
3678 subCategory : service . subCategory ,
37- organisation : org . name ,
38- organisationSlug : org . slug ,
3979 lat : service . latitude ,
4080 lng : service . longitude ,
81+ latitude : service . latitude ,
82+ longitude : service . longitude ,
83+ organisation : org . name ,
84+ organisationSlug : org . slug ,
4185 clientGroups : service . clientGroups || [ ] ,
4286 openTimes : service . openTimes || [ ] ,
43- } ;
44- } ) . filter ( Boolean ) as FlattenedService [ ] ;
87+ } ] ;
88+ } ) ;
4589 } ) ;
4690 } , [ providers ] ) ;
4791
4892 const filteredServicesWithDistance = useMemo ( ( ) => {
49- if ( ! location ) return [ ] ;
93+ if ( ! location || location . lat == null || location . lng == null ) return [ ] ;
5094
5195 return flattenedServices
52- . map ( ( service ) => {
53- const distance = getDistanceFromLatLonInKm ( location . lat , location . lng , service . lat , service . lng ) ;
54- return { ... service , distance } ;
55- } )
96+ . map ( ( service ) => ( {
97+ ... service ,
98+ distance : getDistanceFromLatLonInKm ( location . lat ! , location . lng ! , service . lat , service . lng ) ,
99+ } ) )
56100 . filter ( ( service ) => {
57- const distanceMatch = service . distance <= radius ;
101+ const distanceMatch = service . distance ! <= radius ;
58102 const categoryMatch = selectedCategory ? service . category === selectedCategory : true ;
59103 const subCategoryMatch = selectedSubCategory ? service . subCategory === selectedSubCategory : true ;
60104 return distanceMatch && categoryMatch && subCategoryMatch ;
61105 } ) ;
62- } , [ flattenedServices , location , radius , selectedCategory , selectedSubCategory ] ) ;
106+ } , [ flattenedServices , location , radius , selectedCategory , selectedSubCategory , getDistanceFromLatLonInKm ] ) ;
63107
64108 const sortedServices = useMemo ( ( ) => {
65109 if ( sortOrder === 'alpha' ) {
@@ -68,24 +112,8 @@ export default function FindHelpResults({ providers }: Props) {
68112 return [ ...filteredServicesWithDistance ] . sort ( ( a , b ) => ( a . distance ?? 0 ) - ( b . distance ?? 0 ) ) ;
69113 } , [ filteredServicesWithDistance , sortOrder ] ) ;
70114
71- function getDistanceFromLatLonInKm ( lat1 : number , lon1 : number , lat2 : number , lon2 : number ) {
72- const R = 6371 ;
73- const dLat = deg2rad ( lat2 - lat1 ) ;
74- const dLon = deg2rad ( lon2 - lon1 ) ;
75- const a =
76- Math . sin ( dLat / 2 ) * Math . sin ( dLat / 2 ) +
77- Math . cos ( deg2rad ( lat1 ) ) * Math . cos ( deg2rad ( lat2 ) ) *
78- Math . sin ( dLon / 2 ) * Math . sin ( dLon / 2 ) ;
79- const c = 2 * Math . atan2 ( Math . sqrt ( a ) , Math . sqrt ( 1 - a ) ) ;
80- return R * c ;
81- }
82-
83- function deg2rad ( deg : number ) {
84- return deg * ( Math . PI / 180 ) ;
85- }
86-
87- const combinedMarkers = useMemo ( ( ) => {
88- const markers = filteredServicesWithDistance . map ( ( s ) => ( {
115+ const combinedMarkers : MapMarker [ ] = useMemo ( ( ) => {
116+ const markers : MapMarker [ ] = filteredServicesWithDistance . map ( ( s ) => ( {
89117 id : s . id ,
90118 lat : s . lat ,
91119 lng : s . lng ,
@@ -95,7 +123,8 @@ export default function FindHelpResults({ providers }: Props) {
95123 serviceName : s . name ,
96124 distanceKm : s . distance ,
97125 } ) ) ;
98- if ( location ) {
126+
127+ if ( location && location . lat != null && location . lng != null ) {
99128 markers . unshift ( {
100129 id : 'user-location' ,
101130 lat : location . lat ,
@@ -104,6 +133,7 @@ export default function FindHelpResults({ providers }: Props) {
104133 icon : 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png' ,
105134 } ) ;
106135 }
136+
107137 return markers ;
108138 } , [ filteredServicesWithDistance , location ] ) ;
109139
@@ -155,17 +185,15 @@ export default function FindHelpResults({ providers }: Props) {
155185
156186 { showMap && (
157187 < div className = "block lg:hidden w-full mb-4" data-testid = "map-container" >
158- < GoogleMap center = { location } markers = { combinedMarkers } />
188+ < GoogleMap center = { ( location && location . lat != null && location . lng != null ) ? { lat : location . lat , lng : location . lng } : null } markers = { combinedMarkers } />
159189 </ div >
160190 ) }
161191
162192 < div className = "flex-1 overflow-y-visible lg:overflow-y-auto pr-2" >
163193 { sortedServices . length === 0 ? (
164194 < p > No services found within { radius } km of your location.</ p >
165195 ) : (
166- < div
167- className = { `gap-4 ${ showMap ? 'flex flex-col' : 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3' } ` }
168- >
196+ < div className = { `gap-4 ${ showMap ? 'flex flex-col' : 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3' } ` } >
169197 { sortedServices . map ( ( service ) => (
170198 < div
171199 key = { service . id }
@@ -186,7 +214,14 @@ export default function FindHelpResults({ providers }: Props) {
186214
187215 { showMap && (
188216 < div className = "hidden lg:block w-full lg:w-1/2 mt-8 lg:mt-0 lg:sticky lg:top-[6.5rem] min-h-[400px]" data-testid = "map-container" >
189- < GoogleMap center = { location } markers = { combinedMarkers } />
217+ < GoogleMap
218+ center = {
219+ location && location . lat != null && location . lng != null
220+ ? { lat : location . lat , lng : location . lng }
221+ : null
222+ }
223+ markers = { combinedMarkers }
224+ />
190225 </ div >
191226 ) }
192227 </ section >
0 commit comments