From bbb70d572ebbba7abcbe6d2cfb25ab2e52ea435b Mon Sep 17 00:00:00 2001 From: Aura Alba Date: Thu, 14 Dec 2023 10:42:01 -0500 Subject: [PATCH] feat: Add metrics students panel --- package-lock.json | 8 +- package.json | 2 +- src/assets/colors.scss | 6 ++ src/assets/global.scss | 1 + .../StudentsMetrics/_test_/index.test.jsx | 18 ++++ .../StudentsMetrics/_test_/reducer.test.jsx | 52 ++++++++++++ .../Students/StudentsMetrics/index.jsx | 82 +++++++++++++++++++ .../Students/StudentsMetrics/index.scss | 55 +++++++++++++ .../Students/StudentsMetrics/reducer.jsx | 32 ++++++++ src/features/Students/StudentsPage/index.jsx | 2 + src/features/Students/actionTypes.js | 4 + src/features/Students/data/api.js | 9 ++ 12 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 src/features/Students/StudentsMetrics/_test_/index.test.jsx create mode 100644 src/features/Students/StudentsMetrics/_test_/reducer.test.jsx create mode 100644 src/features/Students/StudentsMetrics/index.jsx create mode 100644 src/features/Students/StudentsMetrics/index.scss create mode 100644 src/features/Students/StudentsMetrics/reducer.jsx diff --git a/package-lock.json b/package-lock.json index 46508f16..8caf7ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "react": "16.14.0", "react-dom": "16.14.0", "react-intl": "^5.25.1", - "react-paragon-topaz": "^1.1.4", + "react-paragon-topaz": "^1.2.1", "react-router": "5.2.1", "react-router-dom": "5.3.0", "regenerator-runtime": "0.13.11" @@ -19837,9 +19837,9 @@ } }, "node_modules/react-paragon-topaz": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/react-paragon-topaz/-/react-paragon-topaz-1.1.4.tgz", - "integrity": "sha512-eq46XnhwkDtK3cmGlxNlnSdnsS5c0WXnU8L6+ZTWvYbIvBWiSkWcHJzpwNaR/VSbpVqQdaXuaZQkSPy2rVcUdA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-paragon-topaz/-/react-paragon-topaz-1.2.1.tgz", + "integrity": "sha512-dQE8LHUTKfPGGLW2Flg0n1FclPnlcRifaurO4Bq4i6G6e6gM80hDhFqo8cIK9bJ/v5TGM4FiOyLwz2Eu8UExAg==", "dependencies": { "@edx/paragon": "^20.45.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", diff --git a/package.json b/package.json index 1804251c..b81c4205 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "react": "16.14.0", "react-dom": "16.14.0", "react-intl": "^5.25.1", - "react-paragon-topaz": "^1.1.4", + "react-paragon-topaz": "^1.2.1", "react-router": "5.2.1", "react-router-dom": "5.3.0", "regenerator-runtime": "0.13.11" diff --git a/src/assets/colors.scss b/src/assets/colors.scss index be925506..68cd8205 100644 --- a/src/assets/colors.scss +++ b/src/assets/colors.scss @@ -7,3 +7,9 @@ $color-gray: #60646d; $gradient-gray: rgba(247, 249, 253, 0.1); $color-active-button: #989ba3; $bg-main-color: white; +$gray-light: #dfe1e1; +$color-pink: #ffd0d7; +$color-green: #c4eacd; +$color-yellow: #f2f4be; +$color-purple: #e4b8d6; +$black-80: #333; diff --git a/src/assets/global.scss b/src/assets/global.scss index ec524eff..8b575af5 100644 --- a/src/assets/global.scss +++ b/src/assets/global.scss @@ -2,6 +2,7 @@ border: 1px solid #dfe1e1; border-radius: 0.375rem; padding: 20px 0; + box-shadow: 0px 3px 12px 0px rgba(0, 0, 0, 0.16); } .filter-container > div { diff --git a/src/features/Students/StudentsMetrics/_test_/index.test.jsx b/src/features/Students/StudentsMetrics/_test_/index.test.jsx new file mode 100644 index 00000000..44e66678 --- /dev/null +++ b/src/features/Students/StudentsMetrics/_test_/index.test.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import StudentsMetrics from 'features/Students/StudentsMetrics'; +import '@testing-library/jest-dom/extend-expect'; + +describe('StudentsMetrics component', () => { + test('renders components', () => { + const { getByText } = render( + , + ); + + expect(getByText('This week')).toBeInTheDocument(); + expect(getByText('Next week')).toBeInTheDocument(); + expect(getByText('Next month')).toBeInTheDocument(); + expect(getByText('New students registered')).toBeInTheDocument(); + expect(getByText('Classes scheduled')).toBeInTheDocument(); + }); +}); diff --git a/src/features/Students/StudentsMetrics/_test_/reducer.test.jsx b/src/features/Students/StudentsMetrics/_test_/reducer.test.jsx new file mode 100644 index 00000000..08bb8de4 --- /dev/null +++ b/src/features/Students/StudentsMetrics/_test_/reducer.test.jsx @@ -0,0 +1,52 @@ +import { + FETCH_METRICS_DATA_REQUEST, + FETCH_METRICS_DATA_SUCCESS, + FETCH_METRICS_DATA_FAILURE, +} from 'features/Students/actionTypes'; +import { RequestStatus } from 'features/constants'; +import reducer from 'features/Students/StudentsMetrics/reducer'; + +describe('Student filter reducers', () => { + const initialState = { + data: [], + status: RequestStatus.SUCCESS, + error: null, + }; + + test('should handle FETCH_METRICS_DATA_REQUEST', () => { + const state = { + ...initialState, + status: RequestStatus.LOADING, + }; + const action = { + type: FETCH_METRICS_DATA_REQUEST, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_METRICS_DATA_SUCCESS', () => { + const state = { + ...initialState, + status: RequestStatus.SUCCESS, + data: [], + }; + const action = { + type: FETCH_METRICS_DATA_SUCCESS, + payload: [], + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_METRICS_DATA_FAILURE', () => { + const state = { + ...initialState, + status: RequestStatus.ERROR, + error: '', + }; + const action = { + type: FETCH_METRICS_DATA_FAILURE, + payload: '', + }; + expect(reducer(state, action)).toEqual(state); + }); +}); diff --git a/src/features/Students/StudentsMetrics/index.jsx b/src/features/Students/StudentsMetrics/index.jsx new file mode 100644 index 00000000..a45e75e1 --- /dev/null +++ b/src/features/Students/StudentsMetrics/index.jsx @@ -0,0 +1,82 @@ +import React, { useEffect, useReducer } from 'react'; +import { Card, CardGrid, ToggleButton } from '@edx/paragon'; +import { ToggleButtonGroup } from 'react-paragon-topaz'; +import { logError } from '@edx/frontend-platform/logging'; +import { camelCaseObject } from '@edx/frontend-platform'; +import { getMetricsStudents } from 'features/Students/data/api'; +import { RequestStatus } from 'features/constants'; +import reducer from 'features/Students/StudentsMetrics/reducer'; +import { + FETCH_METRICS_DATA_REQUEST, + FETCH_METRICS_DATA_SUCCESS, + FETCH_METRICS_DATA_FAILURE, +} from 'features/Students/actionTypes'; +import 'features/Students/StudentsMetrics/index.scss'; + +const initialState = { + data: [], + status: RequestStatus.SUCCESS, + error: null, +}; + +const StudentsMetrics = () => { + const [state, dispatch] = useReducer(reducer, initialState); + + const fetchMetricsData = async () => { + dispatch({ type: FETCH_METRICS_DATA_REQUEST }); + + try { + const response = camelCaseObject(await getMetricsStudents()); + dispatch({ type: FETCH_METRICS_DATA_SUCCESS, payload: response }); + } catch (error) { + dispatch({ type: FETCH_METRICS_DATA_FAILURE, payload: error }); + logError(error); + } + }; + + useEffect(() => { + fetchMetricsData(); + }, []); + + return ( +
+ + + This week + + + Next week + + + Next month + + + + + + +
{state.data.newStudentsRegistered}
+
+
+ + + +
{state.data.classesScheduled}
+
+
+
+
+ ); +}; + +export default StudentsMetrics; diff --git a/src/features/Students/StudentsMetrics/index.scss b/src/features/Students/StudentsMetrics/index.scss new file mode 100644 index 00000000..6601f0cb --- /dev/null +++ b/src/features/Students/StudentsMetrics/index.scss @@ -0,0 +1,55 @@ +@import "assets/colors.scss"; + +.container-cards { + border: 1px solid $gray-light; + padding: 2rem; + border-radius: 0.375rem; + margin-bottom: 2rem; + box-shadow: 0px 3px 12px 0px rgba(0, 0, 0, 0.16); + + .pgn__card { + box-shadow: none; + min-height: 160px; + + .pgn__card-header .pgn__card-header-content { + margin-top: 1rem; + } + + .pgn__card-header .pgn__card-header-title-md { + font-size: 1rem; + text-align: center; + font-weight: 600; + color: $black-80; + } + + .pgn__card-section { + text-align: center; + } + } + + .card-pink { + background: $color-pink; + } + + .card-green { + background: $color-green; + } + + .card-yellow { + background: $color-yellow; + } + + .card-purple { + background: $color-purple; + } + + .card-number { + font-size: 44px; + color: $black-80; + } + + .btn-group-tpz { + width: 45%; + margin-bottom: 2rem; + } +} diff --git a/src/features/Students/StudentsMetrics/reducer.jsx b/src/features/Students/StudentsMetrics/reducer.jsx new file mode 100644 index 00000000..0e839e41 --- /dev/null +++ b/src/features/Students/StudentsMetrics/reducer.jsx @@ -0,0 +1,32 @@ +import { + FETCH_METRICS_DATA_REQUEST, + FETCH_METRICS_DATA_SUCCESS, + FETCH_METRICS_DATA_FAILURE, +} from 'features/Students/actionTypes'; +import { RequestStatus } from 'features/constants'; + +const reducer = (state, action) => { + switch (action.type) { + case FETCH_METRICS_DATA_REQUEST: + return { + ...state, + status: RequestStatus.LOADING, + }; + case FETCH_METRICS_DATA_SUCCESS: + return { + ...state, + status: RequestStatus.SUCCESS, + data: action.payload, + }; + case FETCH_METRICS_DATA_FAILURE: + return { + ...state, + status: RequestStatus.ERROR, + error: action.payload, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/features/Students/StudentsPage/index.jsx b/src/features/Students/StudentsPage/index.jsx index 766633cd..2741795f 100644 --- a/src/features/Students/StudentsPage/index.jsx +++ b/src/features/Students/StudentsPage/index.jsx @@ -1,6 +1,7 @@ import { getStudentbyInstitutionAdmin } from 'features/Students/data/api'; import { StudentsTable } from 'features/Students/StudentsTable/index'; import StudentsFilters from 'features/Students/StudentsFilters'; +import StudentsMetrics from 'features/Students/StudentsMetrics'; import { RequestStatus } from 'features/constants'; import reducer from 'features/Students/StudentsPage/reducer'; @@ -60,6 +61,7 @@ const StudentsPage = () => { return (

Students

+