From 935ef7d2f76cca7bc1f9840dc357de85f93a1ec2 Mon Sep 17 00:00:00 2001 From: Galen Guyer Date: Mon, 11 Jul 2022 17:10:23 -0400 Subject: Update for new dashboard frontend --- src/App.css | 49 ++++++++--- src/App.js | 215 ------------------------------------------------ src/App.jsx | 205 +++++++++++++++++++++++++++++++++++++++++++++ src/App.test.js | 8 -- src/Card.css | 10 --- src/Card.js | 25 ------ src/GoatCounter.js | 26 ------ src/History.js | 82 ------------------ src/HistoryTable.js | 40 --------- src/MainPage.js | 126 ---------------------------- src/components/Card.css | 41 +++++++++ src/components/Card.jsx | 18 ++++ src/index.css | 8 +- src/index.js | 22 ----- src/main.jsx | 13 +++ src/pages/Graph.css | 33 ++++++++ src/pages/Graph.jsx | 118 ++++++++++++++++++++++++++ src/pages/Index.css | 32 +++++++ src/pages/Index.jsx | 76 +++++++++++++++++ src/reportWebVitals.js | 13 --- src/setupTests.js | 5 -- src/useFetch.js | 17 ++++ 22 files changed, 596 insertions(+), 586 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.jsx delete mode 100644 src/App.test.js delete mode 100644 src/Card.css delete mode 100644 src/Card.js delete mode 100644 src/GoatCounter.js delete mode 100644 src/History.js delete mode 100644 src/HistoryTable.js delete mode 100644 src/MainPage.js create mode 100644 src/components/Card.css create mode 100644 src/components/Card.jsx delete mode 100644 src/index.js create mode 100644 src/main.jsx create mode 100644 src/pages/Graph.css create mode 100644 src/pages/Graph.jsx create mode 100644 src/pages/Index.css create mode 100644 src/pages/Index.jsx delete mode 100644 src/reportWebVitals.js delete mode 100644 src/setupTests.js create mode 100644 src/useFetch.js (limited to 'src') diff --git a/src/App.css b/src/App.css index 78106d4..f4846a6 100644 --- a/src/App.css +++ b/src/App.css @@ -1,15 +1,44 @@ .App { text-align: center; - padding-bottom: 2rem; - padding-top: 1rem; - width: 90%; - margin-left: auto; - margin-right: auto; -} + margin: auto; + max-width: 90vw; -.Section { + position: relative; + min-height: 100vh; display: flex; - justify-content: center; - margin-left: auto; - margin-right: auto; + flex-direction: column; +} + +.App h1 { + font-size: 2.4em; + margin: 18px 0px 8px 0px; +} +.Updated .Latest { + font-size: 1.3em; + margin: 0px; +} +.Updated .Prior { + margin: 6px; + color: #666; +} + +.App a { + text-decoration: none; + color: #000; +} + +.BlueLink { + color: #008 !important; +} + +footer { + margin-top: auto; + margin-bottom: 20px; +} + +@media screen and (max-width: 600px) { + .App { + margin: auto; + max-width: 95vw; + } } diff --git a/src/App.js b/src/App.js deleted file mode 100644 index fd5e6d0..0000000 --- a/src/App.js +++ /dev/null @@ -1,215 +0,0 @@ -import React from "react"; -import useSWR from "swr"; -import { DateTime } from "luxon"; -import { BrowserRouter, Route, Switch, Link } from "react-router-dom"; -import MainPage from "./MainPage"; -import History from "./History"; -import HistoryTable from "./HistoryTable"; -import "./App.css"; - -const url = "/data.json"; - -function App() { - let { data: rawData, error: error } = useSWR(url); - - const [timeDifference, setTimeDifference] = React.useState(1); - const [showAllTime, setShowAllTime] = React.useState(false); - - if (error) - return ( -
-

RIT Covid Dashboard

-

An error occurred

-
- ); - if (!rawData) - return ( -
-

RIT Covid Dashboard

-

Loading latest data...

-
- ); - - // rawData = rawData.slice(0, 177); - let data = rawData; - console.log(data.length); - const local = DateTime.local().zoneName; - const semesterStart = DateTime.fromISO("2021-01-01"); - // if (!showAllTime) { - // data = rawData.filter((d) => { - // let date = DateTime.fromSQL(d.last_updated, { zone: "UTC" }).setZone(local); - // return date > semesterStart; - // }); - // const last = rawData[rawData.length - data.length - 1]; - // data = data.map((d) => { - // return { - // alert_level: d.alert_level, - // beds_available: d.beds_available, - // isolation_off_campus: d.isolation_off_campus, - // isolation_on_campus: d.isolation_on_campus, - // last_updated: d.last_updated, - // new_staff: d.new_staff, - // new_students: d.new_students, - // quarantine_off_campus: d.quarantine_off_campus, - // quarantine_on_campus: d.quarantine_on_campus, - // tests_administered: d.tests_administered - last.tests_administered, - // total_staff: d.total_staff - last.total_staff, - // total_students: d.total_students - last.total_students, - // }; - // }); - // } - - const latest = data[data.length - 1]; - const prior = data[data.length - (1 + timeDifference)]; - return ( - -
-

- RIT Covid Dashboard -

- {/* -

- Last Updated:{" "} - {lastUpdate.toLocaleString({ - weekday: "long", - month: "long", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - })} -

-

- Prior Update:{" "} - {priorUpdate.toLocaleString({ - weekday: "long", - month: "long", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - })}{" "} - ({timeDifference == 1 ? "one day ago" : timeDifference == 5 ? "one week ago" : "two weeks ago"}) -

- */} - {/* -   - */} -
- - - - - - { - return { value: d.total_students, date: d.last_updated }; - })} - /> - - - { - return { value: d.total_staff, date: d.last_updated }; - })} - /> - - - { - return { value: d.new_students, date: d.last_updated }; - })} - /> - - - { - return { value: d.new_staff, date: d.last_updated }; - })} - /> - - - { - return { value: d.quarantine_on_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.quarantine_off_campus, date: d.last_updated }; - })} - /> - - - - { - return { value: d.isolation_on_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.isolation_off_campus, date: d.last_updated }; - })} - /> - - - { - return { value: d.tests_administered, date: d.last_updated }; - })} - /> - - - { - return { value: d.beds_available, date: d.last_updated }; - })} - /> - - -
-

- By Galen Guyer. Source available on{" "} - - GitHub - {" "} - ( - - Report Issue - - ) -

-

- - API Documentation - -

-
-
- ); -} - -export default App; diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..7f1f9c3 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,205 @@ +import { useLocation, Routes, Route, Link } from "react-router-dom"; +import { useState, lazy, Suspense } from "react"; +import useFetch from "./useFetch"; +import { DateTime } from "luxon"; +import "./App.css"; +const Index = lazy(() => import("./pages/Index")); +const Graph = lazy(() => import("./pages/Graph")); +import { useEffect } from "react"; + +const App = () => { + const url = localStorage.getItem("url") ?? "/data.json"; + + let routerLocation = useLocation(); + useEffect(() => { + !window.goatcounter ?? + window.goatcounter.count({ + path: location.pathname + location.search + location.hash, + }); + }, [routerLocation]); + + const response = useFetch(url); + + const [timeDifference, setTimeDifference] = useState(1); + + return ( +
+
+ +

2020-2021 RIT COVID Dashboard

+ +
+ + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + + } + /> + + + +
+ ); +}; + +const Updated = (props) => { + const { loading, lastUpdate, priorUpdate, timeDifference } = props; + if (loading) { + return
; + } + + return ( +
+
+ Last Updated:{" "} + {lastUpdate.toLocaleString({ + weekday: "long", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })} +
+
+ Prior Update:{" "} + {priorUpdate.toLocaleString({ + weekday: "long", + month: "long", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + })}{" "} + ({timeDifference == 1 ? "one weekday ago" : timeDifference == 5 ? "one week ago" : "two weeks ago"}) +
+
+ ); +}; + +export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 7c46384..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import App from "./App"; - -test("renders learn react link", () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/Card.css b/src/Card.css deleted file mode 100644 index 785b3e5..0000000 --- a/src/Card.css +++ /dev/null @@ -1,10 +0,0 @@ -.Card { - padding: 16px; - width: 40%; -} - -@media screen and (min-width: 768px) { - .Card { - width: 20%; - } -} diff --git a/src/Card.js b/src/Card.js deleted file mode 100644 index a58f611..0000000 --- a/src/Card.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import "./Card.css"; - -const Card = (props) => { - let diff = props.diff.toString(); - if (diff.charAt(0) != "-") { - diff = "+" + diff; - } - - return ( - -
-

- - {props.latest}{" "} - -

-

{props.name}

-
- - ); -}; - -export default Card; diff --git a/src/GoatCounter.js b/src/GoatCounter.js deleted file mode 100644 index 07a768a..0000000 --- a/src/GoatCounter.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -class GoatCounter extends React.Component { - componentDidMount() { - window.counter = "https://rcd.goatcounter.com/count"; - const script = window.document.createElement("script"); - script.async = 1; - script.src = "https://gc.zgo.at/count.js"; - script.id = "goatcounter"; - script.setAttribute("data-goatcounter", "https://rcd.goatcounter.com/count"); - (window.document.head || window.document.body).appendChild(script); - } - - componentWillUnmount() { - const script = window.document.getElementById("goatcounter"); - if (script) { - script.parentNode.removeChild(script); - } - } - - render() { - return null; - } -} - -export default GoatCounter; diff --git a/src/History.js b/src/History.js deleted file mode 100644 index 7425b61..0000000 --- a/src/History.js +++ /dev/null @@ -1,82 +0,0 @@ -import { React, PureComponent } from "react"; -import { DateTime } from "luxon"; -import { - BarChart, - Bar, - LineChart, - Line, - CartesianGrid, - XAxis, - YAxis, - Tooltip, - ResponsiveContainer, - Label, -} from "recharts"; -import GoatCounter from "./GoatCounter"; - -const History = (props) => { - const offset = DateTime.fromSQL(props.data[0].date, { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(); - const data = props.data.map((d) => { - return { - value: d.value, - date: DateTime.fromSQL(d.date, { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(), - }; - }); - - return ( - <> -

{props.name}

- - - - } - height={90} - /> - - - - - - ); -}; - -class CustomizedAxisTick extends PureComponent { - render() { - const { x, y, payload } = this.props; - - return ( - - - {DateTime.fromSeconds(payload.value).toLocaleString()} - - - ); - } -} - -const CustomTooltip = ({ active, payload, label }) => { - if (active) { - return ( -
-

- {DateTime.fromSeconds(label).toLocaleString({ weekday: "long", month: "long", day: "2-digit" })} -

-

{payload[0].value}

-
- ); - } - return null; -}; - -export default History; diff --git a/src/HistoryTable.js b/src/HistoryTable.js deleted file mode 100644 index fb27f0e..0000000 --- a/src/HistoryTable.js +++ /dev/null @@ -1,40 +0,0 @@ -import { React } from "react"; -import { DateTime } from "luxon"; -import GoatCounter from "./GoatCounter"; - -const HistoryTable = (props) => { - const data = props.data; - console.log(data); - let table = ( - - - - - - - {data.map((element) => { - return ( - - - - - ); - })} - -
DatePositive Case Rate
- {DateTime.fromSQL(element.date, { zone: "UTC" }) - .setZone(DateTime.local().zoneName) - .toLocaleString({ weekday: "long", month: "long", day: "2-digit" })} - {element.value}%
- ); - - return ( - <> -

{props.name}

- {table} - - - ); -}; - -export default HistoryTable; diff --git a/src/MainPage.js b/src/MainPage.js deleted file mode 100644 index fcc2e0b..0000000 --- a/src/MainPage.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from "react"; -import Card from "./Card"; -import GoatCounter from "./GoatCounter"; - -const MainPage = (props) => { - const data = props.data; - const latest = data[data.length - 1]; - const prior = data[data.length - (1 + props.timeDifference)]; - - return ( - <> -

- This site shows data from the 2020 Fall and 2021 Spring semesters. -

-
-
-

- Total Positive Cases Since August 19 (First Day of Classes) -

-
- - -
-
-
-
-

New Positive Cases From Past 14 Days

-
- - -
-
-
-
-

Number of Students in Quarantine

-
- Quarantine separates and restricts the movement of people who were exposed to a contagious disease - to see if they become sick. -
-
- - -
-
-
-
-

Number of Students in Isolation

-
- Isolation separates sick people with a contagious disease from people who are not sick. -
-
- - -
-
-
-
-

Tests

-
- -
-
-
-
-

Quarantine/Isolation Bed Availability On-campus

-
- -
-
- - - ); -}; - -export default MainPage; diff --git a/src/components/Card.css b/src/components/Card.css new file mode 100644 index 0000000..fddfe22 --- /dev/null +++ b/src/components/Card.css @@ -0,0 +1,41 @@ +.cardLink { + text-decoration: none; + color: black; + margin: 12px 24px; + flex-basis: 100%; + max-width: 200px; +} + +.Card { + text-decoration: none; + color: #000; + border: 2px solid #fbd38d; + border-radius: 12px; + padding: 0px 40px; +} + +@media screen and (max-width: 600px) { + .Card { + padding: 0px 16px; + margin: 10px 0px; + } +} + +.Latest { + font-size: 1.8em; +} + +.Card:hover { + background-color: #ffc869; + color: #fff; +} +.Card:hover .Diff { + color: #fff; +} + +.animate { + -moz-transition: color, background-color 0.3s; + -webkit-transition: color, background-color 0.3s; + -ms-transition: color, background-color 0.3s; + transition: color, background-color 0.3s; +} diff --git a/src/components/Card.jsx b/src/components/Card.jsx new file mode 100644 index 0000000..0535033 --- /dev/null +++ b/src/components/Card.jsx @@ -0,0 +1,18 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import "./Card.css"; + +const Card = (props) => { + return ( + +
+

+ {props.latest}{props.suffix ?? ""} +

+

{props.name}

+
+ + ); +}; + +export default Card; diff --git a/src/index.css b/src/index.css index 868b6c6..a7ae2da 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,3 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", @@ -13,3 +9,7 @@ body { code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +* { + font-weight: 400; +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 4c1dc74..0000000 --- a/src/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import { SWRConfig } from "swr"; -import "./index.css"; -import App from "./App"; -import reportWebVitals from "./reportWebVitals"; - -const fetcher = (...args) => fetch(...args).then((res) => res.json()); - -ReactDOM.render( - - - - - , - document.getElementById("root") -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..8601f97 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { BrowserRouter } from "react-router-dom"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/src/pages/Graph.css b/src/pages/Graph.css new file mode 100644 index 0000000..8cb98ee --- /dev/null +++ b/src/pages/Graph.css @@ -0,0 +1,33 @@ +.Title { + font-size: 2em; + margin-top: 24px; +} + +.ToTheMoon { + -webkit-animation-duration: 0.5s; + animation-duration: 0.5s; + -webkit-animation-delay: 1.5s; + animation-delay: 1.5s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/src/pages/Graph.jsx b/src/pages/Graph.jsx new file mode 100644 index 0000000..bbfa43d --- /dev/null +++ b/src/pages/Graph.jsx @@ -0,0 +1,118 @@ +import { DateTime } from "luxon"; +import { + LineChart, + Line, + CartesianGrid, + XAxis, + YAxis, + Tooltip, + ReferenceLine, + ReferenceDot, + Label, +} from "recharts"; +import "./Graph.css"; + +const Graph = (props) => { + const { name, response, dataKey } = props; + const { data, loading, error } = response; + + if (loading) { + return
; + } + + const eventStyle = { fill: "#767676" }; + + const parsed = data.map((d) => { + return { + date: DateTime.fromSQL(d["last_updated"], { zone: "UTC" }).setZone(DateTime.local().zoneName).toSeconds(), + value: d[dataKey], + }; + }); + + const latest = parsed[parsed.length - 1]; + const prior = parsed[parsed.length - 2]; + const toTheMoon = latest.value > prior.value + 1; + + return ( +
+
{name}
+ 600 ? 750 : window.innerWidth * 0.9} + height={500} + margin={{ top: 15, right: 30, left: 0, bottom: 5 }} + data={parsed} + > + + + + {} + + {toTheMoon ? ( + 🚀} + /> + ) : null} + } + height={90} + /> + + + +
+ ); +}; + +const CustomizedAxisTick = ({ x, y, payload }) => { + return ( + + + {DateTime.fromSeconds(payload.value).toLocaleString()} + + + ); +}; + +const CustomTooltip = ({ active, payload, label }) => { + if (active) { + return ( +
+

+ {DateTime.fromSeconds(label).toLocaleString({ + weekday: "long", + month: "long", + day: "2-digit", + })} +

+

{payload[0].value}

+
+ ); + } + return null; +}; + +export default Graph; diff --git a/src/pages/Index.css b/src/pages/Index.css new file mode 100644 index 0000000..3816796 --- /dev/null +++ b/src/pages/Index.css @@ -0,0 +1,32 @@ +.Message { + margin-top: 24px; + font-size: 1.4em; +} + +@media screen and (max-width: 600px) { + .Message { + display: none; + } +} + +.Section .Title { + text-align: center; + font-size: 1.6em; + flex-basis: 100%; + margin-top: 48px; +} + +@media screen and (max-width: 600px) { + .Section .Title { + margin-top: 12px; + } +} + +.Cards { + display: flex; + justify-content: center; +} + +.Tip { + margin-top: 2px; +} \ No newline at end of file diff --git a/src/pages/Index.jsx b/src/pages/Index.jsx new file mode 100644 index 0000000..78c32f0 --- /dev/null +++ b/src/pages/Index.jsx @@ -0,0 +1,76 @@ +import Card from "../components/Card"; +import "./Index.css"; + +const Index = (props) => { + const response = props.response; + if (response.loading) { + return
Loading...
; + } + + const data = response.data; + + const latest = data[data.length - 1]; + + return ( +
+
+
+ This site shows data from the 2020 Fall and 2021 Spring semesters. For the latest data, visit{" "} + ritcoviddashboard.com +
+
+
+
Total Positive Cases Since August 19 (First Day of Classes)
+
+ + +
+
+ +
+
New Positive Cases From Past 14 Days
+
+ + +
+
+ +
+
Number of Students in Quarantine
+

+ Quarantine separates and restricts the movement of people who were exposed to a contagious disease + to see if they become sick. +

+
+ + +
+
+ +
+
Number of Students in Isolation
+

Isolation separates sick people with a contagious disease from people who are not sick.

+
+ + +
+
+ +
+
Tests
+
+ +
+
+ +
+
Quarantine/Isolation Bed Availability On-campus
+
+ +
+
+
+ ); +}; + +export default Index; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 7dc6b90..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = (onPerfEntry) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 1dd407a..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; diff --git a/src/useFetch.js b/src/useFetch.js new file mode 100644 index 0000000..d098e2f --- /dev/null +++ b/src/useFetch.js @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export default function useFetch(url, options) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetch(url, options) + .then((resp) => resp.json()) + .then((resp) => setData(resp)) + .catch((err) => setError(err)) + .finally(() => setLoading(false)); + }, []); + + return { data, loading, error }; +} -- cgit v1.2.3