Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SMA-107: the main page should use user s location #91

Merged
merged 11 commits into from
May 6, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ public class RequestEventDTO {
private List<String> sportsName = new ArrayList<>();
private double longitude;
private double latitude;
private String placeName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,39 +31,47 @@ public interface EventRepository extends JpaRepository<Event, Long> {
List<Event> findEventsByUser(
@Param("id") Long id, @Param("now") LocalDateTime now, Pageable pageable);

@Query("SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateStart > :now ORDER BY ep.event.dateEnd ASC")
@Query(
"SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateStart > :now ORDER BY ep.event.dateEnd ASC")
List<Event> findUpcomingEventsByUser(@Param("id") Long id, @Param("now") LocalDateTime now);




/**
* <p>Finds events near the user location, optionally filtered by sport names.</p>
* Finds events near the user location, optionally filtered by sport names.
*
* <p>This method uses a native SQL query with a Haversine distance calculation to find events ordered by their distance from the user's location.</p>
* <p>This method uses a native SQL query with a Haversine distance calculation to find events
* ordered by their distance from the user's location.
*
* @param userLongitude user's longitude coordinate
* @param userLatitude user's latitude coordinate
* @param sportNames optional, list of sport names to filter events by.
* If null, all events are returned.
* Sport names are not case sensitive.
* @param pageable contains the page and size
* @return list of events filtered by sport names if given, and order by distance from the user's given location
* @param userLatitude user's latitude coordinate
* @param sportNames optional, list of sport names to filter events by. If null, all events are
* returned. Sport names are not case-sensitive.
* @param placeName optional, substring to search name of the place by. If null all places are
* returned. It is not case-sensitive.
* @param now date and time to search events by. Only events starting after this date and time are
* returned.
* @param pageable contains the page and size
* @return list of events filtered by sport names if given, and order by distance from the user's
* given location
*/
@Query(nativeQuery = true, value =
"SELECT e.id, e.date_start, e.date_end, e.min_elo, e.max_elo, e.title, e.is_rank_updated, e.sport_id, e.place_id "
+ "FROM events e "
+ "JOIN sports s ON e.sport_id = s.id "
+ "JOIN places p ON e.place_id = p.id "
+ "WHERE (:sportNames IS NULL OR LOWER(s.name) IN(:sportNames)) "
+ "ORDER BY ( "
+ " 6371 * acos( " // Haversine distance calculation
+ " cos(radians(p.latitude)) * cos(radians(:latitude)) * "
+ " cos(radians(:longitude) - radians(p.longitude)) + "
+ " sin(radians(p.latitude)) * sin(radians(:latitude))) "
+ " ) ASC;")
List<Event> findNearbyEvents(@Param("longitude") final double userLongitude,
@Param("latitude") final double userLatitude,
@Param("sportNames") final List<String> sportNames,
Pageable pageable);
@Query(
nativeQuery = true,
value =
"SELECT e.id, e.date_start, e.date_end, e.min_elo, e.max_elo, e.title, e.is_rank_updated, e.sport_id, e.place_id "
+ "FROM events e "
+ "JOIN sports s ON e.sport_id = s.id "
+ "JOIN places p ON e.place_id = p.id "
+ "WHERE (LOWER(p.name) LIKE %:placeName%) AND (:sportNames IS NULL OR LOWER(s.name) IN(:sportNames)) AND (e.date_start > :now)"
+ "ORDER BY ( "
+ " 6371 * acos( " // Haversine distance calculation
+ " cos(radians(p.latitude)) * cos(radians(:latitude)) * "
+ " cos(radians(:longitude) - radians(p.longitude)) + "
+ " sin(radians(p.latitude)) * sin(radians(:latitude))) "
+ " ) ASC;")
List<Event> findNearbyEvents(
@Param("longitude") final double userLongitude,
@Param("latitude") final double userLatitude,
@Param("sportNames") final List<String> sportNames,
@Param("placeName") final String placeName,
@Param("now") final LocalDateTime now,
Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ public List<EventDTO> getNearbyEvents(RequestEventDTO requestEventDTO, final Pag
requestEventDTO.getLongitude(),
requestEventDTO.getLatitude(),
sportNamesWithLowerCase,
requestEventDTO.getPlaceName() == null
? ""
: requestEventDTO.getPlaceName().toLowerCase(),
LocalDateTime.now(),
pageable);

return events.stream().map(eventMapper::convertEventToEventDTO).collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function HostEventComponent() {
undefined,
undefined,
undefined,
undefined,
999,
)
if (!Array.isArray(response)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export class EventsControllerService {
* @param sportsName
* @param longitude
* @param latitude
* @param placeName
* @param page Zero-based page index (0..N)
* @param size The size of the page to be returned
* @param sort Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.
Expand All @@ -112,6 +113,7 @@ export class EventsControllerService {
sportsName?: Array<string>,
longitude?: number,
latitude?: number,
placeName?: string,
page?: number,
size: number = 20,
sort?: Array<string>,
Expand All @@ -123,6 +125,7 @@ export class EventsControllerService {
'sportsName': sportsName,
'longitude': longitude,
'latitude': latitude,
'placeName': placeName,
'page': page,
'size': size,
'sort': sort,
Expand Down
93 changes: 93 additions & 0 deletions frontend/sportsmatch-app/src/hooks/UseLocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useEffect, useState } from 'react'

// source https://gist.github.com/KristofferEriksson/ee5af0a52e1fea0acc028e9b10aa0969

interface LocationOptions {
enableHighAccuracy?: boolean
timeout?: number
maximumAge?: number
}

interface LocationState {
coords: {
latitude: number | null
longitude: number | null
accuracy: number | null
altitude: number | null
altitudeAccuracy: number | null
heading: number | null
speed: number | null
}
locatedAt: number | null
error: string | null
}

const useLocation = (options: LocationOptions = {}) => {
const [location, setLocation] = useState<LocationState>({
coords: {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
},
locatedAt: null,
error: null,
})

useEffect(() => {
// Ensuring this runs only in a client-side environment
if (typeof window === 'undefined' || !('geolocation' in navigator)) {
setLocation((prevState) => ({
...prevState,
error:
'Geolocation is not supported by your browser or not available in the current' +
' environment',
}))
return
}

const handleSuccess = (position: GeolocationPosition) => {
setLocation({
coords: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
altitudeAccuracy: position.coords.altitudeAccuracy,
heading: position.coords.heading,
speed: position.coords.speed,
},
locatedAt: position.timestamp,
error: null,
})
}

const handleError = (error: GeolocationPositionError) => {
setLocation((prevState) => ({
...prevState,
error: error.message,
}))
}

const geoOptions = {
enableHighAccuracy: options.enableHighAccuracy || false,
timeout: options.timeout || Infinity,
maximumAge: options.maximumAge || 0,
}

const watcher = navigator.geolocation.watchPosition(
handleSuccess,
handleError,
geoOptions,
)

return () => navigator.geolocation.clearWatch(watcher)
}, [options.enableHighAccuracy, options.timeout, options.maximumAge])

return location
}

export default useLocation
40 changes: 22 additions & 18 deletions frontend/sportsmatch-app/src/pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ApiError, EventDTO, EventsControllerService } from '../generated/api'
import useModal from '../hooks/UseModal'
import Modal from '../components/Modal'
import JoinEventComponent from '../components/JoinEventComponent'
import useLocation from '../hooks/UseLocation'

export default function MainPage() {
const [searchQuery, setSearchQuery] = useState<string>('')
Expand All @@ -21,6 +22,8 @@ export default function MainPage() {
const [page, setPage] = useState<number>(0)
const size = 3

const userCoords = useLocation().coords

// handle sports name selected from sportButtoncomponent
const handleSportSelectionChange = (selectedButtonSports: string[]) => {
setSelectedSports(selectedButtonSports)
Expand All @@ -45,8 +48,9 @@ export default function MainPage() {
try {
const response = await EventsControllerService.getNearbyEvents(
selectedSports,
0,
0,
userCoords.longitude || undefined,
userCoords.latitude || undefined,
searchQuery,
page,
size,
)
Expand All @@ -70,7 +74,13 @@ export default function MainPage() {
}
// call the method
fetchData()
}, [selectedSports, page])
}, [
selectedSports,
page,
searchQuery,
userCoords.latitude,
userCoords.longitude,
])

// handle join event pop up after cliking on the event
const handleEventSelection = (e: EventDTO) => {
Expand Down Expand Up @@ -140,21 +150,15 @@ export default function MainPage() {
{filteredEvent.length === 0 ? (
<LoadingSpinner />
) : (
filteredEvent
.filter((e) =>
e.placeDTO.name
.toLowerCase()
.includes(searchQuery.toLowerCase()),
)
.map((event, index) => (
<div
className="nearby-events"
key={index}
onClick={() => handleEventSelection(event)}
>
<SportEvent event={event} />
</div>
))
filteredEvent.map((event, index) => (
<div
className="nearby-events"
key={index}
onClick={() => handleEventSelection(event)}
>
<SportEvent event={event} />
</div>
))
)}
</div>
</div>
Expand Down
Loading