diff options
Diffstat (limited to 'react-frontend')
20 files changed, 244 insertions, 161 deletions
diff --git a/react-frontend/public/assets/outline_cancel_white_18dp.png b/react-frontend/public/assets/outline_cancel_white_18dp.png Binary files differnew file mode 100644 index 0000000..b989e20 --- /dev/null +++ b/react-frontend/public/assets/outline_cancel_white_18dp.png diff --git a/react-frontend/public/assets/outline_close_white_18dp.png b/react-frontend/public/assets/outline_close_white_18dp.png Binary files differnew file mode 100644 index 0000000..65867f3 --- /dev/null +++ b/react-frontend/public/assets/outline_close_white_18dp.png diff --git a/react-frontend/public/assets/outline_minimize_white_18dp.png b/react-frontend/public/assets/outline_minimize_white_18dp.png Binary files differnew file mode 100644 index 0000000..a6a12d6 --- /dev/null +++ b/react-frontend/public/assets/outline_minimize_white_18dp.png diff --git a/react-frontend/public/assets/outline_search_white_18dp.png b/react-frontend/public/assets/outline_search_white_18dp.png Binary files differnew file mode 100644 index 0000000..52ffe5f --- /dev/null +++ b/react-frontend/public/assets/outline_search_white_18dp.png diff --git a/react-frontend/public/assets/outline_tune_white_18dp.png b/react-frontend/public/assets/outline_tune_white_18dp.png Binary files differnew file mode 100644 index 0000000..4168a63 --- /dev/null +++ b/react-frontend/public/assets/outline_tune_white_18dp.png diff --git a/react-frontend/public/round_arrow_back_white_18dp.png b/react-frontend/public/assets/round_arrow_back_white_18dp.png Binary files differindex bbaccda..bbaccda 100644 --- a/react-frontend/public/round_arrow_back_white_18dp.png +++ b/react-frontend/public/assets/round_arrow_back_white_18dp.png diff --git a/react-frontend/public/assets/round_expand_less_white_18dp.png b/react-frontend/public/assets/round_expand_less_white_18dp.png Binary files differnew file mode 100644 index 0000000..a64f430 --- /dev/null +++ b/react-frontend/public/assets/round_expand_less_white_18dp.png diff --git a/react-frontend/public/assets/round_expand_more_white_18dp.png b/react-frontend/public/assets/round_expand_more_white_18dp.png Binary files differnew file mode 100644 index 0000000..f8c7213 --- /dev/null +++ b/react-frontend/public/assets/round_expand_more_white_18dp.png diff --git a/react-frontend/src/components/Hub.js b/react-frontend/src/components/Hub.js index 94830fa..1906684 100644 --- a/react-frontend/src/components/Hub.js +++ b/react-frontend/src/components/Hub.js @@ -10,24 +10,25 @@ import '../css/UserCheckin.css'; * @returns {import('react').HtmlHTMLAttributes} A list element holding a checkin's info. */ function Hub(props) { + const LEN_NAME = 15; + const [isHover, setIsHover] = useState(false); const formatName = name => { - if (name.length > 12) { - return props.name.substring(0, 15) + '...'; + if (name.length >= LEN_NAME) { + return props.name.substring(0, LEN_NAME - 3) + '...'; } return props.name; } return ( - <li - className='Checkin' - onMouseOver = {() => setIsHover(true)} - onMouseLeave = {() => setIsHover(false)}> + <li className='Checkin'> <div className="Img-flex"> <span className="Clickable-name" - onClick = {() => console.log(props.id)}>{isHover ? props.name : formatName(props.name)}</span> + 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 8e00d0f..383d570 100644 --- a/react-frontend/src/components/HubList.js +++ b/react-frontend/src/components/HubList.js @@ -1,6 +1,7 @@ // React and component imports import { useEffect, useState } from "react"; import Hub from "./Hub.js"; +import uuid from 'react-uuid'; // CSS import import "../css/UserCheckin.css"; @@ -11,33 +12,53 @@ import "../css/UserCheckin.css"; * in a vertical layout. */ function HubList(props) { - const [hubItems, setHubItems] = 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); - }; - // React hook that updates when the hubs are recalculated - useEffect(() => updateHubItems(), [props.data]); - - return <ul className='Checkin-list'>{hubItems}</ul>; + 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 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))); + } + + /** + * 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; diff --git a/react-frontend/src/components/HubSearch.js b/react-frontend/src/components/HubSearch.js deleted file mode 100644 index 827ea6d..0000000 --- a/react-frontend/src/components/HubSearch.js +++ /dev/null @@ -1,64 +0,0 @@ -// React and component imports -import { useEffect, useState } from "react"; -import InvestorInfo from "./InvestorInfo.js"; - -// CSS import -import '../css/UserCheckin.css'; -import Hub from "./HubList.js"; -import Search from "./HubSearch.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 HubSearch(props) { - const [queryString, setQueryString] = useState("", (s) => s.toLowerCase()); - const [displayedItems, setDisplayedItems] = useState([]); - - /** - * Method that determines whehter the Hub should be showed. - * @returns {Boolean} True if to be shown, false if not. - */ - 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()); - const matchingName = holder.name.toLowerCase().includes(queryString); - return matchingName; - } - - /** - * Filters the items to be shown, then created the iteams and sets the state with the items. - */ - const filterItems = () => { - console.log(queryString); - const criteria = props.data.filter(holder => toInclude(holder)); - setDisplayedItems(criteria.map(hub => <p>{hub.name}</p>)) - } - - /** - * Hook to update the items on change of the search string. - */ - useEffect(() => filterItems(), [queryString]); - - // TODO: maybe have a quick explanation of what search gives... - // TODO: have number of ceos that make it... - // TODO: weighted search or sort after search.... - // TODO: highlight part of string that matched... - return ( - <div className="User-checkin"> - <div className="Checkins"> - <h2>Search</h2> - <input type="text" onChange={(e) => setQueryString(e.target.value)}></input> - <ul className='Checkin-list'>{displayedItems}</ul>; - </div> - </div> - ); -} - -export default HubSearch;
\ No newline at end of file diff --git a/react-frontend/src/components/HubWidget.js b/react-frontend/src/components/HubWidget.js index de0ae32..7a82c61 100644 --- a/react-frontend/src/components/HubWidget.js +++ b/react-frontend/src/components/HubWidget.js @@ -1,12 +1,11 @@ // React and component imports -import { useEffect, useState } from "react"; +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"; -import HubSearch from "./HubSearch.js"; /** * Component that build the checkin list and displays checkin info. @@ -14,20 +13,70 @@ import HubSearch from "./HubSearch.js"; * 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>Suspicion Ranks</h2> - <HubList setHasLoaded={props.setHasLoaded} data={props.data} setSelected={props.setSelected} selected={props.selected} dates={props.dates}></HubList> + <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> - <HubSearch data={props.data}></HubSearch> + <InvestorInfo + selectedId={props.selectedId} + setSelectedId={updateSelectedId} + dates={props.dates} + setShowSelectedInfo={setShowSelectedInfo} + showSelectedInfo={showSelectedInfo} + followers={followers} + name={selectedName} + ></InvestorInfo> </div> - <InvestorInfo - personId={props.selected} - selectedId={props.selectedId} - dates={props.dates} - ></InvestorInfo> </> ); } diff --git a/react-frontend/src/components/InvestorInfo.js b/react-frontend/src/components/InvestorInfo.js index 2a0f0d9..3a3a11f 100644 --- a/react-frontend/src/components/InvestorInfo.js +++ b/react-frontend/src/components/InvestorInfo.js @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; // CSS import import "../css/UserCheckin.css"; import "../css/InvesterInfo.css"; +import uuid from "react-uuid"; /** * Componenet for checkins. Has a toggle to show more info. @@ -11,78 +12,115 @@ import "../css/InvesterInfo.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, + 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), + 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 coords = userCoords.map((coord, index) => - <li key={index}> - <span>{'('+coord[0].toFixed(6)}, {coord[1].toFixed(6)+')'}</span> - </li> - );*/ const stockTable = () => { return ( - <div id="stockTable"> + <> + <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> - </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>)} + </> ); }; - useEffect(() => getInfo(), [props.name, props.isSelected, props.personId]); + const followerList = () => + props.followers.map(follower => <li key={uuid()} class="Clickable-name" onClick={() => props.setSelectedId(follower.id)}>{follower.name}</li>); + + // Hook that updates when selected has changed + useEffect(() => getInfo(), [props.selectedId]); return ( - <div className="Chosen-user" hidden={false}> - <h2 id="investerName">{"CRUTCHFIELD BRADFORD"}</h2> + <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"> + {props.name} + </h2> <div id="top-bar"> <div id="gain-number"> - <p className="bigNumber">{583}$</p> gain + <p className="bigNumber">{info.percentGain.toFixed(3)}$</p> gain </div> <div> - <p className="bigNumber">{2.8}%</p> - compared to {1.2}% on SP500 + <p className="bigNumber">{info.percentGain.toFixed(3)}%</p> + compared to {info.percentSP500.toFixed(3)}% on SP500 </div> </div> - {stockTable()} + + <div> + <div className="Checkin"> + <div className="Img-flex"> + <span className="tableHeader">View trades</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> ); } 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 d374738..96b2027 100644 --- a/react-frontend/src/components/Visualization.js +++ b/react-frontend/src/components/Visualization.js @@ -1,6 +1,5 @@ // JS module imports -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import uuid from 'react-uuid'; +import { useMemo, useState } from "react"; import Graph from 'vis-react'; // CSS imports @@ -13,6 +12,8 @@ import '../css/Canvas.css'; * @returns {import("react").HtmlHTMLAttributes} The canvas to be retured. */ function Visualization(props) { + const [key, setKey] = useState(0); + const [graphState, setGraphState] = useState({ nodes: [], edges: [] @@ -27,8 +28,7 @@ function Visualization(props) { const events = { selectNode: event => { - console.log(event); - //props.setSelectedId(event.nodes[0]); + props.setSelectedId(event.nodes[0]) } } @@ -95,12 +95,16 @@ function Visualization(props) { } // Hooks to update graph state when data changes - useMemo(() => setGraphState({nodes: getNodes(), edges: getEdges()}), [props.data]); + 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 e045d82..dfe47c8 100644 --- a/react-frontend/src/components/WatchDogs.js +++ b/react-frontend/src/components/WatchDogs.js @@ -26,7 +26,7 @@ function WatchDogs() { }); // State for visualization data const [data, setData] = useState([]); - // State for selected person + // State for selectedId const [selectedId, setSelectedId] = useState(-1); const toEpochMilli = date => Date.parse(date); @@ -77,7 +77,7 @@ 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> - <HubWidget setHasLoaded={setHasLoaded} data={data} setSelected={setSelectedId} selected={selectedId} dates={dates}></HubWidget> + <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.slice(0, 600)} setSelectedId={setSelectedId}></Visualization> </div> diff --git a/react-frontend/src/css/App.css b/react-frontend/src/css/App.css index e39eb3e..1b8de5f 100644 --- a/react-frontend/src/css/App.css +++ b/react-frontend/src/css/App.css @@ -7,6 +7,7 @@ grid-template-columns: max-content auto max-content max-content; background-color: #121212; + } .App-logo { diff --git a/react-frontend/src/css/CoordSelector.css b/react-frontend/src/css/CoordSelector.css index 881be08..e1fde99 100644 --- a/react-frontend/src/css/CoordSelector.css +++ b/react-frontend/src/css/CoordSelector.css @@ -49,4 +49,8 @@ width: 90%; } +.Flex-coord { + margin: auto; +} + diff --git a/react-frontend/src/css/InvesterInfo.css b/react-frontend/src/css/InvesterInfo.css index 5385965..fcfb29c 100644 --- a/react-frontend/src/css/InvesterInfo.css +++ b/react-frontend/src/css/InvesterInfo.css @@ -2,11 +2,11 @@ display: flex; flex-direction: row; justify-content: space-evenly; - margin-bottom: 10px; margin-right: 10px; margin-left: 10px; border-bottom: solid 1px white; margin-top: 0px; + padding-bottom: 20px; } /* div { @@ -25,16 +25,26 @@ .Chosen-user { background-color: #333333; color: lightgreen; - width: 350px; + width: 25vw; border-radius: 10px; } +.Stock-row { + display: flex; + justify-content: space-evenly; + color: white; + margin-bottom: 5px; +} + .tableHeader { display: flex; flex-direction: row; justify-content: left; margin-right: 10px; margin-left: 10px; + font-size: 120%; + padding: 5px; + color: lightgreen; } .symbol-row { diff --git a/react-frontend/src/css/UserCheckin.css b/react-frontend/src/css/UserCheckin.css index 141cc01..389bca6 100644 --- a/react-frontend/src/css/UserCheckin.css +++ b/react-frontend/src/css/UserCheckin.css @@ -20,7 +20,7 @@ ul { z-index: 10; background-color: #333333; border-radius: 20px; - margin: 5px; + margin: 5px 5px 5px 0; } .Coord-ex { @@ -30,22 +30,28 @@ ul { text-align: center; } +.Chosen-user > h3, .Checkins > h3 { + display: flex; + justify-content: space-between; + height: 5vh; + padding: 0 30px; +} + .Chosen-user > h2, .Checkins > h2 { display: flex; - justify-content: space-evenly; + justify-content: center; height: 5vh; - padding: 0 10px; + padding: 0 30px; } .Checkin-list { padding: 0 20px; - height: 86vh; + height: 85vh; overflow-y: scroll; - cursor: default; } .User-checkin-list { - height: 80vh; + height: 75vh; overflow-y: scroll; list-style-position: inside; @@ -79,7 +85,7 @@ ul { .Img-btn { background-color: #424242; border-radius: 50%; - margin-right: 10px; + margin-left: auto; } .Img-btn:hover { @@ -91,4 +97,18 @@ ul { cursor: pointer; text-decoration: underline; color: lightgreen; +} + +/* CSS borrowed from W3 Schools */ +input[type=text] { + width: 80%; + padding: 12px 20px; + box-sizing: border-box; + font-size: 80%; + background-color: lightgoldenrodyellow; + border-radius: 2; +} + +.Title { + margin-top: 20px; }
\ No newline at end of file |