diff options
author | Julia McCauley <skurvyj@gmail.com> | 2021-04-20 11:29:24 -0400 |
---|---|---|
committer | Julia McCauley <skurvyj@gmail.com> | 2021-04-20 11:29:24 -0400 |
commit | 0f86bfc8182dd6b8559f352ebad7ff5ea8db8c73 (patch) | |
tree | 385c86356e7f95058c72aaa1f1fbbe6abe492c6d /react-frontend/src/components | |
parent | 8535db483bd2a153013976a0ad540d00707405ba (diff) | |
parent | 3910a31e5418343e427305bb0b77cf5ec3e2dfbf (diff) |
Merge branch 'master' of github.com:cs0320-2021/term-project-cohwille-jmccaul3-mfoiani-rhunt2
# Conflicts:
# react-frontend/src/components/Visualization.js
Diffstat (limited to 'react-frontend/src/components')
-rw-r--r-- | react-frontend/src/components/Hub.js | 17 | ||||
-rw-r--r-- | react-frontend/src/components/HubList.js | 89 | ||||
-rw-r--r-- | react-frontend/src/components/HubWidget.js | 84 | ||||
-rw-r--r-- | react-frontend/src/components/InvestorInfo.js | 181 | ||||
-rw-r--r-- | react-frontend/src/components/TimeSelector.js | 3 | ||||
-rw-r--r-- | react-frontend/src/components/Visualization.js | 34 | ||||
-rw-r--r-- | react-frontend/src/components/WatchDogs.js | 16 |
7 files changed, 316 insertions, 108 deletions
diff --git a/react-frontend/src/components/Hub.js b/react-frontend/src/components/Hub.js index 8a3ac1c..1906684 100644 --- a/react-frontend/src/components/Hub.js +++ b/react-frontend/src/components/Hub.js @@ -10,12 +10,25 @@ import '../css/UserCheckin.css'; * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info. */ function Hub(props) { - // State - toggled + const LEN_NAME = 15; + + const [isHover, setIsHover] = useState(false); + + const formatName = name => { + if (name.length >= LEN_NAME) { + return props.name.substring(0, LEN_NAME - 3) + '...'; + } + return props.name; + } return ( <li className='Checkin'> <div className="Img-flex"> - <span className="Clickable-name" onClick= {() => console.log(props.id)}>{props.name}</span> + <span + className="Clickable-name" + onMouseOver = {() => setIsHover(true)} + onMouseLeave = {() => setIsHover(false)} + onClick = {() => props.setSelectedId(props.id)}>{isHover || props.searching ? props.name : formatName(props.name)}</span> <span>{props.value.toFixed(3)}</span> </div> </li>); diff --git a/react-frontend/src/components/HubList.js b/react-frontend/src/components/HubList.js index c9a5156..383d570 100644 --- a/react-frontend/src/components/HubList.js +++ b/react-frontend/src/components/HubList.js @@ -1,63 +1,64 @@ // React and component imports import { useEffect, useState } from "react"; import Hub from "./Hub.js"; -import InvestorInfo from "./InvestorInfo.js"; +import uuid from 'react-uuid'; // CSS import -import '../css/UserCheckin.css'; +import "../css/UserCheckin.css"; /** * Component that build the checkin list and displays checkin info. - * @returns {import('react').HtmlHTMLAttributes} A div with the hubs + * @returns {import('react').HtmlHTMLAttributes} A div with the hubs * in a vertical layout. */ function HubList(props) { - const [hubItems, setHubItems] = useState([]); - const [isSelected, setIsSelected] = useState(false); - const [name, setName] = useState(''); - + const [displayedItems, setDisplayedItems] = useState([]); + /** - * Loads new the checkins into the current cache/map of hubs. + * Method that determines whehter the Hub should be showed. + * @returns {Boolean} True if to be shown, false if not. */ - const updateHubItems = () => { - // sort and create the elemnts - let hubs = []; - //const sorted = props.data.sort((a, b) => b.suspicionScore - a.suspicionScore); - props.data.forEach(hub => hubs.push( - <Hub key={hub.id} id={hub.id} name={hub.name} value={hub.suspicionScore} setSelected={props.setSelected}></Hub> - )); - - setHubItems(hubs); + const toInclude = holder => { + // TODO: add number search or differentiate between it + // TODO: add sus score range.... + if (!holder) { + return false; + }; + + // const matchingId = holder.id.toString().includes(queryString.toLowerCase()); + //console.log(props.queryString.toLowerCase(), props.data.length); + const matchingName = holder.name.toLowerCase().includes(props.queryString.toLowerCase()); + return matchingName; } - const getName = () => { - props.data.forEach(hub => { - if (hub.id === props.selected) { - setName(hub.name); - } - }) - setName(''); + const toHubElement = hub => + <Hub + id={hub.id} + name={hub.name} + value={hub.suspicionScore} + setSelectedId={props.setSelectedId} + searching={props.searching} + ></Hub>; + + /** + * Filters the items to be shown, then created the iteams and sets the state with the items. + */ + const filterItems = () => { + if (!props.queryString) { + // don't need to show all unless searching + return setDisplayedItems(props.data.slice(0,600).map(hub => toHubElement(hub))) + } + + const criteria = props.data.filter(holder => toInclude(holder)); + setDisplayedItems(criteria.map(hub => toHubElement(hub))); } - - // React hook that updates when the hubs are recalculated - useEffect(() => updateHubItems(), [props.data]); - - //React hook to show data for an investor - useEffect(() => { - setIsSelected(true) - getName(); - }, [props.selected]); - - return ( - <div className="User-checkin"> - <div className="Checkins"> - <h2>Suspicion Ranks</h2> - <ul className='Checkin-list'>{hubItems}</ul> - </div> - <InvestorInfo personId={props.selected} isSelected={isSelected} name={name} dates={props.dates}></InvestorInfo> - </div> - ); + /** + * Hook to update the items on change of the search string or update of data. + */ + useEffect(() => filterItems(), [props.queryString, props.data, props.searching]); + + return <ul className='Checkin-list'>{displayedItems}</ul>; } -export default HubList;
\ No newline at end of file +export default HubList; diff --git a/react-frontend/src/components/HubWidget.js b/react-frontend/src/components/HubWidget.js new file mode 100644 index 0000000..7a82c61 --- /dev/null +++ b/react-frontend/src/components/HubWidget.js @@ -0,0 +1,84 @@ +// React and component imports +import { useEffect, useState, useRef } from "react"; +import Hub from "./Hub.js"; +import InvestorInfo from "./InvestorInfo.js"; + +// CSS import +import '../css/UserCheckin.css'; +import HubList from "./HubList.js"; + +/** + * Component that build the checkin list and displays checkin info. + * @returns {import('react').HtmlHTMLAttributes} A div with the hubs + * in a vertical layout. + */ +function HubWidget(props) { + // States for selected person + const [followers, setFollowers] = useState([]); + const [selectedName, setSelectedName] = useState([]); + + const [searching, setSearching] = useState(false); + const textInput = useRef(); + + const [showSelectedInfo, setShowSelectedInfo] = useState(false); + + const updateSelected = () => { + props.data.forEach(holder => { + if (holder.id === props.selectedId) { + console.log(holder); + setFollowers(holder.followers); + setSelectedName(holder.name); + } + }); + } + + // Don't need to fetch if repeat id... + const updateSelectedId = id => { + setShowSelectedInfo(true); + props.setSelectedId(id); + } + + // Hook to update and show the info when a user is clicked on ... + useEffect(() => { + updateSelected(); + }, [props.selectedId]); + // On init, don't show... + useEffect(() => setShowSelectedInfo(false), []); + + useEffect(() => { + if (searching) { + return; + } + textInput.current.value = ""; + setQueryString(""); + }, [searching]) + + const [queryString, setQueryString] = useState(""); + + return ( + <> + <div className="User-checkin"> + <div className="Checkins"> + <h2 className="Img-flex Title"> + <span hidden={searching}>Sus Ranks</span> + <input ref={textInput} hidden={!searching} type="text" onChange={e => setQueryString(e.target.value)} placeholder="Search by name"></input> + <img className="Img-btn" hidden={searching} onClick={() => setSearching(true)} src="assets/outline_search_white_18dp.png" alt="image"/> + <img className="Img-btn" hidden={!searching} onClick={() => setSearching(false)} src="assets/outline_cancel_white_18dp.png" alt="image"/> + </h2> + <HubList data={props.data} setSelectedId={updateSelectedId} queryString={queryString} searching={searching}></HubList> + </div> + <InvestorInfo + selectedId={props.selectedId} + setSelectedId={updateSelectedId} + dates={props.dates} + setShowSelectedInfo={setShowSelectedInfo} + showSelectedInfo={showSelectedInfo} + followers={followers} + name={selectedName} + ></InvestorInfo> + </div> + </> + ); +} + +export default HubWidget;
\ No newline at end of file diff --git a/react-frontend/src/components/InvestorInfo.js b/react-frontend/src/components/InvestorInfo.js index d368984..436fa17 100644 --- a/react-frontend/src/components/InvestorInfo.js +++ b/react-frontend/src/components/InvestorInfo.js @@ -2,7 +2,9 @@ import { useEffect, useState } from "react"; // CSS import -import '../css/UserCheckin.css'; +import "../css/UserCheckin.css"; +import "../css/InvesterInfo.css"; +import uuid from "react-uuid"; /** * Componenet for checkins. Has a toggle to show more info. @@ -10,57 +12,154 @@ import '../css/UserCheckin.css'; * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info. */ function InvestorInfo(props) { - const [info, setInfo] = useState({}); + const [info, setInfo] = useState({ + percentGain: 0, + moneyIn: 0, + moneyOut: 0, + percentSP500: 0, + holdings: [], + }); - const toEpochMilli = date => Date.parse(date); - const getInfo = () => { - console.log({ - person: props.name, - start: toEpochMilli(props.dates.start), - end: toEpochMilli(props.dates.end) - }); + const [showStocks, setShowStocks] = useState(false); + const [showFollowers, setShowFollowers] = useState(false); - if (props.name === "") { + const getInfo = () => { + if (props.selectedId === -1) { return; } - + + const toEpochMilli = (date) => Date.parse(date); + + console.log({ + selectedId: props.selectedId, + startTime: toEpochMilli(props.dates.start), + endTime: toEpochMilli(props.dates.end), + }); + fetch("http://localhost:4567/profit", { - method: "POST", - body: JSON.stringify({ - person: props.name, - start: toEpochMilli(props.dates.start), - end: toEpochMilli(props.dates.end) - }), - headers: { - "Content-Type": "application/json", - }, - credentials: "same-origin" + method: "POST", + body: JSON.stringify({ + selectedId: props.selectedId, + startTime: toEpochMilli(props.dates.start), + endTime: toEpochMilli(props.dates.end), + }), + headers: { + "Content-Type": "application/json", + }, + credentials: "same-origin", }) - .then(res => { - console.log(res); - res.json(); - }) - .then(data => { - console.log(data); - setInfo(data); - }) - .catch(err => console.log(err)); - } - /* + .then((res) => res.json()) + .then((data) => { + console.log(data); + setInfo(data); + props.setShowSelectedInfo(true); + }) + .catch((err) => console.log(err)); + }; + + const stockTable = () => { + return ( + <> + <li class="Stock-row"> + <div className="tableHeader"> + <div className="symbol-row">Symbol</div> + <div className="gain-row">Realized gain</div> + <div className="gain-row">Unrealized gain</div> + </div> + </li> + {info.holdings.map((holding) => ( + <li class="Stock-row"> + <div className="symbol-row">{holding.ticker}</div> + <div className="gain-row">{holding.realizedGain.toFixed(3)}</div> + <div className="gain-row">{holding.unrealizedGain.toFixed(3)}</div> + </li> + ))} + </> + ); + }; - const coords = userCoords.map((coord, index) => - <li key={index}> - <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span> - </li> - );*/ + const followerList = () => + props.followers.map((follower) => ( + <li key={uuid()} class="Clickable-name" onClick={() => props.setSelectedId(follower.id)}> + {follower.name} + </li> + )); - useEffect(() => getInfo(), [props.name, props.isSelected, props.personId]) + // Hook that updates when selected has changed + useEffect(() => getInfo(), [props.selectedId]); return ( - <div className="Chosen-user" hidden={props.isSelected}> - hi + <div className="Chosen-user" hidden={!props.showSelectedInfo}> + <h3> + <span onClick={() => props.setShowSelectedInfo(false)}> + <img className="Img-btn" src="assets/round_arrow_back_white_18dp.png" alt="image" /> + </span> + <span>CIK: {props.selectedId}</span> + </h3> + <h2 id="investerName"> + <a href={"https://sec.report/CIK/" + props.selectedId + "/Insider-Trades"} target="_blank"> + {props.name} + <img src="link-icon.svg" alt="image" /> + </a> + </h2> + <div id="top-bar"> + <div id="gain-number"> + <p className="bigNumber">{(info.moneyOut - info.moneyIn).toFixed(3)}$</p> gain + </div> + + <div> + <p className="bigNumber">{info.percentGain.toFixed(3)}%</p> + compared to {info.percentSP500.toFixed(3)}% on SP500 + </div> + </div> + + <div> + <div className="Checkin"> + <div className="Img-flex"> + <span className="tableHeader">View returns</span> + <img + className="Img-btn" + hidden={showStocks} + onClick={() => setShowStocks((toggle) => !toggle)} + src="assets/round_expand_more_white_18dp.png" + alt="image" + /> + <img + className="Img-btn" + hidden={!showStocks} + onClick={() => setShowStocks((toggle) => !toggle)} + src="assets/round_expand_less_white_18dp.png" + alt="image" + /> + </div> + <ul hidden={!showStocks} class="Stock-table"> + {stockTable()} + </ul> + </div> + + <div className="Checkin" hidden={!followerList}> + <div className="Img-flex"> + <span className="tableHeader">View followers</span> + <img + className="Img-btn" + hidden={showFollowers} + onClick={() => setShowFollowers((toggle) => !toggle)} + src="assets/round_expand_more_white_18dp.png" + alt="image" + /> + <img + className="Img-btn" + hidden={!showFollowers} + onClick={() => setShowFollowers((toggle) => !toggle)} + src="assets/round_expand_less_white_18dp.png" + alt="image" + /> + </div> + <ul hidden={!showFollowers}>{followerList()}</ul> + </div> + </div> </div> ); } -export default InvestorInfo;
\ No newline at end of file +export default InvestorInfo; diff --git a/react-frontend/src/components/TimeSelector.js b/react-frontend/src/components/TimeSelector.js index 652a9ec..997494d 100644 --- a/react-frontend/src/components/TimeSelector.js +++ b/react-frontend/src/components/TimeSelector.js @@ -34,9 +34,8 @@ function TimeSelector(props) { <DateSelector side={"left"} name={"Start Date"} className="Coord-select-left" clickedFunc={setCurrent} changedFunc={setStartDate} disabled={current==='start' || props.isChanging} value={toValue(startDate)}></DateSelector> <div> - <h2>Adjust Timeframe</h2> <button className="Btn Route-btn" onClick={() => changeTimeframe()} - disabled={current!=="" || props.isChanging}>Change Timeframe</button> + disabled={current!=="" || props.isChanging}>Adjust Timeframe</button> </div> <DateSelector side={"right"} name={"End Date"} className="Coord-select-right" clickedFunc={setCurrent} changedFunc={setEndDate} disabled={current==='end' || props.isChanging} value={toValue(endDate)}></DateSelector> diff --git a/react-frontend/src/components/Visualization.js b/react-frontend/src/components/Visualization.js index 33f96c9..01093dc 100644 --- a/react-frontend/src/components/Visualization.js +++ b/react-frontend/src/components/Visualization.js @@ -1,6 +1,5 @@ // JS module imports -import { useEffect, useRef, useState } from "react"; -import uuid from 'react-uuid'; +import { useMemo, useState } from "react"; import Graph from 'vis-react'; // CSS imports @@ -26,19 +25,26 @@ function Visualization(props) { hideEdgeInfo = val; }*/ + const [key, setKey] = useState(0); + + const [graphState, setGraphState] = useState({ + nodes: [], + edges: [] + }); + const options = { + autoResize: true, edges: { color: "#ffffff" } }; + const events = { - select: () => event => props.setSelected(event.nodes[0]) - }; + selectNode: event => { + props.setSelectedId(event.nodes[0]) + } + } - const [graphState, setGraphState] = useState({ - nodes: [], - edges: [] - }); /*const getEdgeInfo = (fromID, toID) => { fetch("http://localhost:4567/edge-data", { @@ -64,6 +70,7 @@ function Visualization(props) { let nodes = []; const maxScore = props.data[0].suspicionScore; const interval = maxScore / 4; + props.data.forEach(hub => { if (hub.followers) { let colorVal = '#f6f7d4'; @@ -102,6 +109,7 @@ function Visualization(props) { }); return nodes; } + const getEdges = () => { let edges = [] props.data.forEach(hub => { @@ -129,13 +137,17 @@ function Visualization(props) { hidden={hideEdgeInfo}> </EdgeInfo>*/ - // Hooks to update graph state - useEffect(() => setGraphState({nodes: getNodes(), edges: getEdges()}), [JSON.stringify(props.data)]); + // Hooks to update graph state when data changes + useMemo(() => { + setKey(key => key + 1); + setGraphState({nodes: getNodes(), edges: getEdges()}) + }, [JSON.stringify(props.data)]); + return ( <div className="Map-canvas"> <Graph - key={uuid()} + key={key} graph={graphState} options={options} events={events}> diff --git a/react-frontend/src/components/WatchDogs.js b/react-frontend/src/components/WatchDogs.js index d631ea9..dfe47c8 100644 --- a/react-frontend/src/components/WatchDogs.js +++ b/react-frontend/src/components/WatchDogs.js @@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react'; import TimeSelector from './TimeSelector.js'; import Visualization from './Visualization.js'; -import HubList from './HubList.js'; +import HubWidget from './HubWidget.js'; import Loading from './Loading.js'; import Modal from './Modal.js'; import logo from './images/logo.png'; @@ -26,8 +26,8 @@ function WatchDogs() { }); // State for visualization data const [data, setData] = useState([]); - // State for selected person - const [selected, setSelected] = useState(-1); + // State for selectedId + const [selectedId, setSelectedId] = useState(-1); const toEpochMilli = date => Date.parse(date); const getGraphData = () => { @@ -49,9 +49,9 @@ function WatchDogs() { .then(res => res.json()) .then(data => { //TODO: optimize this - const sliced = data.holders.slice(0, 500); - console.log(sliced); - setData(sliced); + //const sliced = data.holders.slice(0, 500); + //console.log(sliced); + setData(data.holders); setHasLoaded(true); }) .catch(err => console.log(err)); @@ -77,9 +77,9 @@ function WatchDogs() { <div className="Canvas-filler Canvas-filler-1"></div> <div className="Canvas-filler Canvas-filler-2"></div> <div className="Canvas-filler Canvas-filler-3"></div> - <HubList setHasLoaded={setHasLoaded} data={data} setSelected={setSelected} selected={selected} dates={dates}></HubList> + <HubWidget setHasLoaded={setHasLoaded} data={data} dates={dates} selectedId={selectedId} setSelectedId={setSelectedId}></HubWidget> <TimeSelector isChanging={isChanging} dates={dates} setDates={setDates}></TimeSelector> - <Visualization hasLoaded={hasLoaded} data={data} setSelected={setSelected}></Visualization> + <Visualization hasLoaded={hasLoaded} data={data.slice(0, 600)} setSelectedId={setSelectedId}></Visualization> </div> } </> |