From 4f80ddd6bf93f643f08ba0c60a010c1965d72f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Mod=C3=A9e?= <bjomo323@student.liu.se> Date: Wed, 14 Apr 2021 08:41:44 +0000 Subject: [PATCH] Resolve "Create a dashboard" --- client/package-lock.json | 115 ++++++++++++++++++ client/package.json | 3 + client/src/pages/admin/AdminPage.tsx | 9 +- .../src/pages/admin/dashboard/Dashboard.tsx | 58 +++++++++ .../dashboard/components/CurrentUser.tsx | 29 +++++ .../components/NumberOfCompetitions.tsx | 33 +++++ .../dashboard/components/NumberOfRegions.tsx | 33 +++++ .../dashboard/components/NumberOfUsers.tsx | 33 +++++ 8 files changed, 309 insertions(+), 4 deletions(-) create mode 100644 client/src/pages/admin/dashboard/Dashboard.tsx create mode 100644 client/src/pages/admin/dashboard/components/CurrentUser.tsx create mode 100644 client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx create mode 100644 client/src/pages/admin/dashboard/components/NumberOfRegions.tsx create mode 100644 client/src/pages/admin/dashboard/components/NumberOfUsers.tsx diff --git a/client/package-lock.json b/client/package-lock.json index b08413fa..7e73859f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1125,6 +1125,49 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@devexpress/dx-chart-core": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/@devexpress/dx-chart-core/-/dx-chart-core-2.7.5.tgz", + "integrity": "sha512-ZSpBN7SjnOhBOcvmuYZ5U+XyUYKlsTrYXCYMfFH2qXwvqh0e0UbhzW1FPfDXdWqx0Y52MtPX0HJrBC4QqH7Bcg==", + "requires": { + "d3-array": "^2.4.0", + "d3-scale": "^3.2.0", + "d3-shape": "^1.3.7" + } + }, + "@devexpress/dx-core": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/@devexpress/dx-core/-/dx-core-2.7.5.tgz", + "integrity": "sha512-VQQkz0uUqQ7YuVZeBEx1JFqpSSe5Bz9Qy2T0XAbilBQ8IItj74xjzaFMCNjuASAKXOJGtG0RUh+BOKrMGV7jlg==" + }, + "@devexpress/dx-react-chart": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/@devexpress/dx-react-chart/-/dx-react-chart-2.7.5.tgz", + "integrity": "sha512-j3nPsHrMiCbgm6olZCykxHOxxJ2XXG0CVihMYGOoyenM74Ed+NIaLi1bk2VtxKZdEPB5UrWgU10rd9SLH45BsQ==", + "requires": { + "@devexpress/dx-chart-core": "2.7.5", + "d3-scale": "^3.2.0", + "d3-shape": "^1.3.7" + } + }, + "@devexpress/dx-react-chart-material-ui": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/@devexpress/dx-react-chart-material-ui/-/dx-react-chart-material-ui-2.7.5.tgz", + "integrity": "sha512-uS450uSP1D6mZ2jgtueShqaATNRVqJEwqhgyRQUXL/gQyTrTCvM61TC5NLyEYCOW6RkiHK1BabWbhJlVATZbeg==", + "requires": { + "clsx": "^1.0.4", + "prop-types": "^15.7.2" + } + }, + "@devexpress/dx-react-core": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/@devexpress/dx-react-core/-/dx-react-core-2.7.5.tgz", + "integrity": "sha512-YwJ4l8nnMs/vghqamo8OzirDnrbT1ZNIcMP5xJFJJQIyfYAXvGGDS4yLeYI4cwitBg/d2O4jMOReXDB8tUaDDQ==", + "requires": { + "@devexpress/dx-core": "2.7.5", + "prop-types": "^15.7.2" + } + }, "@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -5416,6 +5459,73 @@ "type": "^1.0.1" } }, + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "d3-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", + "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + }, + "d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "requires": { + "d3-color": "1 - 2" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.4.tgz", + "integrity": "sha512-PG6gtpbPCFqKbvdBEswQcJcTzHC8VEd/XzezF5e68KlkT4/ggELw/nR1tv863jY6ufKTvDlzCMZvhe06codbbA==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "1 - 2", + "d3-time-format": "2 - 3" + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + }, + "d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "requires": { + "d3-time": "1 - 2" + } + }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -8492,6 +8602,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", diff --git a/client/package.json b/client/package.json index f41350a6..2d393d3d 100644 --- a/client/package.json +++ b/client/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "private": true, "dependencies": { + "@devexpress/dx-react-chart": "^2.7.5", + "@devexpress/dx-react-chart-material-ui": "^2.7.5", + "@devexpress/dx-react-core": "^2.7.5", "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx index 921f721d..a0dbe2bd 100644 --- a/client/src/pages/admin/AdminPage.tsx +++ b/client/src/pages/admin/AdminPage.tsx @@ -23,6 +23,7 @@ import { getRoles } from '../../actions/roles' import { logoutUser } from '../../actions/user' import { useAppDispatch, useAppSelector } from '../../hooks' import CompetitionManager from './competitions/CompetitionManager' +import Dashboard from './dashboard/Dashboard' import RegionManager from './regions/Regions' import { LeftDrawer } from './styled' import UserManager from './users/UserManager' @@ -45,7 +46,9 @@ const useStyles = makeStyles((theme: Theme) => content: { flexGrow: 1, backgroundColor: theme.palette.background.default, - paddingLeft: theme.spacing(31), + paddingTop: theme.spacing(2), + paddingLeft: theme.spacing(35), + paddingRight: theme.spacing(5), }, }) ) @@ -138,9 +141,7 @@ const AdminView: React.FC = () => { <div className={classes.toolbar} /> <Switch> <Route exact path={[path, `${path}/startsida`]}> - <Typography variant="h1" noWrap> - Startsida - </Typography> + <Dashboard /> </Route> <Route path={`${path}/regioner`}> <RegionManager /> diff --git a/client/src/pages/admin/dashboard/Dashboard.tsx b/client/src/pages/admin/dashboard/Dashboard.tsx new file mode 100644 index 00000000..a0f569b0 --- /dev/null +++ b/client/src/pages/admin/dashboard/Dashboard.tsx @@ -0,0 +1,58 @@ +import { createStyles, makeStyles, Paper, Theme, Typography } from '@material-ui/core' +import Grid from '@material-ui/core/Grid' +import React from 'react' +import CurrentUser from './components/CurrentUser' +import NumberOfCompetitions from './components/NumberOfCompetitions' +import NumberOfRegions from './components/NumberOfRegions' +import NumberOfUsers from './components/NumberOfUsers' + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + flexGrow: 1, + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + }, + }) +) + +const Dashboard: React.FC = () => { + const classes = useStyles() + return ( + <div className={classes.root}> + <div> + <Grid container spacing={3}> + <Grid item xs={4}> + <Paper className={classes.paper}> + <CurrentUser /> + </Paper> + </Grid> + + <Grid item xs> + <Paper className={classes.paper}> + <Typography variant="h4">Antal Användare:</Typography> + <NumberOfUsers /> + </Paper> + </Grid> + <Grid item xs> + <Paper className={classes.paper}> + <Typography variant="h4">Antal Regioner:</Typography> + <NumberOfRegions /> + </Paper> + </Grid> + <Grid item xs> + <Paper className={classes.paper}> + <Typography variant="h4">Antal Tävlingar:</Typography> + <NumberOfCompetitions /> + </Paper> + </Grid> + </Grid> + </div> + </div> + ) +} + +export default Dashboard diff --git a/client/src/pages/admin/dashboard/components/CurrentUser.tsx b/client/src/pages/admin/dashboard/components/CurrentUser.tsx new file mode 100644 index 00000000..0dfdf557 --- /dev/null +++ b/client/src/pages/admin/dashboard/components/CurrentUser.tsx @@ -0,0 +1,29 @@ +import { Box, Typography } from '@material-ui/core' +import React from 'react' +import { useAppSelector } from '../../../../hooks' + +const CurrentUser: React.FC = () => { + const currentUser = useAppSelector((state: { user: { userInfo: any } }) => state.user.userInfo) + return ( + <div> + <Box display="flex" flexDirection="column" alignContent="flex-start"> + <div> + <Typography variant="h2"> + Välkommen{currentUser && currentUser.name ? `, ${currentUser.name}` : ''}! + </Typography> + </div> + <div> + <Typography variant="h6">Email: {currentUser && currentUser.email}</Typography> + </div> + <div> + <Typography variant="h6">Region: {currentUser && currentUser.city && currentUser.city.name}</Typography> + </div> + <div> + <Typography variant="h6">Roll: {currentUser && currentUser.role && currentUser.role.name}</Typography> + </div> + </Box> + </div> + ) +} + +export default CurrentUser diff --git a/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx new file mode 100644 index 00000000..da5d015f --- /dev/null +++ b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx @@ -0,0 +1,33 @@ +import { Box, Typography } from '@material-ui/core' +import React, { useEffect } from 'react' +import { getSearchUsers } from '../../../../actions/searchUser' +import { useAppDispatch, useAppSelector } from '../../../../hooks' + +const NumberOfCompetitions: React.FC = () => { + const cities = useAppSelector((state) => state.cities.cities) + const dispatch = useAppDispatch() + + const handleCount = () => { + if (cities.length >= 1000000) { + ;<div>{cities.length / 1000000 + 'M'}</div> + } else if (cities.length >= 1000) { + ;<div>{cities.length / 1000 + 'K'}</div> + } + return <div>{cities.length}</div> + } + + useEffect(() => { + dispatch(getSearchUsers()) + }, []) + return ( + <div> + <Box width="100%" height="100%"> + <div> + <Typography variant="h4">{handleCount()}</Typography> + </div> + </Box> + </div> + ) +} + +export default NumberOfCompetitions diff --git a/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx new file mode 100644 index 00000000..a48b41a6 --- /dev/null +++ b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx @@ -0,0 +1,33 @@ +import { Box, Typography } from '@material-ui/core' +import React, { useEffect } from 'react' +import { getSearchUsers } from '../../../../actions/searchUser' +import { useAppDispatch, useAppSelector } from '../../../../hooks' + +const NumberOfRegions: React.FC = () => { + const competitionTotal = useAppSelector((state) => state.competitions.total) + const dispatch = useAppDispatch() + + const handleCount = () => { + if (competitionTotal >= 1000000) { + ;<div>{competitionTotal / 1000000 + 'M'}</div> + } else if (competitionTotal >= 1000) { + ;<div>{competitionTotal / 1000 + 'K'}</div> + } + return <div>{competitionTotal}</div> + } + + useEffect(() => { + dispatch(getSearchUsers()) + }, []) + return ( + <div> + <Box width="100%" height="100%"> + <div> + <Typography variant="h4">{handleCount()}</Typography> + </div> + </Box> + </div> + ) +} + +export default NumberOfRegions diff --git a/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx new file mode 100644 index 00000000..af0f9767 --- /dev/null +++ b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx @@ -0,0 +1,33 @@ +import { Box, Typography } from '@material-ui/core' +import React, { useEffect } from 'react' +import { getSearchUsers } from '../../../../actions/searchUser' +import { useAppDispatch, useAppSelector } from '../../../../hooks' + +const NumberOfUsers: React.FC = () => { + const usersTotal = useAppSelector((state) => state.searchUsers.total) + const dispatch = useAppDispatch() + + const handleCount = () => { + if (usersTotal >= 1000000) { + ;<div>{usersTotal / 1000000 + 'M'}</div> + } else if (usersTotal >= 1000) { + ;<div>{usersTotal / 1000 + 'K'}</div> + } + return <div>{usersTotal}</div> + } + + useEffect(() => { + dispatch(getSearchUsers()) + }, []) + return ( + <div> + <Box width="100%" height="100%"> + <div> + <Typography variant="h4">{handleCount()}</Typography> + </div> + </Box> + </div> + ) +} + +export default NumberOfUsers -- GitLab