diff options
Diffstat (limited to 'maps-frontend/src')
-rw-r--r-- | maps-frontend/src/App.js | 11 | ||||
-rw-r--r-- | maps-frontend/src/components/Canvas.js | 557 | ||||
-rw-r--r-- | maps-frontend/src/components/CheckinList.js | 129 | ||||
-rw-r--r-- | maps-frontend/src/components/Hub.js (renamed from maps-frontend/src/components/UserCheckin.js) | 10 | ||||
-rw-r--r-- | maps-frontend/src/components/HubList.js | 44 | ||||
-rw-r--r-- | maps-frontend/src/components/TimeSelector.js (renamed from maps-frontend/src/components/Route.js) | 0 |
6 files changed, 55 insertions, 696 deletions
diff --git a/maps-frontend/src/App.js b/maps-frontend/src/App.js index 0e25f39..659b8f5 100644 --- a/maps-frontend/src/App.js +++ b/maps-frontend/src/App.js @@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react'; import TimeSelector from './components/TimeSelector.js'; import Canvas from './components/Canvas.js'; -import DataWidget from './components/DataWidget.js'; +import HubList from './components/HubList.js'; import Loading from './components/Loading.js'; // CSS import @@ -23,7 +23,7 @@ function App() { end: new Date(Date.now() - 12096e5) }); // State for visualization data - const [graphData, setGraphData] = useState({}); + const [data, setData] = useState([]); // States to control cursor. const [cursor, setCursor] = useState('grab'); // State for routing. @@ -45,7 +45,7 @@ function App() { }) .then(res => res.json()) .then(data => { - setGraphData(data); + setData(data); setHasLoaded(true); }) .catch(err => console.log(err)); @@ -75,9 +75,8 @@ function App() { <div className="Canvas-filler Canvas-filler-1"></div> <div className="Canvas-filler Canvas-filler-2"></div> <div className="Canvas-filler Canvas-filler-3"></div> - <DataWidget setHasLoaded={setHasLoaded}></DataWidget> - <Canvas setCoord={setCoord} route={route} selector={currentSelector} startLat={startLat} - startLon={startLon} endLat={endLat} endLon={endLon} setCursor={setCursor} + <HubList setHasLoaded={setHasLoaded} data={data}></HubList> + <Canvas setCursor={setCursor} hasLoaded={hasLoaded} setHasLoaded={setHasLoaded}></Canvas> {(!hasLoaded) ? <Loading></Loading> : <TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector>} diff --git a/maps-frontend/src/components/Canvas.js b/maps-frontend/src/components/Canvas.js index 166967e..9686f29 100644 --- a/maps-frontend/src/components/Canvas.js +++ b/maps-frontend/src/components/Canvas.js @@ -1,6 +1,5 @@ // JS module imports import { useEffect, useRef, useState } from "react"; -import axios from 'axios'; // CSS imports import '../css/Canvas.css'; @@ -10,562 +9,12 @@ import '../css/Canvas.css'; * @param {Object} props The props for the canvas. * @returns {import("react").HtmlHTMLAttributes} The canvas to be retured. */ -function Canvas(props) { +function Visualization(props) { // instance variables - const CANVAS_WIDTH = window.innerWidth; - const CANVAS_HEIGHT = window.innerHeight; - - const SCALE_RATIO = .035/.015; - const START_LON_SCALE = .005; - const MIN_LON_SCALE = .001; - const MAX_LON_SCALE = 1; - const SCALE_INTERVAL = .0007; - const TILE_SIZE = .1; - // Ways processing. Long object... - const A_TIER_MAX_SCALE = .175; - const B_TIER_MAX_SCALE = .08; - const WAYS_INFO = { - primary: { - color: 'darkorange', - weight: 2, - maxScale: MAX_LON_SCALE - }, - motorway: { - color: 'darkorange', - weight: 1.85, - maxScale: MAX_LON_SCALE - }, - secondary: { - color: 'orange', - weight: 1.75, - maxScale: MAX_LON_SCALE - }, - tertiary: { - color: 'orange', - weight: 1.75, - maxScale: MAX_LON_SCALE - }, - residential: { - color: 'white', - weight: 1.75, - maxScale: MAX_LON_SCALE - }, - primary_link: { - color: 'yellow', - weight: 1.6, - maxScale: A_TIER_MAX_SCALE - }, - motorway_link: { - color: 'yellow', - weight: 1.5, - maxScale: A_TIER_MAX_SCALE - }, - secondary_link: { - color: 'yellow', - weight: 1.5, - maxScale: A_TIER_MAX_SCALE - }, - tertiary_link: { - color: 'yellow', - weight: 1.3, - maxScale: A_TIER_MAX_SCALE - }, - footway: { - color: 'mediumorchid', - weight: 1.25, - maxScale: .25 - }, - cycleway: { - color: 'blue', - weight: 1.35, - maxScale: A_TIER_MAX_SCALE - }, - trunk: { - color: 'amber', - weight: 1.25, - maxScale: A_TIER_MAX_SCALE - }, - trunk_link: { - color: 'amber', - weight: 1, - maxScale: A_TIER_MAX_SCALE - }, - road: { - color: 'white', - weight: 1.25, - maxScale: A_TIER_MAX_SCALE - }, - living_street: { - color: 'white', - weight: 1.25, - maxScale: A_TIER_MAX_SCALE - }, - - service: { - color: 'red', - weight: 1, - maxScale: B_TIER_MAX_SCALE - }, - construction: { - color: 'red', - weight: 1.25, - maxScale: B_TIER_MAX_SCALE - }, - pedestrian: { - color: 'purple', - weight: 1, - maxScale: B_TIER_MAX_SCALE - }, - track: { - color: 'pink', - weight: 1.2, - maxScale: B_TIER_MAX_SCALE - }, - steps: { - color: 'purple', - weight: 1.1, - maxScale: B_TIER_MAX_SCALE - }, - path: { - color: 'purple', - weight: 1.1, - maxScale: B_TIER_MAX_SCALE - } - } - - const START_COORD = { - lat: 41.825, - lon: -71.406 - } - - // React ref to canvas - const ctxRef = useRef(); - - //Reach states - const [canvasWidth, setCanvasWidth] = useState(window.innerWidth); - const [canvasHeight, setCanvasHeight] = useState(window.innerHeight); - const [dragStart, setDragStart] = useState({}); - const [scale, setScale] = useState(START_LON_SCALE); - const [referencePoint, setReferencePoint] = useState(START_COORD); - const [tiles, setTiles] = useState({}); - const [requests, setRequests] = useState(new Set()); - const [mouseDown, setMouseDown] = useState(false); - const [canvasImg, setCanvasImg] = useState(); - - // The following four methods map the latitude, longitude to the x,y of the grid. - - /** - * The method mapping x to lon. - * @param {Number} x The x pixel difference to be converted. - * @returns {Number} The the corresponding longitude . - */ - const xToLon = x => { - return x*(scale/canvasWidth); - } - - /** - * The method mapping y to lat. - * @param {Number} y The y pixel difference to be converted. - * @returns {Number} The the corresponding latitude. - * It's reversed due to origin being at top left (not bottom left). - */ - const yToLat = y => { - const latScale = scale/SCALE_RATIO; - return -y*(latScale/canvasHeight); - } - - /** - * The method mapping lon to x. - * @param {Number} lon The longitude to be coverted to x pixel difference. - * @returns {Number} The the corresponding x pixel difference. - */ - const lonToX = lon => { - return lon/(scale/canvasWidth); - } - - - /** - * @param {Number} lat The latitude to be converted to y pixel difference. - * @returns {Number} The the corresponding y value. - * It's reversed due to the difference origins. - */ - const latToY = lat => { - const latScale = scale/SCALE_RATIO; - return -lat/(latScale/canvasHeight); - } - - /** - * Calculated the current grid's corners to use in the ways command. - * @returns {Object} The top left and bottom right coordinates in terms of the lat and lon. - */ - const getLatLonBounds = () => { - const lat1 = referencePoint.lat + TILE_SIZE; - const long1 = referencePoint.lon - TILE_SIZE; - const lat2 = referencePoint.lat + yToLat(canvasHeight) - TILE_SIZE; - const long2 = referencePoint.lon + xToLon(canvasWidth) + TILE_SIZE; - const topLeft = { - lat1: +(Math.round((lat1 * 1000)/10)/100).toFixed(1), - long1: +(Math.round((long1 * 1000)/10)/100).toFixed(1) - } - const botRight = { - lat2: +(Math.round((lat2 * 1000)/10)/100).toFixed(1), - long2: +(Math.round((long2 * 1000)/10)/100).toFixed(1) - } - - return {...topLeft, ...botRight}; - } - - /** - * Method the gets a tile and puts it into the cache. - * @param {Object} topLeft The topleft coordinate of the tile. - * @returns The ways within the tile. - */ - const getTile = async topLeft => { - // TODO: implement cache - - const toSend = { - lat1: topLeft[0], - long1: topLeft[1], - lat2: +(topLeft[0] - TILE_SIZE).toFixed(1), - long2: +(topLeft[1] + TILE_SIZE).toFixed(1) - } - //console.log(toSend); - - let config = { - headers: { - "Content-Type": "application/json", - 'Access-Control-Allow-Origin': '*', - } - }; - - //Install and import this! - //TODO: Fill in 1) location for request 2) your data 3) configuration - const res = await axios.post( - "http://localhost:4567/maps/ways", - JSON.stringify(toSend), - config - ); - - setTiles(prevState => ({ - ...prevState, - [`lat${topLeft[0]}lon${topLeft[1]}`] : res.data["ways"] - })); - - return res.data["ways"]; - }; - - /** - * Method that draws a single way onto the canvas. - * @param {import("react").HtmlHTMLAttributes} ctx The context of the canvas. - * @param {Object} wayInfo The information of the way as an object. - */ - const drawWay = (ctx, wayInfo) => { - // TODO: implement cache - ctx.beginPath(); - - const startY = latToY(wayInfo.startLat - referencePoint.lat); - const startX = lonToX(wayInfo.startLong - referencePoint.lon); - ctx.moveTo(startX, startY); - - const endY = latToY(wayInfo.destLat - referencePoint.lat); - const endX = lonToX(wayInfo.destLong - referencePoint.lon); - ctx.lineTo(endX, endY); - - if (wayInfo.type && WAYS_INFO[wayInfo.type]) { - if (WAYS_INFO[wayInfo.type].maxScale >= scale) { - ctx.strokeStyle = WAYS_INFO[wayInfo.type].color; - ctx.lineWidth = WAYS_INFO[wayInfo.type].weight; - ctx.stroke(); - } - } else { - if (B_TIER_MAX_SCALE >= scale) { - ctx.strokeStyle = 'grey'; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - - if (props.route.includes(wayInfo.id)) { - ctx.strokeStyle = 'lightgreen'; - ctx.lineWidth = 10; - ctx.stroke(); - } - } - - /** - * Handles the timeout from multiple quick requests to the api to ease the server. - * @param {CallableFunction} func The pending function call. - * @param {Number} delay The time in milliseconds to delay. - * @returns {CallableFunction} The function that runs the code after the delay. - */ - const debounce = (func, delay) => { - let debounceTimer - return () => { - const context = this - const args = arguments - clearTimeout(debounceTimer) - debounceTimer = setTimeout(() => func.apply(context, args), delay) - } - } - - /** - * Determines whether the a tile is loaded on the canvas. - * @param {Object} topLeft The topLeft point of the tile. - * @returns True if any section of it is loaded on the canvas. - */ - const tileOnScreen = topLeft => { - const tileLatBounds = [+(topLeft[0] - TILE_SIZE).toFixed(1), topLeft[0]]; - const tileLongBounds = [topLeft[1], +(topLeft[1] + TILE_SIZE).toFixed(1)]; - const screenLatBounds = [referencePoint.lat, referencePoint.lat + yToLat(canvasHeight)] - const screenLongBounds = [referencePoint.lon, referencePoint.lon + xToLon(canvasWidth)] - //by checking if the following for things are true, we can determine if the rectangles intersect - const tileLeftScreen = tileLatBounds[1] < screenLatBounds[1]; - const tileRightScreen = tileLatBounds[0] > screenLatBounds[0]; - const tileAboveScreen = tileLongBounds[0] > screenLongBounds[1]; - const tileBelowScreen = tileLongBounds[1] < screenLongBounds[0]; - return !( tileLeftScreen || tileRightScreen || tileAboveScreen || tileBelowScreen ); - } - - /** - * Method used to update the ways on the canvas. - * It draws in the new tiles and translates the old. - */ - const updateWaysOnCanvas = debounce(() => { - const ctx = ctxRef.current.getContext("2d"); - const bounds = getLatLonBounds(); - ctx.canvas.width = canvasWidth; - ctx.fillStyle = "#121212"; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - for (let i = +(bounds.lat2 + TILE_SIZE).toFixed(1); i <= bounds.lat1; i = +(i + TILE_SIZE).toFixed(1)) { - for (let j = bounds.long1; j < bounds.long2; j = +(j + TILE_SIZE).toFixed(1)) { - const topLeft = [i, j]; - const pointId = `lat${i}lon${j}`; - if (tiles[pointId] && tileOnScreen(topLeft)) { - //('accessing cache!'); - Object.keys(tiles[pointId]).forEach(key => { - drawWay(ctx, tiles[pointId][key]); - }); - } else { - if (!requests.has(pointId)) { - setRequests(requests.add(pointId)); - getTile(topLeft).then((data) => { - if (tileOnScreen(topLeft)) { - Object.keys(data).forEach(key => { - drawWay(ctx, data[key]); - }); - if (!props.hasLoaded) { - props.setHasLoaded(true); - } - } - }); - } - } - } - } - - // draws the routing points if they have been set - drawPoints(); - }, 0); - - /** - * Stores the starting point of the drah. - * @param {Object} e The event from the mouseDown callback. - */ - const storeDragStart = e => { - setMouseDown(true); - const canvas = ctxRef.current; - setCanvasImg(canvas); - - setDragStart({ - x: e.pageX - canvas.offsetLeft, - y: e.pageY - canvas.offsetTop - }); - - props.setCursor('grabbing'); - } - - /** - * Makes a call to the server to determine the nearest neighbor. - * @param {Number} x The x pixel value of the point inputted. - * @param {Number} y The y pixel value of the point inputted. - * @returns {Object} The lat and lon coordinates of the nearest. - */ - const getNearest = async (x, y) => { - const toSend = { - lat: yToLat(y) + referencePoint.lat, - long: xToLon(x) + referencePoint.lon - }; - - //console.log(toSend); - - let config = { - headers: { - "Content-Type": "application/json", - 'Access-Control-Allow-Origin': '*', - } - }; - - //Install and import this! - //TODO: Fill in 1) location for request 2) your data 3) configuration - const res = await axios.post( - "http://localhost:4567/maps/nearest", - JSON.stringify(toSend), - config - ) - - return res.data; - } - - /** - * This method draws the start and end points for the routing. - */ - const drawPoints = () => { - const ctx = ctxRef.current.getContext('2d'); - - const startY = latToY(props.startLat - referencePoint.lat); - const startX = lonToX(props.startLon - referencePoint.lon); - - const endY = latToY(props.endLat - referencePoint.lat); - const endX = lonToX(props.endLon - referencePoint.lon); - - const drawStart = () => { - ctx.beginPath(); - ctx.arc(startX, startY, 15, 0, Math.PI * 2, true); - - ctx.strokeStyle = 'pink'; - ctx.lineWidth = 10; - ctx.stroke(); - } - - const drawEnd = () => { - ctx.beginPath(); - ctx.arc(endX, endY, 15, 0, Math.PI * 2, true); - - ctx.strokeStyle = 'lightblue'; - ctx.lineWidth = 10; - ctx.stroke(); - } - - const startNotChosen = props.startLat === 0 && props.startLon === 0; - const endNotChosen = props.endLat === 0 && props.endLon === 0; - - if (!startNotChosen && !endNotChosen) { - drawStart(); - drawEnd(); - } else if (!startNotChosen) { - drawStart(); - } else if (!endNotChosen) { - drawEnd(); - } - } - - /** - * Drags the canvas and also responds to a solo click for inputting the route coordinate. - * @param {Object} e The event from the callback click handler. - */ - const dragCanvas = e => { - const canvas = ctxRef.current; - - const endX = e.pageX - canvas.offsetLeft; - const endY = e.pageY - canvas.offsetTop; - - const distX = endX - dragStart.x; - const distY = endY - dragStart.y; - - setDragStart({ - x: e.pageX - canvas.offsetLeft, - y: e.pageY - canvas.offsetTop - }); - - if ((distX === 0 || distY === 0) && props.selector !== '') { - getNearest(endX, endY).then((data) => { - props.setCoord(data); - }); - } else { - setReferencePoint((refPoint) => { - return { - lat: refPoint.lat - yToLat(distY), - lon: refPoint.lon - xToLon(distX) - } - }); - } - } - - /** - * Makes dragging smmother by calling the drag command frame by frame. - * @param {Object} e The event from the callback mouseMove hanlder. - */ - const smoothDrag = e => { - if (mouseDown) { - requestAnimationFrame(() => dragCanvas(e)); - props.setCursor('grabbing'); - } - } - - /** - * Releases the drag by setting the cursor back and setting the state. - * @param {Object} e The event from the callback mouseDown handler. - */ - const releaseDrag = e => { - props.setCursor('grab'); - setMouseDown(false); - } - - // The following are the react hooks that act as handlers and rerender on change. - - useEffect(() => { - updateWaysOnCanvas(); - }, [referencePoint, props.route]); - - useEffect(() => { - updateWaysOnCanvas(); - }, [props.selector]); - - useEffect(() => { - const timer = setTimeout(() => { - updateWaysOnCanvas(); - props.setCursor('grab'); - }, 750); - return () => clearTimeout(timer); - }, [scale]); - - useEffect(() => { - const canvas = ctxRef.current; - - canvas.width = canvasWidth; - canvas.height = canvasHeight; - - const zoomCanvas = e => { - e.preventDefault(); - - let direction = (e.deltaY > 0) ? 1 : -1; - props.setCursor(direction === 1 ? 'zoom-out' : 'zoom-in'); - setScale((s) => { - const newScale = s + direction*SCALE_INTERVAL; - return Math.min(MAX_LON_SCALE, Math.max(newScale, MIN_LON_SCALE)); - }); - } - canvas.addEventListener('wheel', zoomCanvas, false); - - window.addEventListener('resize', () => { - setCanvasWidth(window.innerWidth); - setCanvasHeight(window.innerHeight); - }); - }, []); - - useEffect(() => { - const canvas = ctxRef.current; - canvas.width = canvasWidth; - canvas.height = canvasHeight; - updateWaysOnCanvas(); - }, [canvasWidth, canvasHeight]) - - - return <canvas ref={ctxRef} onMouseDown={(e) => storeDragStart(e)} onClick={(e) => dragCanvas(e)} - onMouseMove={(e) => smoothDrag(e)} onMouseUp={() => releaseDrag()} className="Map-canvas"></canvas>; + return } -export default Canvas; +export default Visualization; diff --git a/maps-frontend/src/components/CheckinList.js b/maps-frontend/src/components/CheckinList.js deleted file mode 100644 index 189ed8b..0000000 --- a/maps-frontend/src/components/CheckinList.js +++ /dev/null @@ -1,129 +0,0 @@ -// React and component imports -import axios from 'axios'; -import { useEffect, useState } from "react"; -import UserCheckin from './UserCheckin.js'; - -// CSS import -import '../css/UserCheckin.css'; - -/** - * Component that build the checkin list and displays checkin info. - * @returns {import('react').HtmlHTMLAttributes} A div with the checkins - * in a vertical layout. - */ -function CheckinList() { - // States... - const [checkins, setCheckins] = useState({}); - const [checkinItems, setCheckinItems] = useState([]); - const [showAllCheckins, setShowAllCheckins] = useState(true); - const [userInfo, setUserInfo] = useState([]); - const [userCoords, setUserCoords] = useState([]); - - /** - * Makes a call to the server to get the newer checkin data. - */ - function getNewCheckins() { - let config = { - headers: { - "Content-Type": "application/json", - 'Access-Control-Allow-Origin': '*', - } - } - - axios.get( - "http://localhost:4567/maps/checkins", - config - ).then((res) => { - setCheckins((prev) => { - return Object.assign(prev, res.data['checkins']); - }); - }); - updateCheckinItems(); - } - - /** - * Gives all the checkins for a specific user. - * @param {String} id The user id to get the checkins for. - * @param {String} name The name to get the checkins for. - */ - function getUserCheckins(id, name) { - const toSend = { - userid : id, - }; - let config = { - headers: { - "Content-Type": "application/json", - 'Access-Control-Allow-Origin': '*', - } - } - - axios.post( - "http://localhost:4567/maps/checkins", - JSON.stringify(toSend), - config - ) - .then(res => { - setUserCoords(res.data['checkins']); - }) - setUserInfo([name, id]); - setShowAllCheckins(false); - } - - /** - * Generates the user checkins html elements. - * @returns A div holding a list of all checkins for a user. - */ - function getUserCheckinElements() { - const coords = userCoords.map((coord, index) => - <li key={index}> - <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span> - </li> - ); - return ( - <div className="Chosen-user" hidden={showAllCheckins}> - <h2> - <span onClick={() => setShowAllCheckins(true)}><img className="Img-btn" src="/round_arrow_back_white_18dp.png" alt="image"/></span> - {userInfo[0]} : {userInfo[1]} - </h2> - <div className='Coord-ex'>{"(lat , lon)"}</div> - <ol className='User-checkin-list'> - {coords} - </ol> - </div> - ); - } - - /** - * Loads new the checkins into the current cache/map of checkins. - */ - const updateCheckinItems = () => { - let tempCheckinItems = []; - const sortedCheckinEntries = Object.entries(checkins).sort((a, b) => b[0] - a[0]); - for (const [key, value] of sortedCheckinEntries) { - tempCheckinItems.push( - <UserCheckin key={key} value={value} getUserCheckins={getUserCheckins}></UserCheckin> - ); - } - setCheckinItems(tempCheckinItems); - } - - // React hook that queries the checkin database every 5 seconds. - useEffect(() => { - const interval = setInterval(() => { - getNewCheckins(); - }, 5000); - return () => clearInterval(interval); - }, []); - - return ( - <div className="User-checkin"> - <div className="Checkins"> - <h2>Checkins</h2> - <ul className='Checkin-list'>{checkinItems}</ul> - </div> - {getUserCheckinElements()} - </div> - ); -} - -export default CheckinList;
\ No newline at end of file diff --git a/maps-frontend/src/components/UserCheckin.js b/maps-frontend/src/components/Hub.js index f85994b..cd160ea 100644 --- a/maps-frontend/src/components/UserCheckin.js +++ b/maps-frontend/src/components/Hub.js @@ -9,7 +9,7 @@ import '../css/UserCheckin.css'; * @param {Object} props The props of the component. * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info. */ -function UserCheckin(props) { +function Hub(props) { // State - toggled const [isToggled, setIsToggled] = useState(false); @@ -21,13 +21,9 @@ function UserCheckin(props) { <img className="Img-btn" hidden={!isToggled} onClick={() => setIsToggled((toggle) => !toggle)} src="/round_expand_less_white_18dp.png" alt="image"/> </div> <div hidden={!isToggled}> - <ul> - <li>Time: {new Date(props.value.ts * 1000).toLocaleTimeString("en-US")}</li> - <li>Lat: {props.value.lat}</li> - <li>Lon: {props.value.lon}</li> - </ul> + Nothin.... </div> </li>); } -export default UserCheckin;
\ No newline at end of file +export default Hub;
\ No newline at end of file diff --git a/maps-frontend/src/components/HubList.js b/maps-frontend/src/components/HubList.js new file mode 100644 index 0000000..3bbcca6 --- /dev/null +++ b/maps-frontend/src/components/HubList.js @@ -0,0 +1,44 @@ +// React and component imports +import { useEffect, useState } from "react"; +import Hub from "./Hub.js"; + +// CSS import +import '../css/UserCheckin.css'; + +/** + * Component that build the checkin list and displays checkin info. + * @returns {import('react').HtmlHTMLAttributes} A div with the hubs + * in a vertical layout. + */ +function HubList(props) { + const [hubItems, setHubItems] = useState([]); + + /** + * Loads new the checkins into the current cache/map of hubs. + */ + const updateHubItems = () => { + // sort and create the elemnts + let hubs = []; + const sorted = props.data.sort((a, b) => b.suspicionScore - a.suspicionScore); + console.log(sorted); + sorted.forEach(hub => hubs.push( + <Hub name={hub.name} value={hub.suspicionScore}></Hub> + )); + + setHubItems(hubs); + } + + // React hook that updates when the hubs are recalculated + useEffect(() => updateHubItems(), [props.data]); + + return ( + <div className="User-checkin"> + <div className="Checkins"> + <h2>Individual Suspicion</h2> + <ul className='Checkin-list'>{hubItems}</ul> + </div> + </div> + ); +} + +export default HubList
\ No newline at end of file diff --git a/maps-frontend/src/components/Route.js b/maps-frontend/src/components/TimeSelector.js index 2a26fd9..2a26fd9 100644 --- a/maps-frontend/src/components/Route.js +++ b/maps-frontend/src/components/TimeSelector.js |