├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── auth-illustration.svg
│ ├── imgs
│ │ └── bg
│ │ │ └── error-404.png
│ ├── logo.svg
│ └── logoIcon.svg
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── favicon1.ico
├── index.html
├── manifest.json
└── robots.txt
└── src
├── App.js
├── actions
├── auth.js
├── group.js
└── metrics.js
├── assets
└── svgs
│ ├── heart-beat-svgrepo-com.svg
│ ├── logo.svg
│ ├── logoIcon.svg
│ └── pulse-line-svgrepo-com.svg
├── components
├── BootstrapDialog.js
├── ConfirmModal
│ └── index.js
├── Icons.js
├── chart.js
├── loader
│ ├── CirclesLoader.js
│ └── CirclesLoader.scss
├── logo.js
├── routing
│ ├── MainRoutes.js
│ └── PrivateRoute.js
├── scrollbar.js
└── severity-pill.js
├── hooks
├── use-nprogress.js
├── use-popover.js
└── use-selection.js
├── index.js
├── layouts
├── auth
│ └── layout.js
└── dashboard
│ ├── account-popover.js
│ ├── config.js
│ ├── layout.js
│ ├── side-nav-item.js
│ ├── side-nav.js
│ └── top-nav.js
├── pages
├── Dashboard
│ ├── BloodPressureChart.js
│ ├── Dashboard.js
│ ├── EveryTimeChart.js
│ ├── GroupChart.js
│ ├── OneLineChart.js
│ └── TableForTextMetrics.js
├── Metrics
│ └── Metrics.js
├── MetricsGroup
│ └── MetricsGroup.js
├── NotFound.js
├── TotalMetricsValues
│ └── TotalMetricsValues.js
├── Track
│ ├── EditTrackDialog.js
│ ├── EditTrackDialogForBlood.js
│ └── Track.js
└── auth
│ ├── Login.js
│ └── Register.js
├── reportWebVitals.js
├── sections
├── dashboard
│ ├── to-do-card.js
│ ├── to-do-dialog-for-bloodPressure.js
│ └── to-do-dialog.js
├── group
│ ├── groupCard.js
│ └── metricCard.js
├── metrics
│ ├── metrics-dialog-title.js
│ ├── metrics-dialog.js
│ ├── metrics-search.js
│ └── metrics-table.js
└── overview
│ └── overview-latest-metrics-value.js
├── setupTests.js
├── store
├── authSlice.js
├── groupSlice.js
├── index.js
└── metricsSlice.js
├── styles
└── index.scss
├── theme
├── colors.js
├── create-components.js
├── create-palette.js
├── create-shadows.js
├── create-typography.js
└── index.js
└── utils
├── api.js
├── index.js
├── setAuthToken.js
└── toast.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
72 | ###
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smart-metrics-logbook",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.10.6",
7 | "@emotion/styled": "^11.10.6",
8 | "@fontsource/roboto": "^4.5.8",
9 | "@heroicons/react": "^2.0.16",
10 | "@mui/icons-material": "^5.11.11",
11 | "@mui/lab": "^5.0.0-alpha.125",
12 | "@mui/material": "^5.11.12",
13 | "@mui/styles": "^5.11.13",
14 | "@mui/system": "^5.11.12",
15 | "@mui/x-date-pickers": "^6.0.0-beta.5",
16 | "@reduxjs/toolkit": "^1.9.3",
17 | "@testing-library/jest-dom": "^5.16.5",
18 | "@testing-library/react": "^13.4.0",
19 | "@testing-library/user-event": "^13.5.0",
20 | "apexcharts": "^3.37.1",
21 | "axios": "^1.3.4",
22 | "clsx": "^1.2.1",
23 | "date-fns": "^2.29.3",
24 | "dayjs": "^1.11.7",
25 | "formik": "^2.2.9",
26 | "moment": "^2.29.4",
27 | "moment-timezone": "^0.5.43",
28 | "notistack": "^3.0.1",
29 | "nprogress": "^0.2.0",
30 | "prop-types": "^15.8.1",
31 | "react": "^18.2.0",
32 | "react-apexcharts": "^1.4.0",
33 | "react-beautiful-dnd": "^13.1.1",
34 | "react-chartjs-2": "^5.2.0",
35 | "react-dom": "^18.2.0",
36 | "react-feather": "^2.0.10",
37 | "react-helmet": "^6.1.0",
38 | "react-redux": "^8.0.5",
39 | "react-router-dom": "^6.8.2",
40 | "react-scripts": "5.0.1",
41 | "react-toastify": "^9.1.1",
42 | "recharts": "^2.5.0",
43 | "redux-devtools-extension": "^2.13.9",
44 | "redux-thunk": "^2.4.2",
45 | "sass": "^1.58.3",
46 | "simplebar-react": "^3.2.1",
47 | "web-vitals": "^2.1.4",
48 | "yup": "^1.0.2"
49 | },
50 | "scripts": {
51 | "start": "set port=3040 && react-scripts start",
52 | "build": "react-scripts build",
53 | "test": "react-scripts test",
54 | "eject": "react-scripts eject"
55 | },
56 | "eslintConfig": {
57 | "extends": [
58 | "react-app",
59 | "react-app/jest"
60 | ]
61 | },
62 | "browserslist": {
63 | "production": [
64 | ">0.2%",
65 | "not dead",
66 | "not op_mini all"
67 | ],
68 | "development": [
69 | "last 1 chrome version",
70 | "last 1 firefox version",
71 | "last 1 safari version"
72 | ]
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/public/assets/imgs/bg/error-404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-miro26/Metrics-health-for-react-frontend/3b768129b02faf5ae94a965fa0e811bcedbd3646/public/assets/imgs/bg/error-404.png
--------------------------------------------------------------------------------
/public/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/logoIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-miro26/Metrics-health-for-react-frontend/3b768129b02faf5ae94a965fa0e811bcedbd3646/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-miro26/Metrics-health-for-react-frontend/3b768129b02faf5ae94a965fa0e811bcedbd3646/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-miro26/Metrics-health-for-react-frontend/3b768129b02faf5ae94a965fa0e811bcedbd3646/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-miro26/Metrics-health-for-react-frontend/3b768129b02faf5ae94a965fa0e811bcedbd3646/public/favicon1.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
24 |
28 |
29 |
30 | Smart Metrcis Logbook
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Smart Metrics Logbook",
3 | "name": "Smart Metrics Logbook",
4 | "icons": [
5 | {
6 | "src": "./favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter as Router } from "react-router-dom";
3 | import { ToastContainer } from "react-toastify";
4 | import { createTheme } from "./theme";
5 | import { CssBaseline } from "@mui/material";
6 |
7 | import MainRoutes from "./components/routing/MainRoutes";
8 | import { ThemeProvider } from "@mui/material/styles";
9 | import "react-toastify/dist/ReactToastify.css";
10 | import "simplebar-react/dist/simplebar.min.css";
11 |
12 | import "./styles/index.scss";
13 |
14 | const App = () => {
15 | const theme = createTheme();
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/src/actions/auth.js:
--------------------------------------------------------------------------------
1 | import {
2 | userAuthError,
3 | userLogined,
4 | userLoginSuccess,
5 | userLogOut,
6 | userRegister,
7 | } from "../store/authSlice";
8 | import { api, setAuthToken, toast } from "../utils";
9 | // Load User
10 | export const apiLoadUser = (navigate) => async (dispatch) => {
11 | try {
12 | const res = await api.get("auth/loadUser");
13 |
14 | await dispatch(userLogined(res.data.doc));
15 | } catch (err) {
16 | navigate("/auth/login");
17 | const errors = err?.response?.data?.errors;
18 | if (errors) {
19 | errors.forEach((error) => console.log(error.msg));
20 | }
21 | await dispatch(userAuthError());
22 | }
23 | };
24 |
25 | // Register User
26 | export const apiRegister = (formData) => async (dispatch) => {
27 | try {
28 | const res = await api.post("auth/register", formData);
29 |
30 | dispatch(userRegister(res.data.doc));
31 | setAuthToken(res.data.doc);
32 | toast.success("You are successfully registered!");
33 | dispatch(apiLoadUser());
34 | } catch (err) {
35 | const errors = err?.response?.data?.errors;
36 | if (errors) {
37 | errors.forEach((error) => toast.error(error.msg));
38 | }
39 |
40 | }
41 | };
42 |
43 | // Login User
44 | export const apiLogin = (formData) => async (dispatch) => {
45 | try {
46 | const res = await api.post("auth/login", formData);
47 |
48 | dispatch(userLoginSuccess(res.data.doc));
49 |
50 | dispatch(apiLoadUser());
51 | } catch (err) {
52 | const errors = err?.response?.data?.errors;
53 |
54 | if (errors) {
55 | errors.forEach((error) => toast.error(error.msg));
56 | }
57 | }
58 | };
59 |
60 | // Logout
61 | export const apiLogout = () => userLogOut();
62 |
--------------------------------------------------------------------------------
/src/actions/group.js:
--------------------------------------------------------------------------------
1 | import {
2 | addGroup,
3 | deleteGroup,
4 | getGroups,
5 | groupError,
6 | updateGroup,
7 | } from "../store/groupSlice";
8 | import { api, toast } from "../utils";
9 |
10 | export const apiGetGroupsByUserId = () => async (dispatch) => {
11 | try {
12 | const res = await api.get("group");
13 |
14 | return dispatch(getGroups(res.data.docs));
15 | } catch (err) {
16 | const errors = err?.response?.data?.errors;
17 | if (errors) {
18 | errors.forEach((error) => toast.error(error.msg));
19 | return dispatch(groupError());
20 | }
21 | }
22 | };
23 |
24 | export const apiAddGroup = (formData) => async (dispatch) => {
25 | try {
26 | const res = await api.post("group", formData);
27 |
28 | toast.success("New Group is added!");
29 | return dispatch(addGroup(res.data.doc));
30 | } catch (err) {
31 | console.log(err);
32 | const errors = err?.response.data?.errors;
33 | if (errors) {
34 | errors.forEach((error) => toast.error(error.msg));
35 | return dispatch(groupError());
36 | }
37 | }
38 | };
39 | export const apiUpdateGroup = (formData) => async (dispatch) => {
40 | console.log(formData);
41 | try {
42 | const res = await api.put("group", formData);
43 |
44 | toast.success(" Group is updated!");
45 | return dispatch(updateGroup(res.data.doc));
46 | } catch (err) {
47 | const errors = err?.response.data?.errors;
48 | if (errors) {
49 | errors.forEach((error) => error && toast.error(error.msg));
50 | return dispatch(groupError());
51 | }
52 | }
53 | };
54 |
55 | export const apiDeleteGroupById = (_id) => async (dispatch) => {
56 | try {
57 | await api.delete(`group?_id=${_id}`);
58 | toast.success("The Group has been successfully deleted.");
59 | return dispatch(deleteGroup({ _id: _id }));
60 | } catch (err) {
61 | const errors = err?.response.data?.errors;
62 | if (errors) {
63 | errors.forEach((error) => toast.error(error.msg));
64 | }
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/src/actions/metrics.js:
--------------------------------------------------------------------------------
1 | import {
2 | addMetric,
3 | metricsError,
4 | getUserMetrics,
5 | deleteMetric,
6 | updateMetric,
7 | addMetricWage,
8 | deleteMetricWage,
9 | getMetricsAllWages,
10 | getMetricsTodayWages,
11 | updateMetricsWage,
12 | getMetricsLastestWages,
13 | } from "../store/metricsSlice";
14 | import { api, toast } from "../utils";
15 |
16 | export const apiAddMetric = (formData) => async (dispatch) => {
17 | console.log("@@@", formData);
18 |
19 | try {
20 | const res = await api.post("metrics", formData);
21 |
22 | toast.success("New metrics is added!");
23 | return dispatch(addMetric(res.data.doc));
24 | } catch (err) {
25 | const errors = err?.response?.data?.errors;
26 | if (errors) {
27 | errors.forEach((error) => toast.error(error.msg));
28 | return dispatch(metricsError());
29 | }
30 | }
31 | };
32 | export const apiUpdateMetric = (formData) => async (dispatch) => {
33 | console.log(formData);
34 | try {
35 | const res = await api.put("metrics", formData);
36 |
37 | toast.success(" Metrics is updated!");
38 | return dispatch(updateMetric(res.data.doc));
39 | } catch (err) {
40 | const errors = err?.response?.data?.errors;
41 | if (errors) {
42 | errors.forEach((error) => toast.error(error.msg));
43 | return dispatch(metricsError());
44 | }
45 | }
46 | };
47 | export const apiGetMetricsByUserId = () => async (dispatch) => {
48 | try {
49 | const res = await api.get("metrics");
50 |
51 | return dispatch(getUserMetrics(res.data.docs));
52 | } catch (err) {
53 | const errors = err?.response?.data?.errors;
54 | if (errors) {
55 | errors.forEach((error) => toast.error(error.msg));
56 | return dispatch(metricsError());
57 | }
58 | }
59 | };
60 |
61 | export const apiDeleteMetricById = (_id) => async (dispatch) => {
62 | try {
63 | await api.delete(`metrics?_id=${_id}`);
64 | toast.success("The metric has been successfully deleted.");
65 | return dispatch(deleteMetric({ _id: _id }));
66 | } catch (err) {
67 | const errors = err?.response?.data?.errors;
68 | if (errors) {
69 | errors.forEach((error) => toast.error(error.msg));
70 | // return dispatch(metricsError());
71 | }
72 | }
73 | };
74 |
75 | export const apiAddMetricWage = (wage) => async (dispatch) => {
76 | try {
77 | const res = await api.post("metrics/wage", wage);
78 |
79 | toast.success(" Metrics Value is added!");
80 | // dispatch(apiGetMetricsLastestWagesByUserId());
81 | return dispatch(addMetricWage(res.data.doc));
82 | } catch (err) {
83 | const errors = err?.response?.data?.errors;
84 | if (errors) {
85 | errors.forEach((error) => toast.error(error.msg));
86 | return dispatch(metricsError());
87 | }
88 | }
89 | };
90 |
91 | export const apiDeleteMetricWageById = (_id) => async (dispatch) => {
92 | try {
93 | await api.delete(`metrics/wage?_id=${_id}`);
94 | await dispatch(apiGetMetricsLastestWagesByUserId());
95 | toast.success("The metric value has been successfully deleted.");
96 | return dispatch(deleteMetricWage({ _id: _id }));
97 | } catch (err) {
98 | const errors = err?.response?.data?.errors;
99 | if (errors) {
100 | errors.forEach((error) => toast.error(error.msg));
101 | }
102 | }
103 | };
104 |
105 | export const apiUpdateMetricWage = (formData) => async (dispatch) => {
106 | try {
107 | const res = await api.put("metrics/wage", formData);
108 | await dispatch(apiGetMetricsLastestWagesByUserId());
109 | console.log(res.data.doc);
110 | toast.success(" Metrics is updated!");
111 | return dispatch(updateMetricsWage(res.data.doc));
112 | } catch (err) {
113 | const errors = err?.response?.data?.errors;
114 | if (errors) {
115 | errors.forEach((error) => toast.error(error.msg));
116 | return dispatch(metricsError());
117 | }
118 | }
119 | };
120 | export const apiGetMetricsAllWagesByUserId = () => async (dispatch) => {
121 | try {
122 | const res = await api.get("metrics/wage");
123 |
124 | return dispatch(getMetricsAllWages(res.data.docs));
125 | } catch (err) {
126 | const errors = err?.response?.data?.errors;
127 | if (errors) {
128 | errors.forEach((error) => toast.error(error.msg));
129 | return dispatch(metricsError());
130 | }
131 | }
132 | };
133 | export const apiGetMetricsTodayWagesByUserId = () => async (dispatch) => {
134 | try {
135 | const res = await api.get("metrics/wage/today");
136 | return dispatch(getMetricsTodayWages(res.data.docs));
137 | } catch (err) {
138 | const errors = err?.response?.data?.errors;
139 | if (errors) {
140 | errors.forEach((error) => toast.error(error.msg));
141 | return dispatch(metricsError());
142 | }
143 | }
144 | };
145 | export const apiGetMetricsLastestWagesByUserId = () => async (dispatch) => {
146 | try {
147 | const res = await api.get("metrics/wage/lastest");
148 |
149 | return dispatch(getMetricsLastestWages(res.data.docs));
150 | } catch (err) {
151 | const errors = err?.response?.data?.errors;
152 | if (errors) {
153 | errors.forEach((error) => toast.error(error.msg));
154 | return dispatch(metricsError());
155 | }
156 | }
157 | };
158 |
--------------------------------------------------------------------------------
/src/assets/svgs/heart-beat-svgrepo-com.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/svgs/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/logoIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/pulse-line-svgrepo-com.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/components/BootstrapDialog.js:
--------------------------------------------------------------------------------
1 | import { styled } from "@mui/material/styles";
2 | import Dialog from "@mui/material/Dialog";
3 |
4 | export const BootstrapDialog = styled(Dialog)(({ theme }) => ({
5 | "& .MuiDialogContent-root": {
6 | padding: theme.spacing(3),
7 | },
8 | "& .MuiDialogActions-root": {
9 | padding: theme.spacing(1),
10 | },
11 | }));
12 |
--------------------------------------------------------------------------------
/src/components/ConfirmModal/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Dialog,
4 | DialogTitle,
5 | DialogContent,
6 | DialogContentText,
7 | DialogActions,
8 | Button,
9 | } from "@mui/material";
10 |
11 | const ConfirmDialog = ({ openDialog, onCancel, onOK, title, content }) => {
12 | return (
13 |
33 | );
34 | };
35 |
36 | export default ConfirmDialog;
37 |
--------------------------------------------------------------------------------
/src/components/Icons.js:
--------------------------------------------------------------------------------
1 | import { ReactComponent as Heart } from "../assets/svgs/heart-beat-svgrepo-com.svg";
2 | import { ReactComponent as BP } from "../assets/svgs/pulse-line-svgrepo-com.svg";
3 |
4 | export { Heart as IconHeart, BP as IconBP };
5 |
--------------------------------------------------------------------------------
/src/components/chart.js:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from "react";
2 | import { styled } from "@mui/material/styles";
3 |
4 | const ApexChart = lazy(() => import("react-apexcharts"), {
5 | ssr: false,
6 | loading: () => null,
7 | });
8 |
9 | export const Chart = styled(ApexChart);
10 |
--------------------------------------------------------------------------------
/src/components/loader/CirclesLoader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, Typography } from "@mui/material";
3 |
4 | import "./CirclesLoader.scss";
5 |
6 | function CirclesLoader() {
7 | return (
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Loading ...
27 |
28 |
29 | );
30 | }
31 |
32 | export default CirclesLoader;
33 |
--------------------------------------------------------------------------------
/src/components/loader/CirclesLoader.scss:
--------------------------------------------------------------------------------
1 | .circles-loader {
2 | display: inline-block;
3 | position: relative;
4 | width: 80px;
5 | height: 80px;
6 | z-index: 1000;
7 | }
8 | .circles-loader div {
9 | animation: circles-loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
10 | transform-origin: 40px 40px;
11 | }
12 | .circles-loader div:after {
13 | content: " ";
14 | display: block;
15 | position: absolute;
16 | width: 7px;
17 | height: 7px;
18 | border-radius: 50%;
19 | background: #797979;
20 | margin: -4px 0 0 -4px;
21 | }
22 | .circles-loader div:nth-child(1) {
23 | animation-delay: -0.036s;
24 | }
25 | .circles-loader div:nth-child(1):after {
26 | top: 63px;
27 | left: 63px;
28 | }
29 | .circles-loader div:nth-child(2) {
30 | animation-delay: -0.072s;
31 | }
32 | .circles-loader div:nth-child(2):after {
33 | top: 68px;
34 | left: 56px;
35 | }
36 | .circles-loader div:nth-child(3) {
37 | animation-delay: -0.108s;
38 | }
39 | .circles-loader div:nth-child(3):after {
40 | top: 71px;
41 | left: 48px;
42 | }
43 | .circles-loader div:nth-child(4) {
44 | animation-delay: -0.144s;
45 | }
46 | .circles-loader div:nth-child(4):after {
47 | top: 72px;
48 | left: 40px;
49 | }
50 | .circles-loader div:nth-child(5) {
51 | animation-delay: -0.18s;
52 | }
53 | .circles-loader div:nth-child(5):after {
54 | top: 71px;
55 | left: 32px;
56 | }
57 | .circles-loader div:nth-child(6) {
58 | animation-delay: -0.216s;
59 | }
60 | .circles-loader div:nth-child(6):after {
61 | top: 68px;
62 | left: 24px;
63 | }
64 | .circles-loader div:nth-child(7) {
65 | animation-delay: -0.252s;
66 | }
67 | .circles-loader div:nth-child(7):after {
68 | top: 63px;
69 | left: 17px;
70 | }
71 | .circles-loader div:nth-child(8) {
72 | animation-delay: -0.288s;
73 | }
74 | .circles-loader div:nth-child(8):after {
75 | top: 56px;
76 | left: 12px;
77 | }
78 | @keyframes circles-loader {
79 | 0% {
80 | transform: rotate(0deg);
81 | }
82 | 100% {
83 | transform: rotate(360deg);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/logo.js:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material/styles';
2 |
3 | export const Logo = () => {
4 | const theme = useTheme();
5 | const fillColor = theme.palette.primary.main;
6 |
7 | return (
8 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/routing/MainRoutes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Routes, useNavigate } from "react-router-dom";
3 | import PrivateRoute from "./PrivateRoute";
4 | import Login from "../../pages/auth/Login.js";
5 | import Register from "../../pages/auth/Register.js";
6 | import Dashboard from "../../pages/Dashboard/Dashboard";
7 | import Metrics from "../../pages/Metrics/Metrics";
8 | import NotFound from "../../pages/NotFound.js";
9 | import MetricsGroup from "../../pages/MetricsGroup/MetricsGroup";
10 | import TotalMetricsValues from "../../pages/TotalMetricsValues/TotalMetricsValues";
11 | import Track from "../../pages/Track/Track";
12 | import { setAuthToken } from "../../utils";
13 | import store from "../../store";
14 | import { apiLoadUser } from "../../actions/auth";
15 | import { userLogOut } from "../../store/authSlice";
16 | const MainRoutes = () => {
17 | const navigate = useNavigate();
18 | React.useEffect(() => {
19 | if (localStorage.getItem("smart-metrics-logbook")) {
20 | setAuthToken(localStorage.getItem("smart-metrics-logbook"));
21 | store.dispatch(apiLoadUser(navigate));
22 | } else {
23 | store.dispatch(userLogOut());
24 | navigate("api/login");
25 | localStorage.removeItem("smart-metrics-logbook");
26 | }
27 | window.addEventListener("storage", () => { });
28 | // eslint-disable-next-line react-hooks/exhaustive-deps
29 | }, []);
30 | return (
31 |
32 | } />
33 | }>
34 | }>
35 | } />
36 | } />
37 | }
40 | />
41 | }
44 | />
45 | } />
46 |
47 | );
48 | };
49 |
50 | export default MainRoutes;
51 |
--------------------------------------------------------------------------------
/src/components/routing/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import { connect, useSelector } from "react-redux";
5 | import CirclesLoader from "../loader/CirclesLoader";
6 |
7 | const PrivateRoute = ({ component: Component }) => {
8 | const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
9 | const loading = useSelector((state) => state.auth.loading);
10 | if (loading) return ;
11 | if (isAuthenticated) return ;
12 |
13 | return ;
14 | };
15 |
16 | PrivateRoute.propTypes = {
17 | auth: PropTypes.object.isRequired,
18 | };
19 |
20 | const mapStateToProps = (state) => ({
21 | auth: state.auth,
22 | });
23 |
24 | export default connect(mapStateToProps)(PrivateRoute);
25 |
--------------------------------------------------------------------------------
/src/components/scrollbar.js:
--------------------------------------------------------------------------------
1 | import SimpleBar from 'simplebar-react';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const Scrollbar = styled(SimpleBar)``;
5 |
--------------------------------------------------------------------------------
/src/components/severity-pill.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const SeverityPillRoot = styled('span')(({ theme, ownerState }) => {
5 | const backgroundColor = theme.palette[ownerState.color].alpha12;
6 | const color = theme.palette.mode === 'dark'
7 | ? theme.palette[ownerState.color].main
8 | : theme.palette[ownerState.color].dark;
9 |
10 | return {
11 | alignItems: 'center',
12 | backgroundColor,
13 | borderRadius: 12,
14 | color,
15 | cursor: 'default',
16 | display: 'inline-flex',
17 | flexGrow: 0,
18 | flexShrink: 0,
19 | fontFamily: theme.typography.fontFamily,
20 | fontSize: theme.typography.pxToRem(12),
21 | lineHeight: 2,
22 | fontWeight: 600,
23 | justifyContent: 'center',
24 | letterSpacing: 0.5,
25 | minWidth: 20,
26 | paddingLeft: theme.spacing(1),
27 | paddingRight: theme.spacing(1),
28 | textTransform: 'uppercase',
29 | whiteSpace: 'nowrap'
30 | };
31 | });
32 |
33 | export const SeverityPill = (props) => {
34 | const { color = 'primary', children, ...other } = props;
35 |
36 | const ownerState = { color };
37 |
38 | return (
39 |
43 | {children}
44 |
45 | );
46 | };
47 |
48 | SeverityPill.propTypes = {
49 | children: PropTypes.node,
50 | color: PropTypes.oneOf([
51 | 'primary',
52 | 'secondary',
53 | 'error',
54 | 'info',
55 | 'warning',
56 | 'success'
57 | ])
58 | };
59 |
--------------------------------------------------------------------------------
/src/hooks/use-nprogress.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import nprogress from "nprogress";
3 | import { useNavigate } from "react-router-dom";
4 |
5 | export function useNProgress() {
6 | // const navigate = useNavigate();
7 | // useEffect(() => {
8 | // const startProgressBar = () => {
9 | // nprogress.start();
10 | // };
11 | // const stopProgressBar = () => {
12 | // nprogress.done();
13 | // };
14 | // navigate.listen(startProgressBar);
15 | // return () => {
16 | // navigate.listen(stopProgressBar);
17 | // };
18 | // }, [navigate]);
19 | }
20 |
--------------------------------------------------------------------------------
/src/hooks/use-popover.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef, useState } from "react";
2 |
3 | export function usePopover() {
4 | const anchorRef = useRef(null);
5 | const [open, setOpen] = useState(false);
6 |
7 | const handleOpen = useCallback(() => {
8 | setOpen(true);
9 | }, []);
10 |
11 | const handleClose = useCallback(() => {
12 | setOpen(false);
13 | }, []);
14 |
15 | const handleToggle = useCallback(() => {
16 | setOpen((prevState) => !prevState);
17 | }, []);
18 |
19 | return {
20 | anchorRef,
21 | handleClose,
22 | handleOpen,
23 | handleToggle,
24 | open,
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/use-selection.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from "react";
2 |
3 | export const useSelection = (items = []) => {
4 | const [selected, setSelected] = useState([]);
5 |
6 | useEffect(() => {
7 | setSelected([]);
8 | }, [items]);
9 |
10 | const handleSelectAll = useCallback(() => {
11 | setSelected([...items]);
12 | }, [items]);
13 |
14 | const handleSelectOne = useCallback((item) => {
15 | setSelected((prevState) => [...prevState, item]);
16 | }, []);
17 |
18 | const handleDeselectAll = useCallback(() => {
19 | setSelected([]);
20 | }, []);
21 |
22 | const handleDeselectOne = useCallback((item) => {
23 | setSelected((prevState) => {
24 | return prevState?.filter((_item) => _item !== item);
25 | });
26 | }, []);
27 |
28 | return {
29 | handleDeselectAll,
30 | handleDeselectOne,
31 | handleSelectAll,
32 | handleSelectOne,
33 | selected,
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.js";
4 | import reportWebVitals from "./reportWebVitals";
5 | import { Provider } from "react-redux";
6 |
7 | import store from "./store/index";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/src/layouts/auth/layout.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { Box, Typography, Unstable_Grid2 as Grid } from "@mui/material";
3 |
4 |
5 | export const Layout = (props) => {
6 | const { children } = props;
7 |
8 | return (
9 |
16 |
17 |
27 |
37 |
45 |
46 |
47 |
48 | {children}
49 |
50 |
65 |
66 |
76 | Welcome to{" "}
77 |
78 | Smart Metrics Logbook
79 |
80 |
81 |
82 | You can track your health status and exercise metrics at any time
83 | here.
84 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | Layout.prototypes = {
94 | children: PropTypes.node,
95 | };
96 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/account-popover.js:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import { Navigate } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import {
5 | Box,
6 | Divider,
7 | MenuItem,
8 | MenuList,
9 | Popover,
10 | Typography,
11 | } from "@mui/material";
12 | import { useSelector } from "react-redux";
13 |
14 | export const AccountPopover = (props) => {
15 | const { anchorEl, onClose, open } = props;
16 | const auth = useSelector((state) => state.auth.user);
17 |
18 | const handleSignOut = useCallback(() => {
19 | onClose?.();
20 | props.onAction();
21 | return ;
22 |
23 | // eslint-disable-next-line
24 | }, []);
25 |
26 | return (
27 |
37 |
43 | Account
44 |
45 | {auth?.name}
46 |
47 |
48 |
49 | *": {
55 | borderRadius: 1,
56 | },
57 | }}
58 | >
59 |
67 |
68 |
69 | );
70 | };
71 |
72 | AccountPopover.propTypes = {
73 | anchorEl: PropTypes.any,
74 | onClose: PropTypes.func,
75 | open: PropTypes.bool.isRequired,
76 | };
77 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/config.js:
--------------------------------------------------------------------------------
1 | import ChartBarIcon from "@heroicons/react/24/solid/ChartBarIcon";
2 |
3 | import ViewListIcon from "@mui/icons-material/ViewList";
4 | import { SvgIcon } from "@mui/material";
5 | import DnsIcon from "@mui/icons-material/Dns";
6 | export const items = [
7 | {
8 | title: "Dashboard",
9 | path: "/",
10 | icon: (
11 |
12 |
13 |
14 | ),
15 | },
16 | {
17 | title: "Metrics",
18 | path: "/metrics",
19 | icon: (
20 |
21 |
22 |
23 | ),
24 | },
25 | {
26 | title: "Metrics Group",
27 | path: "/group",
28 | icon: (
29 |
30 |
31 |
32 | ),
33 | },
34 |
35 | ];
36 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/layout.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { styled } from "@mui/material/styles";
3 | import { SideNav } from "./side-nav";
4 | import { TopNav } from "./top-nav";
5 | import { useLocation } from "react-router-dom";
6 |
7 | const SIDE_NAV_WIDTH = 280;
8 |
9 | const LayoutRoot = styled("div")(({ theme }) => ({
10 | display: "flex",
11 | flex: "1 1 auto",
12 | maxWidth: "100%",
13 | background: "#fbfbfb",
14 | [theme.breakpoints.up("lg")]: {
15 | paddingLeft: SIDE_NAV_WIDTH,
16 | },
17 | }));
18 |
19 | const LayoutContainer = styled("div")({
20 | display: "flex",
21 | flex: "1 1 auto",
22 | flexDirection: "column",
23 | width: "100%",
24 | });
25 |
26 | export const Layout = (props) => {
27 | const { children } = props;
28 | const location = useLocation();
29 | const pathname = location.pathname;
30 | const [openNav, setOpenNav] = useState(true);
31 |
32 | useEffect(() => {
33 | if (openNav) {
34 | setOpenNav(true);
35 | }
36 | }, [pathname, openNav]);
37 |
38 | return (
39 | <>
40 | setOpenNav(true)} onAction={props.onAction} />
41 | setOpenNav(false)} open={openNav} />
42 |
43 | {children}
44 |
45 | >
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/side-nav-item.js:
--------------------------------------------------------------------------------
1 | import { Link as RouterLink } from "react-router-dom";
2 | import PropTypes from "prop-types";
3 | import { Box, ButtonBase } from "@mui/material";
4 |
5 | export const SideNavItem = (props) => {
6 | const { active = false, disabled, external, icon, path, title } = props;
7 |
8 | const linkProps = path
9 | ? external
10 | ? {
11 | component: "a",
12 | to: path,
13 | target: "_blank",
14 | }
15 | : {
16 | component: RouterLink,
17 | to: path,
18 | }
19 | : {};
20 |
21 | return (
22 |
23 |
43 | {icon && (
44 |
57 | {icon}
58 |
59 | )}
60 | theme.typography.fontFamily,
66 | fontSize: 14,
67 | fontWeight: 600,
68 | lineHeight: "24px",
69 | whiteSpace: "nowrap",
70 | ...(active && {
71 | color: "common.white",
72 | }),
73 | ...(disabled && {
74 | color: "neutral.500",
75 | }),
76 | }}
77 | >
78 | {title}
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | SideNavItem.propTypes = {
86 | active: PropTypes.bool,
87 | disabled: PropTypes.bool,
88 | external: PropTypes.bool,
89 | icon: PropTypes.node,
90 | path: PropTypes.string,
91 | title: PropTypes.string.isRequired,
92 | };
93 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/side-nav.js:
--------------------------------------------------------------------------------
1 | import { Link as RouterLink, useLocation } from "react-router-dom";
2 | import PropTypes from "prop-types";
3 | import {
4 | Box,
5 | Divider,
6 | Drawer,
7 | Stack,
8 | Typography,
9 | useMediaQuery, IconButton
10 | } from "@mui/material";
11 | import { Scrollbar } from "../../components/scrollbar";
12 | import { items } from "./config";
13 | import { SideNavItem } from "./side-nav-item";
14 | import CloseIcon from '@mui/icons-material/Close';
15 |
16 | export const SideNav = (props) => {
17 | const { open, onClose } = props;
18 | const location = useLocation();
19 | const pathname = location.pathname;
20 | const lgUp = useMediaQuery((theme) => theme.breakpoints.up("lg"));
21 |
22 | const content = (
23 |
34 |
41 |
42 | {!lgUp &&
43 |
44 |
45 |
46 |
47 |
48 | }
49 |
50 |
59 | {/* */}
60 |
61 |
62 |
74 |
75 | Smart Metrics Logbook
76 |
77 |
78 |
79 |
80 |
81 |
89 |
98 | {items.map((item) => {
99 | const active = item.path ? pathname === item.path : false;
100 |
101 | return (
102 |
111 | );
112 | })}
113 |
114 |
115 |
116 |
122 |
123 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | );
141 |
142 | if (lgUp) {
143 | return (
144 |
156 | {content}
157 |
158 | );
159 | }
160 |
161 | return (
162 | theme.zIndex.appBar + 100 }}
174 | variant="temporary"
175 | >
176 | {content}
177 |
178 | );
179 | };
180 |
181 | SideNav.propTypes = {
182 | onClose: PropTypes.func,
183 | open: PropTypes.bool,
184 | };
185 |
--------------------------------------------------------------------------------
/src/layouts/dashboard/top-nav.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import BellIcon from "@heroicons/react/24/solid/BellIcon";
3 | import UsersIcon from "@heroicons/react/24/solid/UsersIcon";
4 | import Bars3Icon from "@heroicons/react/24/solid/Bars3Icon";
5 | import MagnifyingGlassIcon from "@heroicons/react/24/solid/MagnifyingGlassIcon";
6 | import {
7 | Avatar,
8 | Badge,
9 | Box,
10 | IconButton,
11 | Stack,
12 | SvgIcon,
13 | Tooltip,
14 | useMediaQuery,
15 | } from "@mui/material";
16 | import { alpha } from "@mui/material/styles";
17 | import { usePopover } from "../../hooks/use-popover";
18 | import { AccountPopover } from "./account-popover";
19 |
20 | const SIDE_NAV_WIDTH = 280;
21 | const TOP_NAV_HEIGHT = 64;
22 |
23 | export const TopNav = (props) => {
24 | const { onNavOpen } = props;
25 | const lgUp = useMediaQuery((theme) => theme.breakpoints.up("lg"));
26 | const accountPopover = usePopover();
27 |
28 | return (
29 | <>
30 |
35 | alpha(theme.palette.background.default, 0.8),
36 | position: "sticky",
37 | left: {
38 | lg: `${SIDE_NAV_WIDTH}px`,
39 | },
40 | top: 0,
41 | width: {
42 | lg: `calc(100% - ${SIDE_NAV_WIDTH}px)`,
43 | },
44 | zIndex: (theme) => theme.zIndex.appBar,
45 | }}
46 | >
47 |
57 |
58 | { (
59 |
60 |
61 |
62 |
63 |
64 | )}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
100 |
101 |
102 |
103 |
109 | >
110 | );
111 | };
112 |
113 | TopNav.propTypes = {
114 | onNavOpen: PropTypes.func,
115 | };
116 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/BloodPressureChart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { useSelector } from "react-redux";
4 | import ReactApexChart from "react-apexcharts";
5 | import moment from "moment";
6 | const BloodChart = ({
7 | data,
8 | filterStartDay,
9 | filterEndDay,
10 | selectedShowChatMetric,
11 | }) => {
12 | const metric = useSelector((state) => state.metrics.metrics).filter(
13 | (metric) => metric._id === selectedShowChatMetric
14 | )[0];
15 | const ignore = metric.ignore;
16 | const sortedData = data.sort(
17 | (a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)
18 | );
19 |
20 | const dateArray = [];
21 | const hP = [];
22 | const lP = [];
23 | const hR = [];
24 |
25 | for (
26 | let currentDate = filterStartDay.clone();
27 | currentDate <= filterEndDay;
28 | currentDate.add(1, "days")
29 | ) {
30 | dateArray.push(currentDate.format("DD/MMMM"));
31 |
32 | const mm = sortedData.filter(
33 | (d) =>
34 | moment(d.createdAt).local().format("YYYY-MM-DD") ===
35 | currentDate.local().format("YYYY-MM-DD")
36 | );
37 | const total = mm[0]?.wage?.split(",");
38 |
39 |
40 | if (currentDate.format("DD/MMMM") === filterStartDay.format("DD/MMMM")) {
41 | mm[0]
42 | ? hP.push({
43 | x: currentDate.format("DD/MMMM"),
44 | y: parseInt(total[0]),
45 | })
46 | : hP.push({
47 | x: currentDate.format("DD/MMMM"),
48 | y: 0,
49 | });
50 | mm[0]
51 | ? lP.push({
52 | x: currentDate.format("DD/MMMM"),
53 | y: parseInt(total[1]),
54 | })
55 | : lP.push({
56 | x: currentDate.format("DD/MMMM"),
57 | y: 0,
58 | });
59 | mm[0]
60 | ? hR.push({
61 | x: currentDate.format("DD/MMMM"),
62 | y: parseInt(total[2]),
63 | })
64 | : hR.push({
65 | x: currentDate.format("DD/MMMM"),
66 | y: 0,
67 | });
68 | } else if (
69 | currentDate.format("DD/MMMM") === filterEndDay.format("DD/MMMM")
70 | ) {
71 | mm[0]
72 | ? hP.push({
73 | x: currentDate.format("DD/MMMM"),
74 | y: parseInt(total[0]),
75 | })
76 | : hP.push({
77 | x: currentDate.format("DD/MMMM"),
78 | y: 0,
79 | });
80 | mm[0]
81 | ? lP.push({
82 | x: currentDate.format("DD/MMMM"),
83 | y: parseInt(total[1]),
84 | })
85 | : lP.push({
86 | x: currentDate.format("DD/MMMM"),
87 | y: 0,
88 | });
89 | mm[0]
90 | ? hR.push({
91 | x: currentDate.format("DD/MMMM"),
92 | y: parseInt(total[2]),
93 | })
94 | : hR.push({
95 | x: currentDate.format("DD/MMMM"),
96 | y: 0,
97 | });
98 | } else {
99 | mm[0]
100 | ? hP.push({
101 | x: currentDate.format("DD/MMMM"),
102 | y: parseInt(total[0]),
103 | })
104 | : !ignore &&
105 | hP.push({
106 | x: currentDate.format("DD/MMMM"),
107 | y: 0,
108 | });
109 | mm[0]
110 | ? lP.push({
111 | x: currentDate.format("DD/MMMM"),
112 | y: parseInt(total[1]),
113 | })
114 | : !ignore &&
115 | lP.push({
116 | x: currentDate.format("DD/MMMM"),
117 | y: 0,
118 | });
119 | mm[0]
120 | ? hR.push({
121 | x: currentDate.format("DD/MMMM"),
122 | y: parseInt(total[2]),
123 | })
124 | : !ignore &&
125 | hR.push({
126 | x: currentDate.format("DD/MMMM"),
127 | y: 0,
128 | });
129 | }
130 | }
131 | console.log(hP, lP, hR);
132 | const state = {
133 | options: {
134 | colors: ["#008FFB", "#00E396", "#F04438", "#FF4560"],
135 | chart: {
136 | id: "basic-line",
137 | dropShadow: {
138 | enabled: true,
139 | top: 4,
140 | left: 4,
141 | blur: 3,
142 | opacity: 0.2,
143 | },
144 | background: "#ffffff",
145 | },
146 | xaxis: {
147 | categories: dateArray,
148 | },
149 | stroke: {
150 | curve: "smooth",
151 | },
152 | title: {
153 | text: metric.name,
154 | },
155 | markers: {
156 | size: 6,
157 | strokeWidth: 0,
158 | colors: ["#FFA41B"],
159 | hover: {
160 | size: 8,
161 | },
162 | },
163 |
164 | tooltip: {
165 | theme: "dark",
166 | x: {
167 | show: true,
168 | format: "MMM",
169 | },
170 | y: [
171 | {
172 | title: {
173 | formatter: function () {
174 | return "";
175 | },
176 | },
177 | },
178 | {
179 | title: {
180 | formatter: function () {
181 | return "";
182 | },
183 | },
184 | },
185 | ],
186 | },
187 | },
188 | series: [
189 | {
190 | name: "Systolic pressure",
191 | data: hP,
192 | },
193 | {
194 | name: "Diastolic pressure ",
195 | data: lP,
196 | },
197 | {
198 | name: "Heart Rate",
199 | data: hR,
200 | },
201 | ],
202 | };
203 |
204 | return (
205 |
211 | );
212 | };
213 |
214 | export default BloodChart;
215 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/EveryTimeChart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import ReactApexChart from "react-apexcharts";
4 | import moment from "moment-timezone";
5 | const EveryTiimeChart = ({ data, selectedShowChatMetric, everyTimeDate }) => {
6 | const metric = useSelector((state) => state.metrics.metrics).filter(
7 | (metric) => metric._id === selectedShowChatMetric
8 | )[0];
9 | const sortedData = data.sort(
10 | (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
11 | );
12 | const Today = moment(everyTimeDate).format("YYYY-MM-DD").toString();
13 | function getDataBetweenDates(data) {
14 | const result = [];
15 | console.log(Today);
16 |
17 | data.forEach((item) => {
18 | const createdAt = moment(item.createdAt);
19 | console.log(createdAt.isSame(moment(Today), "day"));
20 | if (createdAt.isSame(moment(Today), "day") === true) {
21 | result.push(item);
22 | }
23 | });
24 | return result;
25 | }
26 | const realDate = getDataBetweenDates(sortedData);
27 | console.log(realDate, selectedShowChatMetric);
28 |
29 | const valueArray = [];
30 | for (let dateIndex = 0; dateIndex < realDate.length; dateIndex++) {
31 | valueArray.push({
32 | x: moment(data[dateIndex]?.createdAt),
33 | y: data[dateIndex]?.wage,
34 | });
35 | }
36 | console.log(valueArray);
37 | const times = [
38 | "00:00",
39 | "01:30",
40 | "03:00",
41 | "04:30",
42 | "06:00",
43 | "07:30",
44 | "09:00",
45 | "10:30",
46 | "12:00",
47 | "13:30",
48 | "15:00",
49 | "16:30",
50 | "18:00",
51 | "19:30",
52 | "21:00",
53 | "22:30",
54 | ];
55 | const state = {
56 | colors: ["#00e396"],
57 | options: {
58 | chart: {
59 | id: "basic-line",
60 | height: 350,
61 | },
62 | xaxis: {
63 | tickAmount: 8,
64 | type: "datetime",
65 | categories: valueArray ? "" : times,
66 | labels: {
67 | formatter: function (val, timestamp) {
68 | return moment(new Date(timestamp)).format("HH:mm:ss");
69 | },
70 | },
71 | },
72 | yaxis: {
73 | show: true,
74 | labels: {
75 | show: true,
76 |
77 | formatter: function (value) {
78 | return value;
79 | },
80 | },
81 | },
82 | stroke: {
83 | curve: "smooth",
84 | width: 2,
85 | },
86 | title: {
87 | text: metric?.name,
88 | },
89 | dataLabels: {
90 | enabled: true,
91 | formatter: function (val) {
92 | return val === 0 ? "" : val;
93 | },
94 |
95 | style: {
96 | fontSize: "12px",
97 | },
98 | },
99 | tooltip: {
100 | theme: "dark",
101 | x: {
102 | show: true,
103 | format: "MMM",
104 | },
105 | y: {
106 | show: true,
107 | title: {
108 | formatter: function () {
109 | return `/${metric.postfix}`;
110 | },
111 | },
112 | },
113 | },
114 | tickAmount: 7,
115 | },
116 | series: [
117 | {
118 | name: "date",
119 | data: valueArray,
120 | },
121 | ],
122 | };
123 |
124 | return (
125 |
131 | );
132 | };
133 |
134 | export default EveryTiimeChart;
135 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/GroupChart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Chart from "react-apexcharts";
3 | import { useSelector } from "react-redux";
4 | import moment from "moment";
5 |
6 | export default function GroupChart({
7 | filterStartDay,
8 | filterEndDay,
9 | selectedGroup,
10 | metrics,
11 | allWages,
12 | setAllData,
13 | // allData,
14 | }) {
15 | const groups = useSelector((state) => state.group.groups);
16 |
17 | const dateArray = [];
18 | for (
19 | let currentDate = filterStartDay.clone();
20 | currentDate <= filterEndDay;
21 | currentDate.add(1, "days")
22 | ) {
23 | dateArray.push(currentDate.local().format("DD/MMMM"));
24 | }
25 | const realGroupMetrics = [];
26 | const mainGroup = groups.filter((group) => group?._id === selectedGroup)[0];
27 |
28 | // eslint-disable-next-line array-callback-return
29 | mainGroup?.contents?.map((content) => {
30 | const filterMetric = metrics.filter(
31 | (metric) => metric?._id === content
32 | )[0];
33 | filterMetric?.fieldType !== "bloodPressure" &&
34 | filterMetric?.timing !== "everytime" &&
35 | realGroupMetrics.push(filterMetric);
36 | });
37 |
38 | // eslint-disable-next-line array-callback-return
39 | const TotalData = realGroupMetrics?.map((metric) => {
40 | const valueArray = [];
41 |
42 | const ignore = metric?.ignore;
43 | const eachWages = allWages.filter(
44 | (wage) => wage?.metricsId === metric?._id
45 | );
46 | const sortedData = eachWages?.sort(
47 | (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
48 | );
49 |
50 | for (
51 | let currentDate = filterStartDay.clone();
52 | currentDate <= filterEndDay;
53 | currentDate.add(1, "days")
54 | ) {
55 | dateArray.push(currentDate.format("DD/MMMM"));
56 |
57 | const mm = sortedData.filter(
58 | (d) =>
59 | moment(d.createdAt).local().format("YYYY-MM-DD") ===
60 | currentDate.local().format("YYYY-MM-DD")
61 | );
62 | if (
63 | currentDate.local().format("DD/MMMM") ===
64 | filterStartDay.local().format("DD/MMMM")
65 | ) {
66 | mm[0]
67 | ? valueArray.push({
68 | x: currentDate.local().format("DD/MMMM"),
69 | y:
70 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
71 | ? parseInt(mm[0]?.wage)
72 | : parseFloat(mm[0]?.wage),
73 | })
74 | : valueArray.push({
75 | x: currentDate.local().format("DD/MMMM"),
76 | y: 0,
77 | });
78 | } else if (
79 | currentDate.local().format("DD") === filterEndDay.format("DD")
80 | ) {
81 | mm[0]
82 | ? valueArray.push({
83 | x: currentDate.local().format("DD/MMMM"),
84 | y:
85 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
86 | ? parseInt(mm[0]?.wage)
87 | : parseFloat(mm[0]?.wage),
88 | })
89 | : valueArray.push({
90 | x: currentDate.local().format("DD/MMMM"),
91 | y: 0,
92 | });
93 | } else {
94 | mm[0]
95 | ? valueArray.push({
96 | x: currentDate.local().format("DD/MMMM"),
97 | y:
98 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
99 | ? parseInt(mm[0]?.wage)
100 | : parseFloat(mm[0]?.wage),
101 | })
102 | : !ignore &&
103 | valueArray.push({
104 | x: currentDate.local().format("DD/MMMM"),
105 | y: 0,
106 | });
107 | }
108 | }
109 | return { name: metric?.name, data: valueArray };
110 |
111 | // eslint-disable-next-line array-callback-return
112 | });
113 | // eslint-disable-next-line react-hooks/exhaustive-deps
114 | const options = {
115 | chart: {
116 | id: "basic-bar",
117 | },
118 | xaxis: {
119 | type: "datetime",
120 | labels: {
121 | formatter: function (val, timestamp) {
122 | return moment(new Date(timestamp)).format("DD-MMM ");
123 | },
124 | },
125 | },
126 | yaxis: {
127 | labels: {
128 | show: true,
129 | formatter: function (value) {
130 | return value;
131 | },
132 | },
133 | },
134 | dataLabels: {
135 | enabled: false,
136 | },
137 | stroke: {
138 | curve: "smooth",
139 | },
140 |
141 | };
142 | const series = [...TotalData];
143 | return (
144 |
145 |
146 |
147 | );
148 | }
149 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/OneLineChart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import ReactApexChart from "react-apexcharts";
4 | import moment from "moment-timezone";
5 | const OneLineChart = ({
6 | data,
7 | selectedShowChatMetric,
8 | filterStartDay,
9 | filterEndDay,
10 | }) => {
11 | const metric = useSelector((state) => state.metrics.metrics).filter(
12 | (metric) => metric._id === selectedShowChatMetric
13 | )[0];
14 | const sortedData = data.sort(
15 | (a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)
16 | );
17 | const ignore = metric.ignore;
18 | const dateArray = [];
19 | const valueArray = [];
20 | for (
21 | let currentDate = filterStartDay.clone();
22 | currentDate <= filterEndDay;
23 | currentDate.add(1, "days")
24 | ) {
25 | dateArray.push(currentDate.format("DD/MMMM"));
26 |
27 | const mm = sortedData.filter(
28 | (d) =>
29 | moment(d.createdAt).format("YYYY-MM-DD") ===
30 | currentDate.format("YYYY-MM-DD")
31 | );
32 | if (currentDate.format("DD/MMMM") === filterStartDay.format("DD/MMMM")) {
33 | mm[0]
34 | ? valueArray.push({
35 | x: currentDate.format("DD/MMMM"),
36 | y:
37 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
38 | ? parseInt(mm[0]?.wage)
39 | : parseFloat(mm[0]?.wage),
40 | })
41 | : valueArray.push({
42 | x: currentDate.format("DD/MMMM"),
43 | y: 0,
44 | });
45 | } else if (
46 | currentDate.format("DD/MMMM") === filterEndDay.format("DD/MMMM")
47 | ) {
48 | mm[0]
49 | ? valueArray.push({
50 | x: currentDate.format("DD/MMMM"),
51 | y:
52 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
53 | ? parseInt(mm[0]?.wage)
54 | : parseFloat(mm[0]?.wage),
55 | })
56 | : valueArray.push({
57 | x: currentDate.format("DD/MMMM"),
58 | y: 0,
59 | });
60 | } else {
61 | mm[0]
62 | ? valueArray.push({
63 | x: currentDate.format("DD/MMMM"),
64 | y:
65 | parseFloat(mm[0]?.wage) - parseInt(mm[0]?.wage) === 0
66 | ? parseInt(mm[0]?.wage)
67 | : parseFloat(mm[0]?.wage),
68 | })
69 | : !ignore &&
70 | valueArray.push({
71 | x: currentDate.format("DD/MMMM"),
72 | y: 0,
73 | });
74 | }
75 | }
76 |
77 | const state = {
78 | colors: ["#00e396"],
79 | options: {
80 | chart: {
81 | id: "basic-line",
82 | height: 350,
83 | },
84 | xaxis: {
85 | categories: dateArray,
86 | },
87 | yaxis: {
88 | show: true,
89 | labels: {
90 | show: true,
91 | formatter: function (value) {
92 | return value;
93 | },
94 | },
95 | },
96 | stroke: {
97 | curve: "smooth",
98 | width: 2,
99 | },
100 | title: {
101 | text: metric?.name,
102 | },
103 | dataLabels: {
104 | enabled: true,
105 | formatter: function (val) {
106 | return val === 0 ? "" : val;
107 | },
108 |
109 | style: {
110 | fontSize: "12px",
111 | },
112 | },
113 | tooltip: {
114 | theme: "dark",
115 | x: {
116 | show: true,
117 | format: "MMM",
118 | },
119 | y: {
120 | show: true,
121 | title: {
122 | formatter: function () {
123 | return `/${metric.postfix}`;
124 | },
125 | },
126 | },
127 | },
128 | tickAmount: 7,
129 | },
130 | series: [
131 | {
132 | name: "date",
133 | data: valueArray,
134 | },
135 | ],
136 | };
137 |
138 | return (
139 |
145 | );
146 | };
147 |
148 | export default OneLineChart;
149 |
--------------------------------------------------------------------------------
/src/pages/Dashboard/TableForTextMetrics.js:
--------------------------------------------------------------------------------
1 | import {
2 | Table,
3 | TableBody,
4 | TableCell,
5 | TableHead,
6 | TableRow,
7 | TableContainer,
8 | Box,
9 | } from "@mui/material";
10 | import moment from "moment";
11 | import React from "react";
12 |
13 | const TableForTextMetrics = ({
14 | data,
15 | selectedShowChatMetric,
16 | filterStartDay,
17 | filterEndDay,
18 | }) => {
19 | const valueArray = [];
20 | const sortedData = data.sort(
21 | (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
22 | );
23 | for (
24 | let currentDate = filterStartDay.clone();
25 | currentDate <= filterEndDay;
26 | currentDate.add(1, "days")
27 | ) {
28 | const mm = sortedData.filter(
29 | (d) =>
30 | moment(d.createdAt).format("YYYY-MM-DD") ===
31 | currentDate.format("YYYY-MM-DD")
32 | );
33 | valueArray.push(...mm);
34 | }
35 |
36 | function sortDataByRecentDate(data) {
37 | const sortedData = data.sort(
38 | (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
39 | );
40 |
41 | const groupedData = sortedData.reduce((acc, curr) => {
42 | const date = new Date(curr.createdAt).toLocaleDateString();
43 | if (!acc[date]) {
44 | acc[date] = [];
45 | }
46 | acc[date].push(curr);
47 | return acc;
48 | }, {});
49 |
50 | const sortedDates = Object.keys(groupedData).sort(
51 | (a, b) => new Date(b) - new Date(a)
52 | );
53 |
54 | const sortedGroupedData = [];
55 | for (const date of sortedDates) {
56 | sortedGroupedData.push(groupedData[date]);
57 | }
58 | return sortedGroupedData;
59 | }
60 | const rows = sortDataByRecentDate(valueArray);
61 |
62 | return (
63 |
64 |
65 |
66 |
67 |
68 |
69 | No
70 |
71 |
72 | Date
73 |
74 |
75 | Content
76 |
77 |
78 |
79 |
80 | {rows.map((row, ind) =>
81 | row?.map((item, index) =>
82 | index === 0 ? (
83 |
84 |
91 | {ind + 1}
92 |
93 |
100 | {moment(row[0]?.createdAt).format("MM-DD-YYYY")}
101 |
102 |
109 | {item.wage}
110 |
111 |
112 | ) : (
113 |
114 |
121 | {item.wage}
122 |
123 |
124 | )
125 | )
126 | )}
127 |
128 |
129 |
130 |
131 | );
132 | };
133 |
134 | export default TableForTextMetrics;
135 |
--------------------------------------------------------------------------------
/src/pages/Metrics/Metrics.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { connect, useDispatch } from "react-redux";
4 | import PlusIcon from "@heroicons/react/24/solid/PlusIcon";
5 | import {
6 | Box,
7 | Button,
8 | Container,
9 | Stack,
10 | SvgIcon,
11 | Typography,
12 | } from "@mui/material";
13 | import { Layout as DashboardLayout } from "../../layouts/dashboard/layout";
14 | import { MetricsTable } from "../../sections/metrics/metrics-table";
15 | import { MetricsSearch } from "../../sections/metrics/metrics-search";
16 | import { usePopover } from "../../hooks/use-popover";
17 | import {
18 | fieldTypes,
19 | chartTypes,
20 | statuses,
21 | timings,
22 | } from "../../sections/metrics/metrics-dialog-title";
23 | import { MetricsDialog } from "../../sections/metrics/metrics-dialog";
24 | import {
25 | apiAddMetric,
26 | apiUpdateMetric,
27 | apiGetMetricsByUserId,
28 | } from "../../actions/metrics";
29 | import { apiLogout } from "../../actions/auth";
30 |
31 | const Metrics = ({
32 | metrics: { metrics },
33 | apiAddMetric,
34 | apiUpdateMetric,
35 | apiLogout,
36 | }) => {
37 | const dispatch = useDispatch();
38 | const metricsPopover = usePopover();
39 | const [deletedId, setDeletedId] = React.useState("");
40 | const [search, setSearch] = React.useState("");
41 |
42 | const initialValues = {
43 | _id: "",
44 | name: "",
45 | description: "",
46 | fieldType: fieldTypes[0].value,
47 | prefix: "",
48 | postfix: "",
49 | chartType: chartTypes[0].value,
50 | status: statuses[0].value,
51 | ignore: true,
52 | timing: timings[0].value,
53 | };
54 | const [editedMetric, setEditedMetric] = React.useState({ ...initialValues });
55 | React.useEffect(() => {
56 | dispatch(apiGetMetricsByUserId());
57 | }, [dispatch]);
58 | return (
59 |
60 |
67 |
68 |
69 |
70 |
71 | Metrics
72 |
73 |
74 |
90 |
91 |
92 |
93 |
98 | metric.name.toLowerCase().includes(search.toLowerCase())
99 | )
100 | }
101 | // onDeselectAll={metricsSelection.handleDeselectAll}
102 | // onDeselectOne={metricsSelection.handleDeselectOne}
103 | // onSelectAll={metricsSelection.handleSelectAll}
104 | // onSelectOne={metricsSelection.handleSelectOne}
105 | // selected={metricsSelection.selected}
106 | setDeletedId={setDeletedId}
107 | deletedId={deletedId}
108 | setEditedMetric={setEditedMetric}
109 | openMetricModal={metricsPopover.handleOpen}
110 | />
111 |
112 |
113 |
114 |
126 |
127 | );
128 | };
129 |
130 | Metrics.propTypes = {
131 | apiAddMetric: PropTypes.func.isRequired,
132 | apiGetMetricsByUserId: PropTypes.func.isRequired,
133 | apiUpdateMetric: PropTypes.func.isRequired,
134 | apiLogout: PropTypes.func.isRequired,
135 | metrics: PropTypes.object.isRequired,
136 | };
137 |
138 | const mapStateToProps = (state) => ({
139 | metrics: state.metrics,
140 | });
141 |
142 | export default connect(mapStateToProps, {
143 | apiAddMetric,
144 | apiGetMetricsByUserId,
145 | apiUpdateMetric,
146 | apiLogout,
147 | })(Metrics);
148 |
--------------------------------------------------------------------------------
/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, SvgIcon, Typography } from "@mui/material";
2 | import { ArrowLeft as IconArrowLeft } from "@mui/icons-material";
3 | const Page404 = () => (
4 |
13 |
14 |
21 |
27 |
36 |
37 |
38 | 404: The page you are looking for isn’t here
39 |
40 |
41 | You either tried some shady route or you came here by mistake.
42 | Whichever it is, try using the navigation
43 |
44 |
57 |
58 |
59 |
60 | );
61 |
62 | export default Page404;
63 |
--------------------------------------------------------------------------------
/src/pages/TotalMetricsValues/TotalMetricsValues.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import moment from "moment";
4 | import {
5 | Box,
6 | Container,
7 | IconButton,
8 | SvgIcon,
9 | Tooltip,
10 | Table,
11 | TableHead,
12 | TableRow,
13 | TableCell,
14 | TableBody,
15 | TablePagination,
16 | TextField,
17 | TableContainer,
18 | Paper,
19 | Grid,
20 | } from "@mui/material";
21 | import { Layout as DashboardLayout } from "../../layouts/dashboard/layout";
22 | import { ArrowLeftIcon } from "@heroicons/react/24/solid";
23 | import { MetricsSearch } from "../../sections/metrics/metrics-search";
24 | import { apiLogout } from "../../actions/auth";
25 |
26 | import { apiGetMetricsAllWagesByUserId } from "../../actions/metrics";
27 | import { useNavigate } from "react-router-dom";
28 |
29 | const TotalMetricsValues = () => {
30 | const navigate = useNavigate();
31 | const dispatch = useDispatch();
32 | const metrics = useSelector((state) => state.metrics.metrics);
33 | const allWages = useSelector((state) => state.metrics.wages);
34 | const [search, setSearch] = React.useState("");
35 |
36 | const [page, setPage] = React.useState(0);
37 | const [rowsPerPage, setRowsPerPage] = React.useState(5);
38 | const matchingWages = allWages?.reduce((acc, wage) => {
39 | const metric = metrics.find(
40 | (metric) => metric._id.toString() === wage.metricsId.toString()
41 | );
42 | if (metric) {
43 | const date = moment(wage.createdAt)
44 | .format("YYYY-MM-DD HH:mm:ss")
45 | .toLocaleString()
46 | .slice(0, 10);
47 | if (!acc[date]) {
48 | acc[date] = [];
49 | }
50 | acc[date].push({ metric, wage });
51 | }
52 | return acc;
53 | }, {});
54 |
55 | const rows =
56 | matchingWages &&
57 | Object.entries(matchingWages)
58 | .sort((a, b) => b[0].localeCompare(a[0]))
59 | .map(([date, wages]) => ({ date, wages }));
60 | const emptyRows =
61 | rowsPerPage - Math.min(rowsPerPage, rows?.length - page * rowsPerPage);
62 | const [startDate, setStartDate] = React.useState("");
63 | const [current, setCurrentRows] = React.useState(rows);
64 |
65 | const [endDate, setEndDate] = React.useState(moment().format("YYYY-MM-DD"));
66 | React.useEffect(() => {
67 | dispatch(apiGetMetricsAllWagesByUserId);
68 | // eslint-disable-next-line
69 | }, []);
70 |
71 | const handleChangePage = (event, newPage) => {
72 | setPage(newPage);
73 | };
74 |
75 | const handleChangeRowsPerPage = (event) => {
76 | setRowsPerPage(parseInt(event.target.value, 10));
77 | setPage(0);
78 | };
79 |
80 | React.useEffect(() => {
81 | const start = startDate.valueOf();
82 | const end = endDate.valueOf();
83 | const findWages = (rows) => {
84 | let newRows = [];
85 | let date = [];
86 |
87 | rows.forEach((item) => {
88 | item.wages.forEach((wageItem) => {
89 | if (wageItem.wage.wage.includes(search)) {
90 | !date.includes(item.date) && newRows.push(item);
91 | !date.includes(item.date) && date.push(item.date);
92 | }
93 | });
94 | });
95 |
96 | return newRows;
97 | };
98 | setCurrentRows(
99 | findWages(rows).filter(
100 | (row) => start <= row.date.valueOf() && end >= row.date.valueOf()
101 | )
102 | );
103 | }, [search, startDate, endDate]); //eslint-disable-line
104 |
105 | return (
106 |
107 |
114 |
115 |
122 |
123 |
124 | navigate("/")}>
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | setStartDate(e.target.value)}
147 | />
148 |
149 |
150 | setEndDate(e.target.value)}
159 | />
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | No
171 | Date
172 | {metrics &&
173 | metrics.map((metric) => (
174 |
175 | {metric?.name}
176 |
177 | ))}
178 |
179 |
180 |
181 | {current
182 | ?.slice(
183 | page * rowsPerPage,
184 | page * rowsPerPage + rowsPerPage
185 | )
186 | ?.map((row, index) => (
187 |
188 | {index + 1}
189 | {row?.date}
190 | {metrics?.map((metric) => {
191 | const wage = row.wages.find(
192 | (w) => w?.metric?._id === metric?._id
193 | );
194 | return (
195 |
196 | {wage ? wage.wage.wage : "-"}
197 |
198 | );
199 | })}
200 |
201 | ))}
202 | {emptyRows > 0 && (
203 |
204 |
205 |
206 | )}
207 |
208 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | );
227 | };
228 | export default TotalMetricsValues;
229 |
--------------------------------------------------------------------------------
/src/pages/Track/EditTrackDialogForBlood.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | import * as yup from "yup";
4 | import PropTypes from "prop-types";
5 | import { useFormik } from "formik";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import {
8 | Box,
9 | Button,
10 | Card,
11 | CardActions,
12 | CardContent,
13 | CardHeader,
14 | Divider,
15 | TextField,
16 | InputAdornment,
17 | SvgIcon,
18 | } from "@mui/material";
19 |
20 | import { HeartIcon } from "@heroicons/react/24/solid";
21 |
22 | import MonitorHeartIcon from "@mui/icons-material/MonitorHeart";
23 |
24 | import { BootstrapDialog } from "../../components/BootstrapDialog";
25 |
26 | import { apiUpdateMetricWage } from "../../actions/metrics";
27 |
28 | const EditTrackDialogForBlood = (props) => {
29 | const dispatch = useDispatch();
30 | const { onClose, openEditMetricWageDialogForBlood, editedMetricWage } = props;
31 | const user = useSelector((state) => state.auth.user);
32 |
33 |
34 | const validationNumber = yup.object({
35 | BPH: yup
36 | .number("Metric Vaule must be number")
37 | .required("metricValue is required"),
38 | BPL: yup
39 | .number("Metric Vaule must be number")
40 | .required("metricValue is required"),
41 | HR: yup
42 | .number("Metric Vaule must be number")
43 | .required("metricValue is required"),
44 | });
45 |
46 | const formik = useFormik({
47 | initialValues: {
48 | fieldType: editedMetricWage?.fieldType,
49 | _id: editedMetricWage._id ? editedMetricWage._id : "",
50 | BPH: "",
51 | BPL: "",
52 | HR: "",
53 | },
54 | validateOnSubmit: true,
55 | validationSchema: validationNumber,
56 |
57 | onSubmit: (values) => {
58 | dispatch(
59 | apiUpdateMetricWage({
60 | _id: values._id,
61 | userId: user?._id,
62 | metricId: values._id,
63 | wage: values.BPH + "," + values.BPL + "," + values.HR,
64 | })
65 | );
66 |
67 | props.onClose();
68 | },
69 | });
70 |
71 | useEffect(() => {
72 | const kk = editedMetricWage?.wage?.split(",");
73 | formik.setValues({
74 | _id: editedMetricWage?._id,
75 | BPH: kk && kk[0],
76 | BPL: kk && kk[1],
77 | HR: kk && kk[2],
78 | fieldType: editedMetricWage?.fieldType,
79 | });
80 | // eslint-disable-next-line
81 | }, [editedMetricWage]);
82 |
83 | return (
84 |
92 |
197 |
198 | );
199 | };
200 | EditTrackDialogForBlood.propTypes = {
201 | onClose: PropTypes.func,
202 | };
203 |
204 | export default EditTrackDialogForBlood;
205 |
--------------------------------------------------------------------------------
/src/pages/auth/Login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link as RouterLink, Navigate } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import { connect } from "react-redux";
5 | import { apiLogin } from "../../actions/auth";
6 | import { useFormik } from "formik";
7 | import * as Yup from "yup";
8 | import {
9 | Box,
10 | Button,
11 | Stack,
12 | Link,
13 | TextField,
14 | Typography,
15 | } from "@mui/material";
16 | import { Layout as AuthLayout } from "../../layouts/auth/layout";
17 |
18 | const Login = ({ isAuthenticated, apiLogin }) => {
19 | const formik = useFormik({
20 | initialValues: {
21 | email: "",
22 | password: "",
23 | submit: null,
24 | },
25 | validationSchema: Yup.object({
26 | email: Yup.string()
27 | .email("Must be a valid email")
28 | .max(255)
29 | .required("Email is required"),
30 | password: Yup.string().max(255).required("Password is required"),
31 | }),
32 | onSubmit: (values) => {
33 | apiLogin({
34 | email: values.email,
35 | password: values.password,
36 | });
37 | },
38 | });
39 |
40 | if (isAuthenticated) {
41 | return ;
42 | }
43 |
44 | return (
45 |
46 |
55 |
63 |
64 |
65 | Login
66 |
67 | Don't have an account?
68 |
74 | Register
75 |
76 |
77 |
78 |
127 |
128 |
129 |
130 |
131 | );
132 | };
133 |
134 | Login.propTypes = {
135 | apiLogin: PropTypes.func.isRequired,
136 | isAuthenticated: PropTypes.bool,
137 | };
138 |
139 | const mapStateToProps = (state) => ({
140 | isAuthenticated: state.auth.isAuthenticated,
141 | });
142 |
143 | export default connect(mapStateToProps, { apiLogin })(Login);
144 |
--------------------------------------------------------------------------------
/src/pages/auth/Register.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link as RouterLink, Navigate } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import { useFormik } from "formik";
5 | import * as Yup from "yup";
6 | import {
7 | Box,
8 | Button,
9 | Link,
10 | Stack,
11 | TextField,
12 | Typography,
13 | } from "@mui/material";
14 | import PropTypes from "prop-types";
15 | import { Layout as AuthLayout } from "../../layouts/auth/layout";
16 |
17 |
18 | import { apiRegister } from "../../actions/auth";
19 |
20 | const Register = ({ apiRegister, isAuthenticated }) => {
21 | const formik = useFormik({
22 | initialValues: {
23 | email: "",
24 | name: "",
25 | password: "",
26 | submit: null,
27 | },
28 | validationSchema: Yup.object({
29 | email: Yup.string()
30 | .email("Must be a valid email")
31 | .max(255)
32 | .required("Email is required"),
33 | name: Yup.string().max(255).required("Name is required"),
34 | password: Yup.string().max(255).required("Password is required"),
35 | }),
36 | onSubmit: (values) => {
37 | apiRegister({
38 | name: values.name,
39 | email: values.email,
40 | password: values.password,
41 | });
42 | },
43 | });
44 |
45 | if (isAuthenticated) {
46 | return ;
47 | }
48 |
49 | return (
50 |
51 |
59 |
67 |
68 |
69 | Register
70 |
71 | Already have an account?
72 |
78 | Log in
79 |
80 |
81 |
82 |
159 |
160 |
161 |
162 |
163 | );
164 | };
165 |
166 | Register.propTypes = {
167 | // register: PropTypes.func.isRequired,
168 | isAuthenticated: PropTypes.bool,
169 | };
170 |
171 | const mapStateToProps = (state) => ({
172 | isAuthenticated: state.auth.isAuthenticated,
173 | });
174 |
175 | export default connect(mapStateToProps, { apiRegister })(Register);
176 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/sections/dashboard/to-do-card.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 |
3 | import ClockIcon from "@heroicons/react/24/solid/ClockIcon";
4 | import {
5 | Avatar,
6 | Card,
7 | CardContent,
8 | Stack,
9 | SvgIcon,
10 | Tooltip,
11 | Typography,
12 | } from "@mui/material";
13 |
14 | export const ToDoCard = (props) => {
15 | const { sx, metric, setOpenModal, setOpenBlood, setSelectedMetric } = props;
16 |
17 | return (
18 |
25 | {
29 | metric.fieldType === "bloodPressure"
30 | ? setOpenBlood(true)
31 | : setOpenModal(true);
32 |
33 | setSelectedMetric(metric);
34 | }}
35 | >
36 |
37 |
43 |
50 |
51 |
52 |
53 |
54 |
55 |
60 | {metric.name}
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | ToDoCard.prototypes = {
71 | sx: PropTypes.object,
72 | value: PropTypes.string.isRequired,
73 | };
74 |
--------------------------------------------------------------------------------
/src/sections/dashboard/to-do-dialog-for-bloodPressure.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | import * as yup from "yup";
4 | import PropTypes from "prop-types";
5 | import { useFormik } from "formik";
6 | import { useDispatch } from "react-redux";
7 | import {
8 | Box,
9 | Button,
10 | Card,
11 | CardActions,
12 | CardContent,
13 | CardHeader,
14 | Divider,
15 | TextField,
16 | InputAdornment,
17 | SvgIcon,
18 | } from "@mui/material";
19 |
20 | import { HeartIcon } from "@heroicons/react/24/solid";
21 |
22 | import MonitorHeartIcon from "@mui/icons-material/MonitorHeart";
23 |
24 | import { BootstrapDialog } from "../../components/BootstrapDialog";
25 |
26 | import { apiAddMetricWage } from "../../actions/metrics";
27 |
28 | const ToDoForBloodPressure = (props) => {
29 | const dispatch = useDispatch();
30 | const { onClose, open, selectedMetric } = props;
31 |
32 |
33 | const validationNumber = yup.object({
34 | BPH: yup
35 | .number("Metric Vaule must be number")
36 | .required("metricValue is required"),
37 | BPL: yup
38 | .number("Metric Vaule must be number")
39 | .required("metricValue is required"),
40 | HR: yup
41 | .number("Metric Vaule must be number")
42 | .required("metricValue is required"),
43 | });
44 |
45 | const formik = useFormik({
46 | initialValues: {
47 | fieldType: selectedMetric?.fieldType,
48 | _id: selectedMetric._id ? selectedMetric._id : "",
49 | BPH: "",
50 | BPL: "",
51 | HR: "",
52 | },
53 | validateOnSubmit: true,
54 | validationSchema: validationNumber,
55 |
56 | onSubmit: (values) => {
57 | dispatch(
58 | apiAddMetricWage({
59 | fieldType: values.fieldType,
60 | metricId: values._id,
61 | metricValue: values.BPH + "," + values.BPL + "," + values.HR,
62 | })
63 | );
64 |
65 | props.onClose();
66 | },
67 | });
68 | useEffect(() => {
69 | formik.setValues({
70 | _id: selectedMetric._id,
71 | BPH: "",
72 | BPL: "",
73 | HR: "",
74 | fieldType: selectedMetric.fieldType,
75 | });
76 | // eslint-disable-next-line
77 | }, [selectedMetric]);
78 | return (
79 |
87 |
190 |
191 | );
192 | };
193 | ToDoForBloodPressure.propTypes = {
194 | onClose: PropTypes.func,
195 | open: PropTypes.bool.isRequired,
196 | };
197 |
198 | export default ToDoForBloodPressure;
199 |
--------------------------------------------------------------------------------
/src/sections/dashboard/to-do-dialog.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { styled } from "@mui/material/styles";
3 | import * as yup from "yup";
4 | import PropTypes from "prop-types";
5 | import { useFormik } from "formik";
6 | import { useDispatch } from "react-redux";
7 | import {
8 | Box,
9 | Button,
10 | Card,
11 | CardActions,
12 | CardContent,
13 | CardHeader,
14 | Divider,
15 | TextField,
16 | InputAdornment,
17 | SvgIcon,
18 | Typography,
19 | } from "@mui/material";
20 | import { Rating } from "@mui/material";
21 | import { HeartIcon } from "@heroicons/react/24/solid";
22 | import FavoriteIcon from "@mui/icons-material/Favorite";
23 | import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
24 | import MonitorHeartIcon from "@mui/icons-material/MonitorHeart";
25 |
26 | import { BootstrapDialog } from "../../components/BootstrapDialog";
27 |
28 | import { apiAddMetricWage } from "../../actions/metrics";
29 |
30 | const StyledRating = styled(Rating)({
31 | "& .MuiRating-iconFilled": {
32 | color: "#f31212",
33 | },
34 | "& .MuiRating-iconHover": {
35 | color: "#ff5d02",
36 | },
37 | });
38 |
39 | const ToDoDialog = (props) => {
40 | const dispatch = useDispatch();
41 | const { onClose, open, selectedMetric } = props;
42 |
43 | const validationString = yup.object({
44 | metricValue: yup
45 | .string("Enter your metricValue")
46 | .required("metricValue is required"),
47 | });
48 | const validationNumber = yup.object({
49 | metricValue: yup
50 | .number("Metric Vaule must be number")
51 | .required("metricValue is required"),
52 | });
53 |
54 | const formik = useFormik({
55 | initialValues: {
56 | fieldType: selectedMetric?.fieldType,
57 | _id: selectedMetric._id ? selectedMetric._id : "",
58 | metricValue: "",
59 | },
60 | validateOnSubmit: true,
61 | validationSchema:
62 | selectedMetric.fieldType === "text" ? validationString : validationNumber,
63 |
64 | onSubmit: (values) => {
65 | dispatch(
66 | apiAddMetricWage({
67 | fieldType: values.fieldType,
68 | metricId: values._id,
69 | metricValue: values.metricValue,
70 | })
71 | );
72 |
73 | props.onClose();
74 | },
75 | });
76 | useEffect(() => {
77 | formik.setValues({
78 | _id: selectedMetric._id,
79 | metricValue: "",
80 | fieldType: selectedMetric.fieldType,
81 | });
82 | // eslint-disable-next-line
83 | }, [selectedMetric]);
84 | return (
85 |
93 |
265 |
266 | );
267 | };
268 | ToDoDialog.propTypes = {
269 | onClose: PropTypes.func,
270 | open: PropTypes.bool.isRequired,
271 | };
272 |
273 | export default ToDoDialog;
274 |
--------------------------------------------------------------------------------
/src/sections/group/groupCard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Card,
4 | Typography,
5 | IconButton,
6 | SvgIcon,
7 | MenuItem,
8 | Menu,
9 | } from "@mui/material";
10 | import {
11 | EllipsisVerticalIcon as MoreIcon,
12 | } from "@heroicons/react/24/solid";
13 | import { Box } from "@mui/system";
14 |
15 | const GroupCard = ({ group, handleClickEdit, setDeletedId, setOpenDialog }) => {
16 | const moreRef = React.useRef(null);
17 | const [isMenuOpen, setMenuOpen] = React.useState(false);
18 | const handleMenuOpen = () => {
19 | setMenuOpen(true);
20 | };
21 |
22 | const handleMenuClose = () => {
23 | setMenuOpen(false);
24 | };
25 | return (
26 |
27 |
28 |
29 | {group.name}
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
71 |
72 | );
73 | };
74 |
75 | export default GroupCard;
76 |
--------------------------------------------------------------------------------
/src/sections/group/metricCard.js:
--------------------------------------------------------------------------------
1 | import { Card } from "@mui/material";
2 | import React from "react";
3 |
4 | const MetricCard = () => {
5 | return ;
6 | };
7 |
8 | export default MetricCard;
9 |
--------------------------------------------------------------------------------
/src/sections/metrics/metrics-dialog-title.js:
--------------------------------------------------------------------------------
1 | export const fieldTypes = [
2 | {
3 | value: "number",
4 | label: "Number",
5 | },
6 | {
7 | value: "text",
8 | label: "Text",
9 | },
10 | { value: "bloodPressure", label: "Blood Pressure" },
11 | {
12 | value: "5rating",
13 | label: "Rate From 1-5",
14 | },
15 | {
16 | value: "10rating",
17 | label: "Rate From 1-10",
18 | },
19 | ];
20 |
21 | export const chartTypes = [
22 | {
23 | value: "line",
24 | label: "Line",
25 | },
26 | ];
27 |
28 | export const statuses = [
29 | {
30 | value: "active",
31 | label: "Active",
32 | },
33 | {
34 | value: "inactive",
35 | label: "Inactive",
36 | },
37 | {
38 | value: "fixed",
39 | label: "Fixed",
40 | },
41 | ];
42 |
43 | export const timings = [
44 | {
45 | value: "daily",
46 | label: "Daily",
47 | },
48 | {
49 | value: "everytime",
50 | label: "Everytime",
51 | },
52 | ];
53 |
--------------------------------------------------------------------------------
/src/sections/metrics/metrics-dialog.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import { useFormik } from "formik";
4 | import {
5 | Box,
6 | Button,
7 | Card,
8 | CardActions,
9 | CardContent,
10 | CardHeader,
11 | Divider,
12 | TextField,
13 | Unstable_Grid2 as Grid,
14 | FormControlLabel,
15 | Checkbox,
16 | } from "@mui/material";
17 | import { BootstrapDialog } from "../../components/BootstrapDialog";
18 | import ConfirmDialog from "../../components/ConfirmModal";
19 | import { toast } from "react-toastify";
20 |
21 | export const MetricsDialog = (props) => {
22 | const { onClose, open, timings, fieldTypes, chartTypes, statuses } = props;
23 | const [openDialog, setOpenDialog] = React.useState(false);
24 |
25 | const formik = useFormik({
26 | initialValues: props.initialValues,
27 |
28 | onSubmit: (values) => {
29 | if (values.name === "") {
30 | toast.error("You must input Metric name!");
31 | } else {
32 | if (values._id) {
33 | setOpenDialog(true);
34 | } else {
35 | props.onAddMetric(values);
36 | props.onClose();
37 | }
38 | }
39 | },
40 | });
41 |
42 | useEffect(() => {
43 | props.initialValues && formik.setValues(props.initialValues);
44 | // eslint-disable-next-line
45 | }, [props.initialValues]); // eslint-disable-next-line
46 |
47 | return (
48 |
54 |
220 |
221 | );
222 | };
223 |
224 | MetricsDialog.propTypes = {
225 | onClose: PropTypes.func,
226 | open: PropTypes.bool.isRequired,
227 | };
228 |
--------------------------------------------------------------------------------
/src/sections/metrics/metrics-search.js:
--------------------------------------------------------------------------------
1 | import MagnifyingGlassIcon from "@heroicons/react/24/solid/MagnifyingGlassIcon";
2 | import PropTypes from "prop-types";
3 |
4 | import { InputAdornment, OutlinedInput, SvgIcon } from "@mui/material";
5 |
6 | export const MetricsSearch = ({ search, setSearch }) => (
7 |
8 | {
13 | setSearch(e.target.value);
14 | }}
15 | startAdornment={
16 |
17 |
18 |
19 |
20 |
21 | }
22 | />
23 |
24 | );
25 | MetricsSearch.prototype = {
26 | search: PropTypes.object.isRequired,
27 | setSearch: PropTypes.func.isRequired,
28 | };
29 |
--------------------------------------------------------------------------------
/src/sections/metrics/metrics-table.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import {
4 | Box,
5 | Card,
6 | IconButton,
7 | Stack,
8 | Table,
9 | TableBody,
10 | TableCell,
11 | TableHead,
12 | TableRow,
13 | SvgIcon,
14 | Tooltip,
15 | Typography,
16 | } from "@mui/material";
17 | import { Scrollbar } from "../../components/scrollbar";
18 | import { SeverityPill } from "../../components/severity-pill";
19 | import { PencilIcon, TrashIcon } from "@heroicons/react/24/solid";
20 | import ConfirmDialog from "../../components/ConfirmModal";
21 | import { apiDeleteMetricById } from "../../actions/metrics";
22 | import { useDispatch } from "react-redux";
23 | const statusMap = {
24 | active: "success",
25 | inactive: "warning",
26 | fixed: "error",
27 | };
28 |
29 | export const MetricsTable = (props) => {
30 | const {
31 | metrics,
32 |
33 | selected = [],
34 | deletedId,
35 | setDeletedId,
36 | setEditedMetric,
37 | openMetricModal,
38 | } = props;
39 | const dispatch = useDispatch();
40 | const [openDialog, setOpenDialog] = React.useState(false);
41 |
42 |
43 | const onOK = () => {
44 | deletedId && dispatch(apiDeleteMetricById(deletedId));
45 | setDeletedId("");
46 | setOpenDialog(false);
47 | };
48 | const onCancel = () => {
49 | setOpenDialog(false);
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 | No
60 | Name
61 | Description
62 | Chart Type
63 | Timing
64 | Status
65 |
66 |
67 |
68 |
69 | {metrics?.map((metric, index) => {
70 | const isSelected = selected.includes(metric._id);
71 |
72 | return (
73 |
74 | {index + 1}
75 |
76 |
77 |
78 | {metric.name}
79 |
80 |
81 |
82 | {metric.description}
83 | {metric.chartType}
84 | {metric.timing}
85 |
86 |
87 | {metric.status}
88 |
89 |
90 |
91 |
92 | {
94 | setEditedMetric(metric);
95 | openMetricModal();
96 | }}
97 | >
98 |
99 |
100 |
101 |
102 |
103 |
104 | {
106 | setDeletedId(metric._id);
107 | setOpenDialog(true);
108 | }}
109 | >
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | );
118 | })}
119 |
120 |
121 |
122 |
123 |
130 |
131 | );
132 | };
133 |
134 | MetricsTable.propTypes = {
135 | metrics: PropTypes.array,
136 | onDeselectAll: PropTypes.func,
137 | onDeselectOne: PropTypes.func,
138 | onSelectAll: PropTypes.func,
139 | onSelectOne: PropTypes.func,
140 | selected: PropTypes.array,
141 | };
142 |
--------------------------------------------------------------------------------
/src/sections/overview/overview-latest-metrics-value.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { useDispatch } from "react-redux";
4 | import { useNavigate } from "react-router-dom";
5 | import { formatDistanceToNow } from "date-fns";
6 | import ArrowRightIcon from "@heroicons/react/24/solid/ArrowRightIcon";
7 | import EllipsisVerticalIcon from "@heroicons/react/24/solid/EllipsisVerticalIcon";
8 | import {
9 | Menu,
10 | Card,
11 | List,
12 | Avatar,
13 | Button,
14 | SvgIcon,
15 | Divider,
16 | ListItem,
17 | MenuItem,
18 | CardHeader,
19 | IconButton,
20 | CardActions,
21 | ListItemText,
22 | ListItemAvatar,
23 | } from "@mui/material";
24 | import ClockIcon from "@heroicons/react/24/solid/ClockIcon";
25 | import { apiDeleteMetricWageById } from "../../actions/metrics";
26 | import ConfirmDialog from "../../components/ConfirmModal";
27 |
28 | export const OverviewLatestMetricsValue = (props) => {
29 | const dispatch = useDispatch();
30 | const navigate = useNavigate();
31 | const { lastestWages = [], metrics = [], sx } = props;
32 | const [deletedMetricWageId, setDeleteMetricWageId] = React.useState("");
33 | const [anchorEl, setAnchorEl] = React.useState(null);
34 | const [openDialog, setOpenDialog] = React.useState(false);
35 | const open = Boolean(anchorEl);
36 | const handleClick = (event, id) => {
37 | setDeleteMetricWageId(id);
38 | setAnchorEl(event.currentTarget);
39 | };
40 | const handleClose = () => {
41 | setAnchorEl(null);
42 | };
43 | const onOK = () => {
44 | console.log(deletedMetricWageId);
45 | dispatch(apiDeleteMetricWageById(deletedMetricWageId));
46 | setOpenDialog(false);
47 | handleClose();
48 | };
49 | return (
50 | <>
51 |
52 |
53 |
54 | {lastestWages &&
55 | lastestWages.map((wage, index) => {
56 | const hasDivider = index < lastestWages.length - 1;
57 | const ago = formatDistanceToNow(
58 | new Date(wage.updatedAt).getTime()
59 | );
60 |
61 | return (
62 |
63 |
64 |
71 |
72 |
73 |
74 |
75 |
76 | metric._id === wage.metricsId
80 | )[0]
81 | ? `${
82 | metrics?.filter(
83 | (metric) => metric._id === wage.metricsId
84 | )[0]?.name
85 | }`
86 | : ""
87 | }
88 | primaryTypographyProps={{ variant: "subtitle1" }}
89 | secondary={`Updated ${ago} ago`}
90 | secondaryTypographyProps={{ variant: "body2" }}
91 | />
92 | handleClick(e, wage._id)}
95 | >
96 |
97 |
98 |
99 |
100 |
120 |
121 | );
122 | })}
123 |
124 |
125 |
126 |
141 |
142 |
143 | {
147 | setOpenDialog(false);
148 | }}
149 | onOK={onOK}
150 | openDialog={openDialog}
151 | />
152 | >
153 | );
154 | };
155 |
156 | OverviewLatestMetricsValue.propTypes = {
157 | products: PropTypes.array,
158 | sx: PropTypes.object,
159 | };
160 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/store/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | const initialState = {
3 | token: localStorage.getItem("token"),
4 | isAuthenticated: null,
5 | loading: true,
6 | user: {},
7 | };
8 |
9 | export const authReducer = createSlice({
10 | name: "auth",
11 | initialState: initialState,
12 | reducers: {
13 | userLogined: (state, action) => {
14 | state.isAuthenticated = true;
15 | state.loading = false;
16 | state.user = action.payload;
17 | },
18 | userRegister: (state) => {
19 | state.isAuthenticated = true;
20 | state.loading = false;
21 | },
22 | userLoginSuccess: (state, action) => {
23 | state.isAuthenticated = true;
24 | state.loading = false;
25 | state.token = action.payload;
26 | },
27 | userLogOut: (state) => {
28 | state.token = null;
29 | state.isAuthenticated = false;
30 | state.loading = false;
31 | state.user = null;
32 | },
33 | userAuthError: (state, action) => {
34 | state.token = null;
35 | },
36 | },
37 | });
38 |
39 | // Action creators are generated for each case reducer function
40 | export const {
41 | userLogined,
42 | userLoginSuccess,
43 | userLogOut,
44 | userRegister,
45 | userAuthError,
46 | } = authReducer.actions;
47 |
48 | export default authReducer.reducer;
49 |
--------------------------------------------------------------------------------
/src/store/groupSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | groups: [{ name: "", userId: "", contents: [] }],
5 | loading: true,
6 | };
7 |
8 | export const groupSlice = createSlice({
9 | name: "auth",
10 | initialState: initialState,
11 | reducers: {
12 | addGroup: (state, action) => {
13 | state.loading = false;
14 | state.groups = [...state.groups, action.payload];
15 | },
16 | getGroups: (state, action) => {
17 | if (action.payload) {
18 | state.groups = action.payload;
19 | } else {
20 | state.groups = [];
21 | }
22 | },
23 | deleteGroup: (state, action) => {
24 | state.groups = state.groups.filter(
25 | (group) => group._id !== action.payload._id
26 | );
27 | state.loading = false;
28 | },
29 | updateGroup: (state, action) => {
30 | state.loading = false;
31 | state.groups =
32 | state.groups &&
33 | state.groups.map((group) =>
34 | group._id === action.payload._id ? action.payload : group
35 | );
36 | },
37 | groupError: (state) => {
38 | state.loading = false;
39 | },
40 | },
41 | });
42 |
43 | // Action creators are generated for each case reducer function
44 | export const { addGroup, getGroups, deleteGroup, updateGroup, groupError } =
45 | groupSlice.actions;
46 |
47 | export default groupSlice.reducer;
48 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 |
3 | import authSlice from "./authSlice";
4 | import metricsSlice from "./metricsSlice";
5 | import groupSlice from "./groupSlice";
6 | import { setAuthToken } from "../utils";
7 | // import kabanSlice from "./kabanSlice";
8 | const store = configureStore({
9 | reducer: {
10 | auth: authSlice,
11 | metrics: metricsSlice,
12 | group: groupSlice,
13 | },
14 | });
15 |
16 | export default store;
17 |
18 | let currentState = store.getState();
19 |
20 | store.subscribe(() => {
21 | // keep track of the previous and current state to compare changes
22 | let previousState = currentState;
23 | currentState = store.getState();
24 | // if the token changes set the value in localStorage and axios headers
25 | if (previousState.auth.token !== currentState.auth.token) {
26 | const token = currentState.auth.token;
27 | setAuthToken(token);
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/src/store/metricsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | metrics: [],
5 | loading: true,
6 | wages: [],
7 | todayWages: [],
8 | lastestWages: [],
9 | };
10 |
11 | export const metricsSlice = createSlice({
12 | name: "auth",
13 | initialState: initialState,
14 | reducers: {
15 | addMetric: (state, action) => {
16 | state.loading = false;
17 |
18 | state.metrics = action.payload
19 | ? [...state.metrics, action.payload]
20 | : state.metrics;
21 | },
22 |
23 | updateMetric: (state, action) => {
24 | state.loading = false;
25 | state.metrics = state.metrics?.map((metric) =>
26 | metric._id === action.payload._id ? action.payload : metric
27 | );
28 | },
29 |
30 | getUserMetrics: (state, action) => {
31 | state.metrics = action.payload ? action.payload : [];
32 | state.loading = false;
33 | },
34 |
35 | deleteMetric: (state, action) => {
36 | state.metrics = state.metrics?.filter(
37 | (metric) => metric._id !== action.payload._id
38 | );
39 |
40 | state.loading = false;
41 | },
42 |
43 | addMetricWage: (state, action) => {
44 | state.wages = [...state.wages, action.payload];
45 | state.todayWages = [...state.todayWages, action.payload];
46 | state.lastestWages.unshift(action.payload);
47 | state.lastestWages.length > 3 && state.lastestWages.pop();
48 | },
49 |
50 | getMetricsAllWages: (state, action) => {
51 | state.wages = action.payload ? action.payload : [];
52 | },
53 | getMetricsTodayWages: (state, action) => {
54 | state.todayWages = action.payload ? action.payload : [];
55 | },
56 | getMetricsLastestWages: (state, action) => {
57 | state.lastestWages = action.payload ? action.payload : [];
58 | },
59 | deleteMetricWage: (state, action) => {
60 | state.wages = state.wages?.filter(
61 | (value) => value._id !== action.payload._id
62 | );
63 | state.todayWages = state.todayWages?.filter(
64 | (value) => value._id !== action.payload._id
65 | );
66 | state.lastestWages = state.lastestWages?.filter(
67 | (value) => value._id !== action.payload._id
68 | );
69 | state.loading = false;
70 | },
71 | updateMetricsWage: (state, action) => {
72 | state.loading = false;
73 | state.wages = state.wages?.map((metric) =>
74 | metric._id === action.payload._id ? action.payload : metric
75 | );
76 | state.todayWages = state.todayWages?.map((wage) =>
77 | wage._id === action.payload._id ? action.payload : wage
78 | );
79 | },
80 | metricsError: (state) => {
81 | state.loading = false;
82 | },
83 | },
84 | });
85 |
86 | // Action creators are generated for each case reducer function
87 | export const {
88 | addMetric,
89 | getUserMetrics,
90 | metricsError,
91 | deleteMetric,
92 | updateMetric,
93 | addMetricWage,
94 | deleteMetricWage,
95 | updateMetricsWage,
96 | getMetricsAllWages,
97 | getMetricsTodayWages,
98 | getMetricsLastestWages,
99 | } = metricsSlice.actions;
100 |
101 | export default metricsSlice.reducer;
102 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | #root {
2 | --toastify-font-family: "Poppins", sans-serif;
3 | display: flex;
4 | flex: 1 1 auto;
5 | flex-direction: column;
6 | height: 100%;
7 | width: 100%;
8 | font-family: "Roboto", "Helvetica", "Arial", "sans-serif" !important;
9 | }
10 |
11 | ::-webkit-scrollbar {
12 | width: 6px;
13 | height: 6px;
14 | }
15 |
16 | ::-webkit-scrollbar-thumb {
17 | background-color: #0003;
18 | border-radius: 6px;
19 | transition: all 0.2s ease-in-out;
20 | }
21 | ::-webkit-scrollbar-track {
22 | border-radius: 6px;
23 | }
24 | p {
25 | font-family: "Roboto", "Helvetica", "Arial", "sans-serif" !important;
26 | }
27 |
--------------------------------------------------------------------------------
/src/theme/colors.js:
--------------------------------------------------------------------------------
1 | import { alpha } from "@mui/material/styles";
2 |
3 | const withAlphas = (color) => {
4 | return {
5 | ...color,
6 | alpha4: alpha(color.main, 0.04),
7 | alpha8: alpha(color.main, 0.08),
8 | alpha12: alpha(color.main, 0.12),
9 | alpha30: alpha(color.main, 0.3),
10 | alpha50: alpha(color.main, 0.5),
11 | };
12 | };
13 |
14 | export const neutral = {
15 | 50: "#F8F9FA",
16 | 100: "#F3F4F6",
17 | 200: "#E5E7EB",
18 | 300: "#D2D6DB",
19 | 400: "#9DA4AE",
20 | 500: "#6C737F",
21 | 600: "#4D5761",
22 | 700: "#2F3746",
23 | 800: "#1C2536",
24 | 900: "#111927",
25 | };
26 |
27 | export const indigo = withAlphas({
28 | lightest: "#F5F7FF",
29 | light: "#EBEEFE",
30 | main: "#6366F1",
31 | dark: "#4338CA",
32 | darkest: "#312E81",
33 | contrastText: "#FFFFFF",
34 | });
35 |
36 | export const success = withAlphas({
37 | lightest: "#F0FDF9",
38 | light: "#3FC79A",
39 | main: "#10B981",
40 | dark: "#0B815A",
41 | darkest: "#134E48",
42 | contrastText: "#FFFFFF",
43 | });
44 |
45 | export const info = withAlphas({
46 | lightest: "#ECFDFF",
47 | light: "#CFF9FE",
48 | main: "#06AED4",
49 | dark: "#0E7090",
50 | darkest: "#164C63",
51 | contrastText: "#FFFFFF",
52 | });
53 |
54 | export const warning = withAlphas({
55 | lightest: "#FFFAEB",
56 | light: "#FEF0C7",
57 | main: "#F79009",
58 | dark: "#B54708",
59 | darkest: "#7A2E0E",
60 | contrastText: "#FFFFFF",
61 | });
62 |
63 | export const error = withAlphas({
64 | lightest: "#FEF3F2",
65 | light: "#FEE4E2",
66 | main: "#F04438",
67 | dark: "#B42318",
68 | darkest: "#7A271A",
69 | contrastText: "#FFFFFF",
70 | });
71 |
--------------------------------------------------------------------------------
/src/theme/create-components.js:
--------------------------------------------------------------------------------
1 | import {
2 | createTheme,
3 | filledInputClasses,
4 | inputLabelClasses,
5 | outlinedInputClasses,
6 | paperClasses,
7 | tableCellClasses,
8 | } from "@mui/material";
9 |
10 | // Used only to create transitions
11 | const muiTheme = createTheme();
12 |
13 | export function createComponents(config) {
14 | const { palette } = config;
15 |
16 | return {
17 | MuiAvatar: {
18 | styleOverrides: {
19 | root: {
20 | fontSize: 14,
21 | fontWeight: 600,
22 | letterSpacing: 0,
23 | },
24 | },
25 | },
26 | MuiButton: {
27 | styleOverrides: {
28 | root: {
29 | borderRadius: "12px",
30 | textTransform: "none",
31 | },
32 | sizeSmall: {
33 | padding: "6px 16px",
34 | },
35 | sizeMedium: {
36 | padding: "8px 20px",
37 | },
38 | sizeLarge: {
39 | padding: "11px 24px",
40 | },
41 | textSizeSmall: {
42 | padding: "7px 12px",
43 | },
44 | textSizeMedium: {
45 | padding: "9px 16px",
46 | },
47 | textSizeLarge: {
48 | padding: "12px 16px",
49 | },
50 | },
51 | },
52 | MuiCard: {
53 | styleOverrides: {
54 | root: {
55 | [`&.${paperClasses.elevation1}`]: {
56 | boxShadow:
57 | "0px 5px 22px rgba(0, 0, 0, 0.04), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)",
58 | },
59 | },
60 | },
61 | },
62 | MuiCardContent: {
63 | styleOverrides: {
64 | root: {
65 | padding: "32px 24px",
66 | "&:last-child": {
67 | paddingBottom: "32px",
68 | },
69 | },
70 | },
71 | },
72 | MuiCardHeader: {
73 | defaultProps: {
74 | titleTypographyProps: {
75 | variant: "h6",
76 | },
77 | subheaderTypographyProps: {
78 | variant: "body2",
79 | },
80 | },
81 | styleOverrides: {
82 | root: {
83 | padding: "32px 24px 16px",
84 | },
85 | },
86 | },
87 | MuiCssBaseline: {
88 | styleOverrides: {
89 | "*": {
90 | boxSizing: "border-box",
91 | },
92 | html: {
93 | MozOsxFontSmoothing: "grayscale",
94 | WebkitFontSmoothing: "antialiased",
95 | display: "flex",
96 | flexDirection: "column",
97 | minHeight: "100%",
98 | width: "100%",
99 | },
100 | body: {
101 | display: "flex",
102 | flex: "1 1 auto",
103 | flexDirection: "column",
104 | minHeight: "100%",
105 | width: "100%",
106 | },
107 | "#__next": {
108 | display: "flex",
109 | flex: "1 1 auto",
110 | flexDirection: "column",
111 | height: "100%",
112 | width: "100%",
113 | },
114 | "#nprogress": {
115 | pointerEvents: "none",
116 | },
117 | "#nprogress .bar": {
118 | backgroundColor: palette.primary.main,
119 | height: 3,
120 | left: 0,
121 | position: "fixed",
122 | top: 0,
123 | width: "100%",
124 | zIndex: 2000,
125 | },
126 | },
127 | },
128 | MuiInputBase: {
129 | styleOverrides: {
130 | input: {
131 | "&::placeholder": {
132 | opacity: 1,
133 | },
134 | },
135 | },
136 | },
137 | MuiInput: {
138 | styleOverrides: {
139 | input: {
140 | fontSize: 14,
141 | fontWeight: 500,
142 | lineHeight: "24px",
143 | "&::placeholder": {
144 | color: palette.text.secondary,
145 | },
146 | },
147 | },
148 | },
149 | MuiFilledInput: {
150 | styleOverrides: {
151 | root: {
152 | backgroundColor: "transparent",
153 | borderRadius: 8,
154 | borderStyle: "solid",
155 | borderWidth: 1,
156 | overflow: "hidden",
157 | borderColor: palette.neutral[200],
158 | transition: muiTheme.transitions.create([
159 | "border-color",
160 | "box-shadow",
161 | ]),
162 | "&:hover": {
163 | backgroundColor: palette.action.hover,
164 | },
165 | "&:before": {
166 | display: "none",
167 | },
168 | "&:after": {
169 | display: "none",
170 | },
171 | [`&.${filledInputClasses.disabled}`]: {
172 | backgroundColor: "transparent",
173 | },
174 | [`&.${filledInputClasses.focused}`]: {
175 | backgroundColor: "transparent",
176 | borderColor: palette.primary.main,
177 | boxShadow: `${palette.primary.main} 0 0 0 2px`,
178 | },
179 | [`&.${filledInputClasses.error}`]: {
180 | borderColor: palette.error.main,
181 | boxShadow: `${palette.error.main} 0 0 0 2px`,
182 | },
183 | },
184 | input: {
185 | fontSize: 14,
186 | fontWeight: 500,
187 | lineHeight: "24px",
188 | },
189 | },
190 | },
191 | MuiOutlinedInput: {
192 | styleOverrides: {
193 | root: {
194 | "&:hover": {
195 | backgroundColor: palette.action.hover,
196 | [`& .${outlinedInputClasses.notchedOutline}`]: {
197 | borderColor: palette.neutral[200],
198 | },
199 | },
200 | [`&.${outlinedInputClasses.focused}`]: {
201 | backgroundColor: "transparent",
202 | [`& .${outlinedInputClasses.notchedOutline}`]: {
203 | borderColor: palette.primary.main,
204 | boxShadow: `${palette.primary.main} 0 0 0 2px`,
205 | },
206 | },
207 | [`&.${filledInputClasses.error}`]: {
208 | [`& .${outlinedInputClasses.notchedOutline}`]: {
209 | borderColor: palette.error.main,
210 | boxShadow: `${palette.error.main} 0 0 0 2px`,
211 | },
212 | },
213 | },
214 | input: {
215 | fontSize: 14,
216 | fontWeight: 500,
217 | lineHeight: "24px",
218 | },
219 | notchedOutline: {
220 | borderColor: palette.neutral[200],
221 | transition: muiTheme.transitions.create([
222 | "border-color",
223 | "box-shadow",
224 | ]),
225 | },
226 | },
227 | },
228 | MuiFormLabel: {
229 | styleOverrides: {
230 | root: {
231 | fontSize: 14,
232 | fontWeight: 500,
233 | [`&.${inputLabelClasses.filled}`]: {
234 | transform: "translate(12px, 18px) scale(1)",
235 | },
236 | [`&.${inputLabelClasses.shrink}`]: {
237 | [`&.${inputLabelClasses.standard}`]: {
238 | transform: "translate(0, -1.5px) scale(0.85)",
239 | },
240 | [`&.${inputLabelClasses.filled}`]: {
241 | transform: "translate(12px, 6px) scale(0.85)",
242 | },
243 | [`&.${inputLabelClasses.outlined}`]: {
244 | transform: "translate(14px, -9px) scale(0.85)",
245 | },
246 | },
247 | },
248 | },
249 | },
250 | MuiTab: {
251 | styleOverrides: {
252 | root: {
253 | fontSize: 14,
254 | fontWeight: 500,
255 | lineHeight: 1.71,
256 | minWidth: "auto",
257 | paddingLeft: 0,
258 | paddingRight: 0,
259 | textTransform: "none",
260 | "& + &": {
261 | marginLeft: 24,
262 | },
263 | },
264 | },
265 | },
266 | MuiTableCell: {
267 | styleOverrides: {
268 | root: {
269 | borderBottomColor: palette.divider,
270 | padding: "15px 16px",
271 | borderBottom: "solid 2px #e7ebf0",
272 | },
273 | },
274 | },
275 | MuiTableHead: {
276 | styleOverrides: {
277 | root: {
278 | borderBottom: "none",
279 | [`& .${tableCellClasses.root}`]: {
280 | borderBottom: "none",
281 | backgroundColor: "#e2e2e2",
282 | color: palette.neutral[700],
283 | fontSize: 12,
284 | fontWeight: 600,
285 | lineHeight: 1,
286 | letterSpacing: 0.5,
287 | textTransform: "uppercase",
288 | },
289 | [`& .${tableCellClasses.paddingCheckbox}`]: {
290 | paddingTop: 4,
291 | paddingBottom: 4,
292 | },
293 | },
294 | },
295 | },
296 | MuiTextField: {
297 | defaultProps: {
298 | variant: "filled",
299 | },
300 | },
301 | };
302 | }
303 |
--------------------------------------------------------------------------------
/src/theme/create-palette.js:
--------------------------------------------------------------------------------
1 | import { common } from "@mui/material/colors";
2 | import { alpha } from "@mui/material/styles";
3 | import { error, indigo, info, neutral, success, warning } from "./colors";
4 |
5 | export function createPalette() {
6 | return {
7 | action: {
8 | active: neutral[500],
9 | disabled: alpha(neutral[900], 0.38),
10 | disabledBackground: alpha(neutral[900], 0.12),
11 | focus: alpha(neutral[900], 0.16),
12 | hover: alpha(neutral[900], 0.04),
13 | selected: alpha(neutral[900], 0.12),
14 | },
15 | background: {
16 | default: common.white,
17 | paper: common.white,
18 | },
19 | divider: "#e7ebf0",
20 | error,
21 | info,
22 | mode: "light",
23 | neutral,
24 | primary: indigo,
25 | secondary: info,
26 | success,
27 | text: {
28 | primary: neutral[900],
29 | secondary: neutral[500],
30 | disabled: alpha(neutral[900], 0.38),
31 | },
32 | warning,
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/theme/create-shadows.js:
--------------------------------------------------------------------------------
1 | export const createShadows = () => {
2 | return [
3 | 'none',
4 | '0px 1px 2px rgba(0, 0, 0, 0.08)',
5 | '0px 1px 5px rgba(0, 0, 0, 0.08)',
6 | '0px 1px 8px rgba(0, 0, 0, 0.08)',
7 | '0px 1px 10px rgba(0, 0, 0, 0.08)',
8 | '0px 1px 14px rgba(0, 0, 0, 0.08)',
9 | '0px 1px 18px rgba(0, 0, 0, 0.08)',
10 | '0px 2px 16px rgba(0, 0, 0, 0.08)',
11 | '0px 3px 14px rgba(0, 0, 0, 0.08)',
12 | '0px 3px 16px rgba(0, 0, 0, 0.08)',
13 | '0px 4px 18px rgba(0, 0, 0, 0.08)',
14 | '0px 4px 20px rgba(0, 0, 0, 0.08)',
15 | '0px 5px 22px rgba(0, 0, 0, 0.08)',
16 | '0px 5px 24px rgba(0, 0, 0, 0.08)',
17 | '0px 5px 26px rgba(0, 0, 0, 0.08)',
18 | '0px 6px 28px rgba(0, 0, 0, 0.08)',
19 | '0px 6px 30px rgba(0, 0, 0, 0.08)',
20 | '0px 6px 32px rgba(0, 0, 0, 0.08)',
21 | '0px 7px 34px rgba(0, 0, 0, 0.08)',
22 | '0px 7px 36px rgba(0, 0, 0, 0.08)',
23 | '0px 8px 38px rgba(0, 0, 0, 0.08)',
24 | '0px 8px 40px rgba(0, 0, 0, 0.08)',
25 | '0px 8px 42px rgba(0, 0, 0, 0.08)',
26 | '0px 9px 44px rgba(0, 0, 0, 0.08)',
27 | '0px 9px 46px rgba(0, 0, 0, 0.08)'
28 | ];
29 | };
30 |
--------------------------------------------------------------------------------
/src/theme/create-typography.js:
--------------------------------------------------------------------------------
1 | import "@fontsource/roboto/300.css";
2 | import "@fontsource/roboto/400.css";
3 | import "@fontsource/roboto/500.css";
4 | import "@fontsource/roboto/700.css";
5 | export const createTypography = () => {
6 | return {
7 | fontFamily: "'Roboto', 'Helvetica', 'Arial', 'sans-serif'",
8 | body1: {
9 | fontSize: "1rem",
10 | fontWeight: 400,
11 | lineHeight: 1.5,
12 | },
13 | body2: {
14 | fontSize: "0.875rem",
15 | fontWeight: 400,
16 | lineHeight: 1.57,
17 | },
18 | button: {
19 | fontWeight: 600,
20 | },
21 | caption: {
22 | fontSize: "0.75rem",
23 | fontWeight: 500,
24 | lineHeight: 1.66,
25 | },
26 | subtitle1: {
27 | fontSize: "1rem",
28 | fontWeight: 500,
29 | lineHeight: 1.57,
30 | },
31 | subtitle2: {
32 | fontSize: "0.875rem",
33 | fontWeight: 500,
34 | lineHeight: 1.57,
35 | },
36 | overline: {
37 | fontSize: "0.75rem",
38 | fontWeight: 500,
39 | letterSpacing: "0.5px",
40 | lineHeight: 2.5,
41 | textTransform: "uppercase",
42 | },
43 | h1: {
44 | fontWeight: 500,
45 | fontSize: 35,
46 | letterSpacing: "-0.24px",
47 | },
48 | h2: {
49 | fontWeight: 500,
50 | fontSize: 29,
51 | letterSpacing: "-0.24px",
52 | },
53 | h3: {
54 | fontWeight: 500,
55 | fontSize: 24,
56 | letterSpacing: "-0.06px",
57 | },
58 | h4: {
59 | fontWeight: 500,
60 | fontSize: 20,
61 | letterSpacing: "-0.06px",
62 | },
63 | h5: {
64 | fontWeight: 500,
65 | fontSize: 16,
66 | letterSpacing: "-0.05px",
67 | },
68 | h6: {
69 | fontWeight: 500,
70 | fontSize: 14,
71 | letterSpacing: "-0.05px",
72 | },
73 | };
74 | };
75 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import { createTheme as createMuiTheme } from "@mui/material";
2 | import { createPalette } from "./create-palette";
3 | import { createComponents } from "./create-components";
4 | import { createShadows } from "./create-shadows";
5 | import { createTypography } from "./create-typography";
6 |
7 | export function createTheme() {
8 | const palette = createPalette();
9 | const components = createComponents({ palette });
10 | const shadows = createShadows();
11 | const typography = createTypography();
12 |
13 | return createMuiTheme({
14 | breakpoints: {
15 | values: {
16 | xs: 0,
17 | sm: 600,
18 | md: 900,
19 | lg: 1200,
20 | xl: 1440,
21 | },
22 | },
23 | components,
24 | palette,
25 | shadows,
26 | shape: {
27 | borderRadius: 8,
28 | },
29 | typography,
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import store from "../store";
3 | import { userLogOut } from "../store/authSlice";
4 |
5 | // const isDev = process.env.NODE_ENV === "development";
6 | export const MODEL_API_URL =
7 | window.location.hostname === "localhost" ||
8 | window.location.hostname === "127.0.0.1"
9 | ? "http://127.0.0.1:8000/api/"
10 | : "https://smart-metrics-logbook.herokuapp.com/api/";
11 |
12 | // Create an instance of axios
13 | const api = axios.create({
14 | baseURL: MODEL_API_URL,
15 | headers: {
16 | Accept: "application/json",
17 | },
18 | });
19 | /*
20 | NOTE: intercept any error responses from the api
21 | and check if the token is no longer valid.
22 | ie. Token has expired or user is no longer
23 | authenticated.
24 | logout the user if the token has expired
25 | */
26 |
27 | api.interceptors.response.use(
28 | (res) => res,
29 | (err) => {
30 | if (err.response.status === 401) {
31 | store.dispatch(userLogOut());
32 | }
33 | return Promise.reject(err);
34 | }
35 | );
36 |
37 | export default api;
38 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as api } from "./api";
2 | export { default as setAuthToken } from "./setAuthToken";
3 | export { default as toast } from "./toast";
4 |
--------------------------------------------------------------------------------
/src/utils/setAuthToken.js:
--------------------------------------------------------------------------------
1 | import api from "./api";
2 |
3 | const setAuthToken = (token) => {
4 | if (token) {
5 | api.defaults.headers.common["x-auth-token"] = token;
6 | localStorage.setItem("smart-metrics-logbook", token);
7 | } else {
8 | delete api.defaults.headers["x-auth-token"];
9 | localStorage.removeItem("smart-metrics-logbook");
10 | }
11 | };
12 |
13 | export default setAuthToken;
14 |
--------------------------------------------------------------------------------
/src/utils/toast.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-toastify";
2 |
3 | const options = {
4 | autoClose: 5000,
5 | hideProgressBar: false,
6 | position: toast.POSITION.TOP_RIGHT,
7 | pauseOnHover: true,
8 | };
9 |
10 | const myToast = {
11 | success: (message) => {
12 | toast.success(message, options);
13 | },
14 | info: (message) => {
15 | toast.info(message, options);
16 | },
17 | error: (message) => {
18 | toast.error(message, options);
19 | },
20 | warning: (message) => {
21 | toast.warn(message, options);
22 | },
23 | };
24 |
25 | export default myToast;
26 |
--------------------------------------------------------------------------------