diff --git a/src/components/Event/Event.tsx b/src/components/Event/Event.tsx index 38e4553..99d11db 100644 --- a/src/components/Event/Event.tsx +++ b/src/components/Event/Event.tsx @@ -24,6 +24,8 @@ function GroupName({ group }: { group: IEvent['group'] }) { return {group.name}; } +const NOT_LOADED = {}; + export default function Event({ id, initial, @@ -32,10 +34,11 @@ export default function Event({ initial?: IEvent; }) { const [event, setEvent] = useState(initial || null); - const [placeId, setPlaceId] = useState(null); + const [myPlaceId, setPlaceId] = useState(null); const [interested, setInterested] = useState(false); const [updating, setUpdating] = useState(false); - const [signups, setSignups] = useState(null); + const [signups, setSignups] = + useState>(NOT_LOADED); const [hasCarpool, setHasCarpool] = useState(false); const toggleInterested = useCallback(() => setInterested((i) => !i), []); const toggleInterestedThrottled = useThrottle(toggleInterested, 500); @@ -65,7 +68,7 @@ export default function Event({ useEffect(refresh, [refresh]); useEffect(() => { - if (signups === null) { + if (signups === NOT_LOADED) { return; } @@ -80,16 +83,16 @@ export default function Event({ }; const addOrUpdateSignup = () => { - if (!prev.interested || prev.placeId !== placeId) { + if (!prev.interested || prev.placeId !== myPlaceId) { console.log('Adding or updating signup.', prev, { interested, - placeId, + placeId: myPlaceId, eventId: id, signups, }); - addOrUpdateEventSignup(id, placeId) + addOrUpdateEventSignup(id, myPlaceId) .then(() => { - prev.placeId = placeId; + prev.placeId = myPlaceId; prev.eventId = id; prev.interested = true; }) @@ -104,7 +107,7 @@ export default function Event({ } else { addOrUpdateSignup(); } - }, [id, interested, placeId, signups, updating]); + }, [id, interested, myPlaceId, signups, updating]); useEffect(() => { getEventSignups(id) @@ -118,7 +121,11 @@ export default function Event({ existingSignup.current.interested = true; } } - setSignups(signups); + const signupMap: Record = {}; + for (let signup of signups) { + signupMap[signup.user.id] = signup; + } + setSignups(signupMap); }) .catch(console.error); }, [id, me?.id]); @@ -141,6 +148,7 @@ export default function Event({ signups, hasCarpool, setHasCarpool, + myPlaceId, }} > @@ -166,14 +174,12 @@ export default function Event({ onSelected={(_address, placeID) => { setPlaceId(placeID); }} - style={placeId != null ? { border: '2px solid ' + green } : {}} - placeId={placeId} + style={myPlaceId != null ? { border: '2px solid ' + green } : {}} + placeId={myPlaceId} />
- {signups !== null && ( - - )} + {signups !== null && } )}
diff --git a/src/components/Event/EventCarpools.tsx b/src/components/Event/EventCarpools.tsx index a08e170..009b313 100644 --- a/src/components/Event/EventCarpools.tsx +++ b/src/components/Event/EventCarpools.tsx @@ -4,6 +4,7 @@ import EmojiPeopleIcon from '@material-ui/icons/EmojiPeople'; import { useEffect } from 'react'; import { useCallback, useContext, useMemo, useState } from 'react'; import { lightgrey } from '../../lib/colors'; +import furthestPoint from '../../lib/furthestpoint'; import { useCancelCarpoolRequest, useInvitationState, @@ -15,6 +16,8 @@ import { IEvent } from '../types'; import UIButton from '../UI/UIButton'; import UILink from '../UI/UILink'; import EventContext from './EventContext'; +import estimateOptimalPath, { Location } from '../../lib/estimateoptimalpath'; +import usePlace from '../usePlace'; function CarpoolRow({ carpool, @@ -29,6 +32,8 @@ function CarpoolRow({ const cancelCarpoolRequest = useCancelCarpoolRequest(); const sendCarpoolRequest = useSendCarpoolRequest(); + const { signups } = useContext(EventContext); + const sendButton = useCallback(() => { sendCarpoolRequest(carpool.id); }, [sendCarpoolRequest, carpool.id]); @@ -37,6 +42,61 @@ function CarpoolRow({ cancelCarpoolRequest(carpool.id); }, [cancelCarpoolRequest, carpool.id]); + const { + event: { latitude, longitude }, + myPlaceId, + } = useContext(EventContext); + + const myLocation = usePlace(myPlaceId); + + const extraDistance = useMemo(() => { + if (!myLocation) { + console.log('!myLocation'); + return null; + } + + // Calculates the minimum distance if I'm in the carpool + // and subtracts the distance if I'm not in the carpool + + const memberLocations = carpool.members + .map((member) => { + const signup = signups[member.id]; + if (!signup) { + return null; + } + return { + latitude: signup.latitude, + longitude: signup.longitude, + }; + }) + .filter(Boolean) as Location[]; + + const { maxLocation: driverLocation } = furthestPoint(memberLocations, { + latitude, + longitude, + }); + + const passengerLocations = memberLocations.filter( + (location) => location !== driverLocation + ); + + const { distance: distanceInCarpool } = estimateOptimalPath({ + from: driverLocation, + waypoints: [...passengerLocations, myLocation], + to: { latitude, longitude }, + }); + + const { distance: distanceNotInCarpool } = estimateOptimalPath({ + from: driverLocation, + waypoints: passengerLocations, + to: { latitude, longitude }, + }); + + return distanceInCarpool - distanceNotInCarpool; + }, [carpool.members, latitude, longitude, myLocation, signups]); + + console.log(carpool.id, extraDistance); + return (
- {carpool.name} + {carpool.name} {extraDistance !== null && '+ ' + extraDistance}

@@ -132,7 +192,7 @@ export default function Carpools() { const tentativeInviteNames = useMemo(() => { if (!signups) return []; const names = tentativeInvites.map((id) => { - const signup = signups.find((s) => s.user.id === id); + const signup = signups[id]; return signup?.user.name; }); const nonNull = names.filter((n) => n != null); diff --git a/src/components/Event/EventContext.tsx b/src/components/Event/EventContext.tsx index 1da0402..c946df3 100644 --- a/src/components/Event/EventContext.tsx +++ b/src/components/Event/EventContext.tsx @@ -8,7 +8,7 @@ const EventContext = createContext({ }, event: null! as IEvent, default: true, - signups: null as IEventSignup[] | null, + signups: {} as Record, addTentativeInvite: (id: number) => { console.error('not implemented: addTentativeInvite'); }, @@ -20,6 +20,7 @@ const EventContext = createContext({ setHasCarpool: (has: boolean) => { console.error('not implemented: setHasCarpool'); }, + myPlaceId: null as string | null, }); export default EventContext; diff --git a/src/components/Event/EventSignups.tsx b/src/components/Event/EventSignups.tsx index f847ada..a7ec3b6 100644 --- a/src/components/Event/EventSignups.tsx +++ b/src/components/Event/EventSignups.tsx @@ -110,13 +110,11 @@ function EventSignup({ } export default function EventSignups({ - signups, myPlaceId, }: { - signups: IEventSignup[]; myPlaceId: string | null; }) { - const { event } = useContext(EventContext); + const { event, signups } = useContext(EventContext); const carpools = event.carpools; const myPlaceDetails = usePlace(myPlaceId); @@ -125,7 +123,9 @@ export default function EventSignups({ const members = carpools.map((c) => c.members); const allMembers = members.reduce((a, b) => a.concat(b), []); const allMembersIds = allMembers.map((m) => m.id); - return signups.filter((s) => !allMembersIds.includes(s.user.id)); + return Object.keys(signups) + .filter((id) => !allMembersIds.includes(+id)) + .map((id) => signups[id]); }, [signups, carpools]); return ( diff --git a/src/lib/estimateoptimalpath.ts b/src/lib/estimateoptimalpath.ts index b7b7e9d..c3f4ec1 100644 --- a/src/lib/estimateoptimalpath.ts +++ b/src/lib/estimateoptimalpath.ts @@ -1,4 +1,4 @@ -import latlongdist from './latlongdist'; +import getDistance from './getdistance'; export type Location = { latitude: number; @@ -11,21 +11,6 @@ export type Path = { waypoints: Location[]; }; -function getDistance(...locations: Location[]): number { - let distance = 0; - for (let i = 0; i < locations.length - 1; i++) { - const from = locations[i]; - const to = locations[i + 1]; - distance += latlongdist( - from.latitude, - from.longitude, - to.latitude, - to.longitude - ); - } - return distance; -} - export default function estimateOptimalPath(path: Path): { path: Path; distance: number; diff --git a/src/lib/furthestpoint.ts b/src/lib/furthestpoint.ts new file mode 100644 index 0000000..e97ab8b --- /dev/null +++ b/src/lib/furthestpoint.ts @@ -0,0 +1,20 @@ +import { Location } from './estimateoptimalpath'; +import getDistance from './getdistance'; + +export default function furthestPoint( + locations: Location[], + destination: Location +) { + let maxDistance = 0; + let maxLocation = { latitude: 0, longitude: 0 }; + for (let i = 0; i < locations.length; i++) { + let distance = getDistance(locations[i], destination); + + if (distance > maxDistance) { + maxDistance = distance; + maxLocation = locations[i]; + } + } + + return { maxDistance, maxLocation }; +} diff --git a/src/lib/getPlaceDetails.ts b/src/lib/getPlaceDetails.ts index 9bb630a..d6275e2 100644 --- a/src/lib/getPlaceDetails.ts +++ b/src/lib/getPlaceDetails.ts @@ -8,18 +8,26 @@ export type PlaceDetails = { longitude: number; }; +const cache = new Map(); + export default async function getPlaceDetails(placeId: string) { + if (cache.has(placeId)) { + return cache.get(placeId)!; + } + return new Promise((resolve, reject) => { places.getDetails( { placeId, fields: ['name', 'formatted_address', 'geometry'] }, (result, status) => { if (result || status === 'OK') { - resolve({ + const place = { name: result.name, formattedAddress: result.formatted_address!, latitude: result.geometry!.location.lat(), longitude: result.geometry!.location.lng(), - }); + }; + cache.set(placeId, place); + resolve(place); } else { reject(new Error('Unexpected Places status ' + status)); } diff --git a/src/lib/getdistance.ts b/src/lib/getdistance.ts new file mode 100644 index 0000000..d051038 --- /dev/null +++ b/src/lib/getdistance.ts @@ -0,0 +1,17 @@ +import { Location } from './estimateoptimalpath'; +import latlongdist from './latlongdist'; + +export default function getDistance(...locations: Location[]): number { + let distance = 0; + for (let i = 0; i < locations.length - 1; i++) { + const from = locations[i]; + const to = locations[i + 1]; + distance += latlongdist( + from.latitude, + from.longitude, + to.latitude, + to.longitude + ); + } + return distance; +}