diff --git a/package.json b/package.json index 4358a2787..0ebad66ab 100755 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "react-select": "^5.7.4", "react-table": "^7.8.0", "react-toastify": "^9.0.8", + "recharts": "^2.12.7", "redux": "^4.2.0", "redux-devtools-extension": "^2.13.9", "redux-state-sync": "^3.1.4", diff --git a/src/components/Performance/PerformanceChart.tsx b/src/components/Performance/PerformanceChart.tsx new file mode 100644 index 000000000..ff95b454b --- /dev/null +++ b/src/components/Performance/PerformanceChart.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { useTheme } from "../../hooks/darkmode"; +import PerformanceChartHeader from "./PerformanceChartHeader"; +import PerformanceChartGraph from "./PerformanceChartGraph"; + +interface PerformanceChartProps { + data: { name: string; performance: number }[]; + currentDate: string; +} + +const PerformanceChart: React.FC = ({ + data, + currentDate, +}) => { + const { theme } = useTheme(); + + return ( +
+ +
+ +
+
+ ); +}; + +export default PerformanceChart; diff --git a/src/components/Performance/PerformanceChartGraph.tsx b/src/components/Performance/PerformanceChartGraph.tsx new file mode 100644 index 000000000..097634951 --- /dev/null +++ b/src/components/Performance/PerformanceChartGraph.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { + AreaChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Area, +} from "recharts"; + +interface PerformanceChartGraphProps { + data: { name: string; performance: number }[]; + theme: boolean; +} + +const PerformanceChartGraph: React.FC = ({ + data, + theme, +}) => ( + + + + + + + + + + + + + + + + +); + +export default PerformanceChartGraph; diff --git a/src/components/Performance/PerformanceChartHeader.tsx b/src/components/Performance/PerformanceChartHeader.tsx new file mode 100644 index 000000000..0cf2b8197 --- /dev/null +++ b/src/components/Performance/PerformanceChartHeader.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +interface PerformanceChartHeaderProps { + theme: boolean; + currentDate: string; +} + +const PerformanceChartHeader: React.FC = ({ + theme, + currentDate, +}) => ( + <> +

+ Overall performance +

+

+ as of {currentDate} +

+

+ Overall performance +

+ +); + +export default PerformanceChartHeader; diff --git a/src/components/StatCard.tsx b/src/components/StatCard.tsx new file mode 100644 index 000000000..5ff77c028 --- /dev/null +++ b/src/components/StatCard.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { useTheme } from "../hooks/darkmode"; + +interface StatCardProps { + icon: JSX.Element; + title: string; + count: number | string; +} + +const StatCard: React.FC = ({ icon, title, count }) => { + const { theme } = useTheme(); + return ( +
+ {icon} +

{title}

+ + {count} + +
+ ); +}; + +export default StatCard; diff --git a/src/components/iconss/SvgIcons.tsx b/src/components/iconss/SvgIcons.tsx new file mode 100644 index 000000000..f09f7a2ea --- /dev/null +++ b/src/components/iconss/SvgIcons.tsx @@ -0,0 +1,73 @@ +import React from "react"; + +export const CoordinatorIcon = () => ( + + + +); + +export const PerformanceIcon = () => ( + + + +); + +export const TraineeIcon = () => ( + + + +); + +export const CohortIcon = () => ( + + + + +); diff --git a/src/hooks/useDashboardData.ts b/src/hooks/useDashboardData.ts new file mode 100644 index 000000000..cdd9c8eed --- /dev/null +++ b/src/hooks/useDashboardData.ts @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; +import { getAlltraineeapplicants } from "../redux/actions/filterTraineeActions"; +import { getAllCohorts } from "../redux/actions/cohortActions"; +import { getAllCoordinators } from "../redux/actions/users"; +import { getAllPrograms } from "../redux/actions/programsActions"; + +type PerformanceMetric = { + name: string; + performance: number; +}; + +export const useDashboardData = () => { + const [traineeCount, setTraineeCount] = useState(0); + const [cohortCount, setCohortCount] = useState(0); + const [coordinatorCount, setCoordinatorCount] = useState(0); + const [programCount, setProgramCount] = useState(0); + const [averagePerformance, setAveragePerformance] = useState(0); + const [performanceData, setPerformanceData] = useState( + [] + ); + const [currentDate, setCurrentDate] = useState(""); + const dispatch = useDispatch(); + useEffect(() => { + const fetchData = async () => { + try { + const trainees = await dispatch(getAlltraineeapplicants()); + const cohorts = await dispatch(getAllCohorts()); + const coordinators = await dispatch(getAllCoordinators()); + const programs = await dispatch(getAllPrograms()); + setTraineeCount(trainees); + setCohortCount(cohorts); + setCoordinatorCount(coordinators); + setProgramCount(programs); + + const performanceMetric: PerformanceMetric[] = [ + { name: "0", performance: trainees / coordinators }, + { name: "1", performance: trainees / programs }, + { name: "2", performance: cohorts / programs }, + { name: "3", performance: programs / coordinators }, + { name: "4", performance: trainees / cohorts }, + { name: "5", performance: coordinators / cohorts }, + { name: "6", performance: programs / trainees }, + { name: "7", performance: coordinators / programs }, + { name: "8", performance: programs / cohorts }, + { name: "9 Sprint", performance: coordinators / trainees }, + ]; + setPerformanceData(performanceMetric); + const totalPerformance = performanceMetric.reduce( + (acc, curr) => acc + curr.performance, + 0 + ); + setAveragePerformance(totalPerformance / performanceMetric.length); + } catch (error) {} + }; + const formatDate = () => { + const date = new Date(); + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "long", + day: "numeric", + }; + return date.toLocaleDateString(undefined, options); + }; + setCurrentDate(formatDate()); + fetchData(); + }, [dispatch]); + return { + traineeCount, + cohortCount, + coordinatorCount, + programCount, + performanceData, + averagePerformance, + currentDate, + }; +}; diff --git a/src/index.css b/src/index.css index 7a9846d7b..f453c3630 100755 --- a/src/index.css +++ b/src/index.css @@ -11,7 +11,10 @@ display: none !important; } body { - background: #f4f4f4; + background: #262E3D; +} +body.light-mode { + background: #ffffff; } /* Other existing CSS rules for input and label styles */ diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 4bab6a810..89fd7c1f0 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,18 +1,76 @@ -import React from "react"; +import React, { useEffect } from "react"; import NavBar from "../components/sidebar/navHeader"; +import StatCard from "../components/StatCard"; +import PerformanceChart from "../components/Performance/PerformanceChart"; +import { + CoordinatorIcon, + PerformanceIcon, + TraineeIcon, + CohortIcon, +} from "../components/iconss/SvgIcons"; +import { useDashboardData } from "../hooks/useDashboardData"; +import { useTheme } from "../hooks/darkmode"; + const Dashboard = () => { + + const { + traineeCount, + cohortCount, + coordinatorCount, + programCount, + performanceData, + averagePerformance, + currentDate, + } = useDashboardData(); + const { theme, setTheme } = useTheme(); + useEffect(() => { + if (theme) { + document.body.classList.add("light-mode"); + } else { + document.body.classList.remove("light-mode"); + } + }, [theme]); + return ( <> -
-
-
-

Dashboard Page

-
+
+

+ ADMIN DASHBOARD +

+ +
+ } + title="All coordinators" + count={coordinatorCount} + /> + } + title="All trainees" + count={traineeCount} + /> + } title="Cohorts" count={cohortCount} /> + } + title="Performance" + count={averagePerformance.toFixed(2)} + />
+ +
); + }; export default Dashboard; diff --git a/src/redux/actions/cohortActions.ts b/src/redux/actions/cohortActions.ts index 5f02908c5..acfe118bc 100644 --- a/src/redux/actions/cohortActions.ts +++ b/src/redux/actions/cohortActions.ts @@ -21,9 +21,8 @@ export const getAllCohorts = () => async (dispatch: any) => { }); const cohorts = await datas.data.data.getAllCohorts; dispatch(creator(GET_COHORTS, cohorts)); + return cohorts.length; } catch (error) { - if (error) { - return console.log(error); - } + return 0; } }; diff --git a/src/redux/actions/filterTraineeActions.ts b/src/redux/actions/filterTraineeActions.ts index 12361a6ee..954dba8aa 100644 --- a/src/redux/actions/filterTraineeActions.ts +++ b/src/redux/actions/filterTraineeActions.ts @@ -99,10 +99,9 @@ export const getAlltraineeapplicants = () => async (dispatch: any) => { type: fetchtrainapplicantcount.fetchtrainapplicantcount_success, data: totalTraineeApllicants, }); + return totalTraineeApllicants; } catch (error) { - if (error) { - return console.log(error); - } + return 0; } }; diff --git a/src/redux/actions/programsActions.ts b/src/redux/actions/programsActions.ts index 30490cc53..80dc60af8 100644 --- a/src/redux/actions/programsActions.ts +++ b/src/redux/actions/programsActions.ts @@ -27,9 +27,8 @@ export const getAllPrograms = () => async (dispatch: any) => { }); const programs = await datas.data.data.getAll; dispatch(creator(GET_PROGRAMS, programs)); + return programs.length; } catch (error) { - if (error) { - return console.log(error); - } + return 0; } }; diff --git a/src/redux/actions/users.ts b/src/redux/actions/users.ts index 98d02678f..e1374098d 100644 --- a/src/redux/actions/users.ts +++ b/src/redux/actions/users.ts @@ -88,4 +88,39 @@ export const assignMemberRoles= async (userId, roleId) => { return err; } -} \ No newline at end of file +} + +export const getAllCoordinators = () => async (dispatch: any) => { + try { + const data = await axios.post("/", { + query: ` + query getMembers { + getUsers_Logged { + firstname + lastname + email + role { + roleName + } + } + } + `, + }); + + const members = data.data.data.getUsers_Logged; + const coordinators = members.filter( + (member: any) => + member.role.roleName === "superAdmin" || + member.role.roleName === "admin" + ); + + dispatch({ + type: "FETCH_COORDINATORS", + data: coordinators, + }); + + return coordinators.length; + } catch (err) { + return 0; + } +}; \ No newline at end of file