From 0643d85ce2e826229516c75f3781f5a28ee90052 Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Wed, 13 Nov 2024 23:06:41 +0200 Subject: [PATCH 1/9] main admin dashboard --- package-lock.json | 10 ++- package.json | 2 +- src/Chart/AppointmentsChart.tsx | 113 +++++++++++++++++++++++++++ src/Chart/BarChart.tsx | 101 ++++++++++++++++++++++++ src/components/ProgramUsersModal.tsx | 28 +++---- src/pages/AdminDashboard.tsx | 29 ++++--- 6 files changed, 254 insertions(+), 29 deletions(-) create mode 100644 src/Chart/AppointmentsChart.tsx create mode 100644 src/Chart/BarChart.tsx diff --git a/package-lock.json b/package-lock.json index 172e4d9a0..0553666ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "apollo-upload-client": "^17.0.0", "autoprefixer": "^10.4.14", "axios": "^1.6.1", - "chart.js": "^4.3.2", + "chart.js": "^4.4.6", "cleave.js": "^1.6.0", "cloudinary": "^1.39.0", "cloudinary-react": "^1.8.1", @@ -7197,9 +7197,10 @@ } }, "node_modules/chart.js": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", - "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", + "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", + "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -19698,6 +19699,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "license": "MIT", "peerDependencies": { "chart.js": "^4.1.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/package.json b/package.json index a053d4731..e39a6b701 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "apollo-upload-client": "^17.0.0", "autoprefixer": "^10.4.14", "axios": "^1.6.1", - "chart.js": "^4.3.2", + "chart.js": "^4.4.6", "cleave.js": "^1.6.0", "cloudinary": "^1.39.0", "cloudinary-react": "^1.8.1", diff --git a/src/Chart/AppointmentsChart.tsx b/src/Chart/AppointmentsChart.tsx new file mode 100644 index 000000000..8713df2fb --- /dev/null +++ b/src/Chart/AppointmentsChart.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { Line } from 'react-chartjs-2'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +); + +// eslint-disable-next-line react/function-component-definition +const AppointmentsChart: React.FC = () => { + const data = { + labels: [ + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + '24', + '25', + '26', + '27', + '28', + '29', + '30', + '31', + ], + datasets: [ + { + label: 'Last days', + data: [ + 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, + 3, 8, 0, 3, 5, 7, + ], + fill: false, + borderColor: '#4F46E5', + tension: 0.4, + }, + { + label: 'Last days', + data: [ + 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, + 1, 2, 3, 4, 5, 6.5, + ], + fill: false, + borderColor: '#8C8120', + tension: 0.4, + }, + ], + }; + + const options = { + responsive: true, + plugins: { + legend: { + position: 'bottom' as const, + }, + }, + scales: { + y: { + beginAtZero: true, + grid: { + color: '#D1D5DB', + }, + }, + x: { + grid: { + display: false, + }, + }, + }, + }; + + return ( +
+ +
+ ); +}; + +export default AppointmentsChart; diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx new file mode 100644 index 000000000..b7a81e611 --- /dev/null +++ b/src/Chart/BarChart.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Bar } from 'react-chartjs-2'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, +); + +interface Props {} + +// eslint-disable-next-line react/function-component-definition +const BarChart: React.FC = () => { + const data = { + labels: [ + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10', + '11', + '12', + ], + datasets: [ + { + label: 'Last 8 days', + data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], + backgroundColor: '#5A6ACF', + borderRadius: 0, + barThickness: 8, + }, + { + label: 'Last Week', + data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], + backgroundColor: '#D1D5DB', + borderRadius: 0, + barThickness: 8, + }, + ], + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom' as const, + labels: { + color: '#121212', + }, + }, + tooltip: { + enabled: true, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: '#737B8B', + }, + }, + y: { + grid: { + borderDash: [5, 5], + color: '#ffffff', + }, + ticks: { + color: '#ffffff', + }, + }, + }, + }; + + return ( +
+ +
+ ); +}; + +export default BarChart; diff --git a/src/components/ProgramUsersModal.tsx b/src/components/ProgramUsersModal.tsx index 64c47b1f2..dbdd1a78f 100644 --- a/src/components/ProgramUsersModal.tsx +++ b/src/components/ProgramUsersModal.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { gql, useQuery } from '@apollo/client'; -import DataTable from './DataTable'; import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import { styled } from '@mui/material/styles'; +import DataTable from './DataTable'; interface User { email: string; @@ -70,7 +70,7 @@ export function ProgramUsersModal({ isOpen, onClose, // defaultProgram = 'default', - programName + programName, }: ProgramUsersModalProps) { const { data, loading, error } = useQuery(GET_ALL_USERS, { variables: { @@ -79,10 +79,11 @@ export function ProgramUsersModal({ skip: !isOpen, }); - const programUsers = data?.getAllUsers.filter( - (user: User) => user.team?.cohort?.program?.name === programName + const programUsers = + data?.getAllUsers.filter( + (user: User) => user.team?.cohort?.program?.name === programName, // || (user.team === null && programName === defaultProgram) - ) || []; + ) || []; const columns = [ { @@ -91,7 +92,11 @@ export function ProgramUsersModal({ Cell: ({ value }: CellProps) => (
- + @@ -119,7 +124,7 @@ export function ProgramUsersModal({ if (loading) { return (
-
+
); } @@ -152,12 +157,7 @@ export function ProgramUsersModal({ }; return ( - +
{programName} - Users @@ -168,4 +168,4 @@ export function ProgramUsersModal({
); -} \ No newline at end of file +} diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 660d06df4..9b8c23926 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,6 +3,8 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import BarChart from '../Chart/BarChart'; +import AppointmentsChart from '../Chart/AppointmentsChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; @@ -54,11 +56,10 @@ function SupAdDashboard() { }, [inviteEmail]); return ( <> - {/* =========================== Start:: InviteTraineeModel =============================== */} -
@@ -122,12 +123,20 @@ function SupAdDashboard() {
- {/* =========================== End:: InviteTraineeModel =============================== */} -
-
-
- +
+
+
+ Users +
+ +
+ +
+
+ Teams +
+
@@ -135,4 +144,4 @@ function SupAdDashboard() { ); } -export default SupAdDashboard; \ No newline at end of file +export default SupAdDashboard; From e90781bd978b504ddf0cbed8c64084987449949d Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Thu, 14 Nov 2024 09:10:59 +0200 Subject: [PATCH 2/9] new piechart and stats updated --- src/Chart/BarChart.tsx | 6 +- src/Chart/PieChart.tsx | 96 +++++++++++++++++++ .../{AppointmentsChart.tsx => UsersChart.tsx} | 8 +- src/pages/AdminDashboard.tsx | 9 +- 4 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 src/Chart/PieChart.tsx rename src/Chart/{AppointmentsChart.tsx => UsersChart.tsx} (92%) diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx index b7a81e611..895a544a0 100644 --- a/src/Chart/BarChart.tsx +++ b/src/Chart/BarChart.tsx @@ -40,14 +40,14 @@ const BarChart: React.FC = () => { ], datasets: [ { - label: 'Last 8 days', + label: 'Nova', data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], backgroundColor: '#5A6ACF', borderRadius: 0, barThickness: 8, }, { - label: 'Last Week', + label: 'Fighters', data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], backgroundColor: '#D1D5DB', borderRadius: 0, @@ -63,7 +63,7 @@ const BarChart: React.FC = () => { legend: { position: 'bottom' as const, labels: { - color: '#121212', + color: '#D1D5DB', }, }, tooltip: { diff --git a/src/Chart/PieChart.tsx b/src/Chart/PieChart.tsx new file mode 100644 index 000000000..2ad039f2c --- /dev/null +++ b/src/Chart/PieChart.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +// eslint-disable-next-line react/function-component-definition +const PieChart: React.FC = () => { + const data = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 100], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data2 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 70], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data3 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [60, 60], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + + const options = { + responsive: true, + cutout: '70%', + plugins: { + tooltip: { + callbacks: { + // eslint-disable-next-line func-names, object-shorthand + label: function (tooltipItem: any) { + return `${tooltipItem.label}: ${tooltipItem.raw}%`; + }, + }, + }, + legend: { + display: false, + }, + }, + }; + + return ( +
+
+
+ +
+
+

10

+
+
+

New Invitations & Registration

+
+
+ +
+
+

20

+
+
+

Upcoming Events

+
+
+ +
+
+

50

+
+
+

Active& Progressive Tickets

+
+
+
+ ); +}; + +export default PieChart; diff --git a/src/Chart/AppointmentsChart.tsx b/src/Chart/UsersChart.tsx similarity index 92% rename from src/Chart/AppointmentsChart.tsx rename to src/Chart/UsersChart.tsx index 8713df2fb..ee3ab2aaf 100644 --- a/src/Chart/AppointmentsChart.tsx +++ b/src/Chart/UsersChart.tsx @@ -22,7 +22,7 @@ ChartJS.register( ); // eslint-disable-next-line react/function-component-definition -const AppointmentsChart: React.FC = () => { +const usersChart: React.FC = () => { const data = { labels: [ '01', @@ -59,7 +59,7 @@ const AppointmentsChart: React.FC = () => { ], datasets: [ { - label: 'Last days', + label: 'Andela', data: [ 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, 3, 8, 0, 3, 5, 7, @@ -69,7 +69,7 @@ const AppointmentsChart: React.FC = () => { tension: 0.4, }, { - label: 'Last days', + label: 'NESA', data: [ 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, 1, 2, 3, 4, 5, 6.5, @@ -110,4 +110,4 @@ const AppointmentsChart: React.FC = () => { ); }; -export default AppointmentsChart; +export default usersChart; diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 9b8c23926..574710315 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,8 +3,9 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import PieChart from '../Chart/PieChart'; import BarChart from '../Chart/BarChart'; -import AppointmentsChart from '../Chart/AppointmentsChart'; +import UsersChart from '../Chart/usersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; @@ -25,7 +26,6 @@ function SupAdDashboard() { const inviteModel = () => { const newState = !inviteTraineeModel; setInviteTraineeModel(newState); - // this is true }; const [inviteUser] = useMutation(INVITE_USER_MUTATION, { @@ -125,11 +125,14 @@ function SupAdDashboard() {
+
+ +
Users
- +
From a0523255c2ac77ba06a5a795a21f5ed3d17f5630 Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Thu, 14 Nov 2024 09:10:59 +0200 Subject: [PATCH 3/9] new piechart and stats updated --- src/Chart/BarChart.tsx | 6 +- src/Chart/PieChart.tsx | 96 ++++++++++++++++ .../{AppointmentsChart.tsx => UsersChart.tsx} | 8 +- src/components/AdminDashboardTable.tsx | 103 ++++++++++++++++++ src/pages/AdminDashboard.tsx | 22 +++- 5 files changed, 223 insertions(+), 12 deletions(-) create mode 100644 src/Chart/PieChart.tsx rename src/Chart/{AppointmentsChart.tsx => UsersChart.tsx} (92%) create mode 100644 src/components/AdminDashboardTable.tsx diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx index b7a81e611..895a544a0 100644 --- a/src/Chart/BarChart.tsx +++ b/src/Chart/BarChart.tsx @@ -40,14 +40,14 @@ const BarChart: React.FC = () => { ], datasets: [ { - label: 'Last 8 days', + label: 'Nova', data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], backgroundColor: '#5A6ACF', borderRadius: 0, barThickness: 8, }, { - label: 'Last Week', + label: 'Fighters', data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], backgroundColor: '#D1D5DB', borderRadius: 0, @@ -63,7 +63,7 @@ const BarChart: React.FC = () => { legend: { position: 'bottom' as const, labels: { - color: '#121212', + color: '#D1D5DB', }, }, tooltip: { diff --git a/src/Chart/PieChart.tsx b/src/Chart/PieChart.tsx new file mode 100644 index 000000000..2ad039f2c --- /dev/null +++ b/src/Chart/PieChart.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +// eslint-disable-next-line react/function-component-definition +const PieChart: React.FC = () => { + const data = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 100], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data2 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 70], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data3 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [60, 60], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + + const options = { + responsive: true, + cutout: '70%', + plugins: { + tooltip: { + callbacks: { + // eslint-disable-next-line func-names, object-shorthand + label: function (tooltipItem: any) { + return `${tooltipItem.label}: ${tooltipItem.raw}%`; + }, + }, + }, + legend: { + display: false, + }, + }, + }; + + return ( +
+
+
+ +
+
+

10

+
+
+

New Invitations & Registration

+
+
+ +
+
+

20

+
+
+

Upcoming Events

+
+
+ +
+
+

50

+
+
+

Active& Progressive Tickets

+
+
+
+ ); +}; + +export default PieChart; diff --git a/src/Chart/AppointmentsChart.tsx b/src/Chart/UsersChart.tsx similarity index 92% rename from src/Chart/AppointmentsChart.tsx rename to src/Chart/UsersChart.tsx index 8713df2fb..ee3ab2aaf 100644 --- a/src/Chart/AppointmentsChart.tsx +++ b/src/Chart/UsersChart.tsx @@ -22,7 +22,7 @@ ChartJS.register( ); // eslint-disable-next-line react/function-component-definition -const AppointmentsChart: React.FC = () => { +const usersChart: React.FC = () => { const data = { labels: [ '01', @@ -59,7 +59,7 @@ const AppointmentsChart: React.FC = () => { ], datasets: [ { - label: 'Last days', + label: 'Andela', data: [ 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, 3, 8, 0, 3, 5, 7, @@ -69,7 +69,7 @@ const AppointmentsChart: React.FC = () => { tension: 0.4, }, { - label: 'Last days', + label: 'NESA', data: [ 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, 1, 2, 3, 4, 5, 6.5, @@ -110,4 +110,4 @@ const AppointmentsChart: React.FC = () => { ); }; -export default AppointmentsChart; +export default usersChart; diff --git a/src/components/AdminDashboardTable.tsx b/src/components/AdminDashboardTable.tsx new file mode 100644 index 000000000..e04847f70 --- /dev/null +++ b/src/components/AdminDashboardTable.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { FaEye } from 'react-icons/fa'; + +const DashboardTableDesign = () => { + const dummyData = [ + { team: 'Nova', logins: '2.4k', users: 45 }, + { team: 'Fighters', logins: '1.8k', users: 32 }, + { team: 'Bitcrafters', logins: '1.2k', users: 28}, + { team: 'Team1', logins: '3.1k', users: 52 }, + { team: 'Team2', logins: '0.9k', users: 19 }, + ]; + + return ( +
+
+

Team Analytics

+
+ +
+
+ +
+
+ + + + + + + + + + + {dummyData.map((row, index) => ( + + + + + + + ))} + +
+
+ Team Name +
+
+
+ Logins +
+
+
+ Users +
+
+
+ Actions +
+
+
+
+ {row.team} +
+
+
{row.logins}
+
+
+ {row.users} +
+
+ +
+
+ +
+
+ Showing 5 of 12 teams +
+
+ + +
+
+
+
+ ); +}; + +export default DashboardTableDesign; \ No newline at end of file diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 9b8c23926..216ac3c19 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,16 +3,19 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import PieChart from '../Chart/PieChart'; import BarChart from '../Chart/BarChart'; -import AppointmentsChart from '../Chart/AppointmentsChart'; +import UsersChart from '../Chart/usersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; +import { FaEye } from 'react-icons/fa'; +import DashboardTableDesign from '../components/AdminDashboardTable'; -function SupAdDashboard() { +function AdminDashboard() { const { user } = useContext(UserContext); const { t }: any = useTranslation(); @@ -25,7 +28,6 @@ function SupAdDashboard() { const inviteModel = () => { const newState = !inviteTraineeModel; setInviteTraineeModel(newState); - // this is true }; const [inviteUser] = useMutation(INVITE_USER_MUTATION, { @@ -125,11 +127,14 @@ function SupAdDashboard() {
+
+ +
Users
- +
@@ -140,8 +145,15 @@ function SupAdDashboard() {
+
+ +
); } -export default SupAdDashboard; +export default AdminDashboard; + + + + From f99dac5cc9d805fb034cde94ab03e42bbd90e469 Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Thu, 14 Nov 2024 09:10:59 +0200 Subject: [PATCH 4/9] new piechart and stats updated --- src/Chart/BarChart.tsx | 6 +- src/Chart/PieChart.tsx | 96 +++++++++ .../{AppointmentsChart.tsx => UsersChart.tsx} | 8 +- src/components/AdminDashboardTable.tsx | 195 ++++++++++++++++++ src/components/AdminTeamDetails.tsx | 156 ++++++++++++++ src/pages/AdminDashboard.tsx | 22 +- 6 files changed, 471 insertions(+), 12 deletions(-) create mode 100644 src/Chart/PieChart.tsx rename src/Chart/{AppointmentsChart.tsx => UsersChart.tsx} (92%) create mode 100644 src/components/AdminDashboardTable.tsx create mode 100644 src/components/AdminTeamDetails.tsx diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx index b7a81e611..895a544a0 100644 --- a/src/Chart/BarChart.tsx +++ b/src/Chart/BarChart.tsx @@ -40,14 +40,14 @@ const BarChart: React.FC = () => { ], datasets: [ { - label: 'Last 8 days', + label: 'Nova', data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], backgroundColor: '#5A6ACF', borderRadius: 0, barThickness: 8, }, { - label: 'Last Week', + label: 'Fighters', data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], backgroundColor: '#D1D5DB', borderRadius: 0, @@ -63,7 +63,7 @@ const BarChart: React.FC = () => { legend: { position: 'bottom' as const, labels: { - color: '#121212', + color: '#D1D5DB', }, }, tooltip: { diff --git a/src/Chart/PieChart.tsx b/src/Chart/PieChart.tsx new file mode 100644 index 000000000..2ad039f2c --- /dev/null +++ b/src/Chart/PieChart.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +// eslint-disable-next-line react/function-component-definition +const PieChart: React.FC = () => { + const data = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 100], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data2 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 70], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data3 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [60, 60], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + + const options = { + responsive: true, + cutout: '70%', + plugins: { + tooltip: { + callbacks: { + // eslint-disable-next-line func-names, object-shorthand + label: function (tooltipItem: any) { + return `${tooltipItem.label}: ${tooltipItem.raw}%`; + }, + }, + }, + legend: { + display: false, + }, + }, + }; + + return ( +
+
+
+ +
+
+

10

+
+
+

New Invitations & Registration

+
+
+ +
+
+

20

+
+
+

Upcoming Events

+
+
+ +
+
+

50

+
+
+

Active& Progressive Tickets

+
+
+
+ ); +}; + +export default PieChart; diff --git a/src/Chart/AppointmentsChart.tsx b/src/Chart/UsersChart.tsx similarity index 92% rename from src/Chart/AppointmentsChart.tsx rename to src/Chart/UsersChart.tsx index 8713df2fb..ee3ab2aaf 100644 --- a/src/Chart/AppointmentsChart.tsx +++ b/src/Chart/UsersChart.tsx @@ -22,7 +22,7 @@ ChartJS.register( ); // eslint-disable-next-line react/function-component-definition -const AppointmentsChart: React.FC = () => { +const usersChart: React.FC = () => { const data = { labels: [ '01', @@ -59,7 +59,7 @@ const AppointmentsChart: React.FC = () => { ], datasets: [ { - label: 'Last days', + label: 'Andela', data: [ 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, 3, 8, 0, 3, 5, 7, @@ -69,7 +69,7 @@ const AppointmentsChart: React.FC = () => { tension: 0.4, }, { - label: 'Last days', + label: 'NESA', data: [ 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, 1, 2, 3, 4, 5, 6.5, @@ -110,4 +110,4 @@ const AppointmentsChart: React.FC = () => { ); }; -export default AppointmentsChart; +export default usersChart; diff --git a/src/components/AdminDashboardTable.tsx b/src/components/AdminDashboardTable.tsx new file mode 100644 index 000000000..53b0aa8f2 --- /dev/null +++ b/src/components/AdminDashboardTable.tsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import { FaEye } from 'react-icons/fa'; +import TeamDetailsModal from './AdminTeamDetails'; + +interface TeamData { + ttlName?: string; + team?: string; + organization?: string; + program?: string; + phase?: string; + cohort?: string; + activeUsers?: number; + droppedUsers?: number; + rating?: number; + logins: string; + users: number; +} + +const DashboardTableDesign: React.FC = () => { + const [selectedTeam, setSelectedTeam] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + const dummyData: TeamData[] = [ + { + team: 'Nova', + logins: '2.4k', + users: 45, + ttlName: 'Sostene', + organization: 'Tech Corp', + program: 'Web Development', + phase: 'Phase 2', + cohort: 'Cohort 3', + activeUsers: 42, + droppedUsers: 3, + rating: 4.8 + }, + { + team: 'Fighters', + logins: '1.8k', + users: 32, + ttlName: 'John Doe', + organization: 'Tech Corp', + program: 'Mobile Development', + phase: 'Phase 1', + cohort: 'Cohort 2', + activeUsers: 30, + droppedUsers: 2, + rating: 4.5 + }, + { + team: 'Bitcrafters', + logins: '1.2k', + users: 28, + ttlName: 'Jane Smith', + organization: 'Tech Corp', + program: 'Data Science', + phase: 'Phase 3', + cohort: 'Cohort 1', + activeUsers: 25, + droppedUsers: 3, + rating: 4.6 + }, + { + team: 'Team1', + logins: '3.1k', + users: 52, + ttlName: 'Alice Johnson', + organization: 'Tech Corp', + program: 'Cloud Computing', + phase: 'Phase 2', + cohort: 'Cohort 4', + activeUsers: 48, + droppedUsers: 4, + rating: 4.7 + }, + { + team: 'Team2', + logins: '0.9k', + users: 19, + ttlName: 'Bob Wilson', + organization: 'Tech Corp', + program: 'DevOps', + phase: 'Phase 1', + cohort: 'Cohort 2', + activeUsers: 17, + droppedUsers: 2, + rating: 4.4 + } + ]; + + const handleViewClick = (team: TeamData) => { + setSelectedTeam(team); + setIsModalOpen(true); + }; + + return ( +
+
+

Team Analytics

+
+ +
+
+ +
+
+ + + + + + + + + + + {dummyData.map((row, index) => ( + + + + + + + ))} + +
+
+ Team Name +
+
+
+ Logins +
+
+
+ Users +
+
+
+ Actions +
+
+
+
+ {row.team} +
+
+
{row.logins}
+
+
+ {row.users} +
+
+ +
+
+ +
+
+ Showing 5 of 12 teams +
+
+ + +
+
+
+ + setIsModalOpen(false)} + teamData={selectedTeam} + /> +
+ ); +}; + +export default DashboardTableDesign; \ No newline at end of file diff --git a/src/components/AdminTeamDetails.tsx b/src/components/AdminTeamDetails.tsx new file mode 100644 index 000000000..dfe5588c1 --- /dev/null +++ b/src/components/AdminTeamDetails.tsx @@ -0,0 +1,156 @@ +import React, { useState } from 'react'; +import { FaAngleDown } from "react-icons/fa6"; + +interface TeamData { + ttlName?: string; + team?: string; + organization?: string; + program?: string; + phase?: string; + cohort?: string; + activeUsers?: number; + droppedUsers?: number; + rating?: number; +} + +interface TeamDetailsModalProps { + isOpen: boolean; + onClose: () => void; + teamData: TeamData | null; +} + +const TeamDetailsModal: React.FC = ({ isOpen, onClose, teamData }) => { + if (!isOpen) return null; + + const [showAttendanceSummary, setShowAttendanceSummary] = useState(false); + + const handleAttendanceSummaryEnter = () => { + setShowAttendanceSummary(true); + }; + + const handleAttendanceSummaryLeave = () => { + setShowAttendanceSummary(false); + }; + + return ( +
+
+
+
+

+ Overview +

+

+ Logins +

+
+ +
+ +
+
+
+ +

{teamData?.ttlName || 'Sostene'}

+
+ +
+ +

{teamData?.team}

+
+ +
+ +

{teamData?.organization || 'Organization Name'}

+
+ +
+ +

{teamData?.program || 'Program Name'}

+
+ +
+ +

{teamData?.phase || 'Current Phase'}

+
+ +
+ +

{teamData?.cohort || 'Current Cohort'}

+
+
+ +
+
+ +
+
+

Active Members

+

+ {teamData?.activeUsers || '0'} +

+
+
+

Dropped Members

+

+ {teamData?.droppedUsers || '0'} +

+
+
+
+ +
+ + {showAttendanceSummary && ( +
+

Quality: 1.5

+

Quantity: 2.3

+

Professionalism: 3.1

+
+ )} +
+ +
+ +
+

+ {teamData?.rating || '4.5'} / 5.0 +

+
+
+
+
+
+
+ ); +}; + +export default TeamDetailsModal; \ No newline at end of file diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 9b8c23926..216ac3c19 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,16 +3,19 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import PieChart from '../Chart/PieChart'; import BarChart from '../Chart/BarChart'; -import AppointmentsChart from '../Chart/AppointmentsChart'; +import UsersChart from '../Chart/usersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; +import { FaEye } from 'react-icons/fa'; +import DashboardTableDesign from '../components/AdminDashboardTable'; -function SupAdDashboard() { +function AdminDashboard() { const { user } = useContext(UserContext); const { t }: any = useTranslation(); @@ -25,7 +28,6 @@ function SupAdDashboard() { const inviteModel = () => { const newState = !inviteTraineeModel; setInviteTraineeModel(newState); - // this is true }; const [inviteUser] = useMutation(INVITE_USER_MUTATION, { @@ -125,11 +127,14 @@ function SupAdDashboard() {
+
+ +
Users
- +
@@ -140,8 +145,15 @@ function SupAdDashboard() {
+
+ +
); } -export default SupAdDashboard; +export default AdminDashboard; + + + + From 6997c811af80a615d801e00390dac53f4d82932b Mon Sep 17 00:00:00 2001 From: Tuyisenge2 Date: Thu, 14 Nov 2024 16:19:39 +0200 Subject: [PATCH 5/9] ft-admin-dashboard-can-vieww-table-teams --- src/components/AdminDashboardTable.tsx | 147 +++++++++---------------- src/components/CoordinatorCard.tsx | 25 +++-- src/pages/AdminDashboard.tsx | 6 +- 3 files changed, 72 insertions(+), 106 deletions(-) diff --git a/src/components/AdminDashboardTable.tsx b/src/components/AdminDashboardTable.tsx index e04847f70..33644a6d4 100644 --- a/src/components/AdminDashboardTable.tsx +++ b/src/components/AdminDashboardTable.tsx @@ -1,103 +1,62 @@ +import { useQuery } from '@apollo/client'; import React from 'react'; import { FaEye } from 'react-icons/fa'; +import { useTranslation } from 'react-i18next'; +import DataTable from './DataTable'; +import { GET_TEAMS_CARDS } from './CoordinatorCard'; -const DashboardTableDesign = () => { - const dummyData = [ - { team: 'Nova', logins: '2.4k', users: 45 }, - { team: 'Fighters', logins: '1.8k', users: 32 }, - { team: 'Bitcrafters', logins: '1.2k', users: 28}, - { team: 'Team1', logins: '3.1k', users: 52 }, - { team: 'Team2', logins: '0.9k', users: 19 }, - ]; +function DashboardTableDesign() { + const { t } = useTranslation(); + const { + data: TeamsData, + loading, + error, + refetch, + } = useQuery(GET_TEAMS_CARDS, { + variables: { + orgToken: localStorage.getItem('orgToken'), + }, + fetchPolicy: 'network-only', + }); + const TableData = TeamsData?.getAllTeams.map((items: any) => ({ + teams: items.name, + users: items.members.length, + logins: items.members.reduce( + (total: number, i: any) => total + i.profile.activity.length, + 0, + ), + })); + const organizationColumns = [ + { Header: t('Teams'), accessor: 'teams' }, + { Header: t('Logins'), accessor: 'logins' }, + { Header: t('Users'), accessor: 'users' }, + { + Header: t('action'), + accessor: '', + Cell: () => ( + <> + + + ), + }, + ]; return (
-
-

Team Analytics

-
- -
-
- -
-
- - - - - - - - - - - {dummyData.map((row, index) => ( - - - - - - - ))} - -
-
- Team Name -
-
-
- Logins -
-
-
- Users -
-
-
- Actions -
-
-
-
- {row.team} -
-
-
{row.logins}
-
-
- {row.users} -
-
- -
-
- -
-
- Showing 5 of 12 teams -
-
- - -
-
-
+
); -}; +} -export default DashboardTableDesign; \ No newline at end of file +export default DashboardTableDesign; diff --git a/src/components/CoordinatorCard.tsx b/src/components/CoordinatorCard.tsx index 4b856735e..19258afc3 100644 --- a/src/components/CoordinatorCard.tsx +++ b/src/components/CoordinatorCard.tsx @@ -31,8 +31,19 @@ export const GET_TEAMS_CARDS = gql` name lastName firstName + address + activity { + date + city + IPv4 + state + latitude + longitude + postal + failed + } } - status{ + status { status } } @@ -147,12 +158,12 @@ function ManagerCard() { rating = 'text-red-700'; } - const activeMembers = team.members.filter( - (member: any) => member.status?.status === 'active' - ).length; - const droppedMembers = team.members.filter( - (member: any) => member.status?.status === 'drop' - ).length; + const activeMembers = team.members.filter( + (member: any) => member.status?.status === 'active', + ).length; + const droppedMembers = team.members.filter( + (member: any) => member.status?.status === 'drop', + ).length; return { stylebg, diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 216ac3c19..1323d7a98 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,6 +3,7 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import { FaEye } from 'react-icons/fa'; import PieChart from '../Chart/PieChart'; import BarChart from '../Chart/BarChart'; import UsersChart from '../Chart/usersChart'; @@ -12,7 +13,6 @@ import Comingsoon from './Comingsoon'; import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; -import { FaEye } from 'react-icons/fa'; import DashboardTableDesign from '../components/AdminDashboardTable'; function AdminDashboard() { @@ -153,7 +153,3 @@ function AdminDashboard() { } export default AdminDashboard; - - - - From 3786c0f13d28b167d62e5065149917251298aa6d Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Thu, 14 Nov 2024 09:10:59 +0200 Subject: [PATCH 6/9] new piechart and stats updated --- src/Chart/BarChart.tsx | 6 +- src/Chart/PieChart.tsx | 96 +++++++++ .../{AppointmentsChart.tsx => UsersChart.tsx} | 8 +- src/components/AdminDashboardTable.tsx | 195 ++++++++++++++++++ src/components/AdminTeamDetails.tsx | 156 ++++++++++++++ src/pages/AdminDashboard.tsx | 22 +- 6 files changed, 471 insertions(+), 12 deletions(-) create mode 100644 src/Chart/PieChart.tsx rename src/Chart/{AppointmentsChart.tsx => UsersChart.tsx} (92%) create mode 100644 src/components/AdminDashboardTable.tsx create mode 100644 src/components/AdminTeamDetails.tsx diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx index b7a81e611..895a544a0 100644 --- a/src/Chart/BarChart.tsx +++ b/src/Chart/BarChart.tsx @@ -40,14 +40,14 @@ const BarChart: React.FC = () => { ], datasets: [ { - label: 'Last 8 days', + label: 'Nova', data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], backgroundColor: '#5A6ACF', borderRadius: 0, barThickness: 8, }, { - label: 'Last Week', + label: 'Fighters', data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], backgroundColor: '#D1D5DB', borderRadius: 0, @@ -63,7 +63,7 @@ const BarChart: React.FC = () => { legend: { position: 'bottom' as const, labels: { - color: '#121212', + color: '#D1D5DB', }, }, tooltip: { diff --git a/src/Chart/PieChart.tsx b/src/Chart/PieChart.tsx new file mode 100644 index 000000000..2ad039f2c --- /dev/null +++ b/src/Chart/PieChart.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +// eslint-disable-next-line react/function-component-definition +const PieChart: React.FC = () => { + const data = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 100], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data2 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [30, 70], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + const data3 = { + labels: ['new pie chart'], + datasets: [ + { + label: 'rates', + data: [60, 60], + backgroundColor: ['#4F46E5', '#A5B4FC'], + hoverOffset: 4, + }, + ], + }; + + const options = { + responsive: true, + cutout: '70%', + plugins: { + tooltip: { + callbacks: { + // eslint-disable-next-line func-names, object-shorthand + label: function (tooltipItem: any) { + return `${tooltipItem.label}: ${tooltipItem.raw}%`; + }, + }, + }, + legend: { + display: false, + }, + }, + }; + + return ( +
+
+
+ +
+
+

10

+
+
+

New Invitations & Registration

+
+
+ +
+
+

20

+
+
+

Upcoming Events

+
+
+ +
+
+

50

+
+
+

Active& Progressive Tickets

+
+
+
+ ); +}; + +export default PieChart; diff --git a/src/Chart/AppointmentsChart.tsx b/src/Chart/UsersChart.tsx similarity index 92% rename from src/Chart/AppointmentsChart.tsx rename to src/Chart/UsersChart.tsx index 8713df2fb..ee3ab2aaf 100644 --- a/src/Chart/AppointmentsChart.tsx +++ b/src/Chart/UsersChart.tsx @@ -22,7 +22,7 @@ ChartJS.register( ); // eslint-disable-next-line react/function-component-definition -const AppointmentsChart: React.FC = () => { +const usersChart: React.FC = () => { const data = { labels: [ '01', @@ -59,7 +59,7 @@ const AppointmentsChart: React.FC = () => { ], datasets: [ { - label: 'Last days', + label: 'Andela', data: [ 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, 3, 8, 0, 3, 5, 7, @@ -69,7 +69,7 @@ const AppointmentsChart: React.FC = () => { tension: 0.4, }, { - label: 'Last days', + label: 'NESA', data: [ 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, 1, 2, 3, 4, 5, 6.5, @@ -110,4 +110,4 @@ const AppointmentsChart: React.FC = () => { ); }; -export default AppointmentsChart; +export default usersChart; diff --git a/src/components/AdminDashboardTable.tsx b/src/components/AdminDashboardTable.tsx new file mode 100644 index 000000000..df3f4e41f --- /dev/null +++ b/src/components/AdminDashboardTable.tsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import { FaEye } from 'react-icons/fa'; +import TeamDetailsModal from './AdminTeamDetails'; + +interface TeamData { + ttlName?: string; + team?: string; + organization?: string; + program?: string; + phase?: string; + cohort?: string; + activeUsers?: number; + droppedUsers?: number; + rating?: number; + logins: string; + users: number; +} + +const DashboardTableDesign: React.FC = () => { + const [selectedTeam, setSelectedTeam] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + const dummyData: TeamData[] = [ + { + team: 'Nova', + logins: '2.4k', + users: 45, + ttlName: 'Sostene', + organization: 'Tech Corp', + program: 'Atlp1', + phase: 'Phase 2', + cohort: 'Cohort 3', + activeUsers: 42, + droppedUsers: 3, + rating: 4.8 + }, + { + team: 'Fighters', + logins: '1.8k', + users: 32, + ttlName: 'Sostene', + organization: 'Andela', + program: 'Atlp1', + phase: 'Phase 1', + cohort: 'Cohort 2', + activeUsers: 30, + droppedUsers: 2, + rating: 4.5 + }, + { + team: 'Bitcrafters', + logins: '1.2k', + users: 28, + ttlName: 'Jacqueline', + organization: 'Irembo', + program: 'Data Science', + phase: 'Phase 3', + cohort: 'Cohort 1', + activeUsers: 25, + droppedUsers: 3, + rating: 4.6 + }, + { + team: 'Team1', + logins: '3.1k', + users: 52, + ttlName: 'Alice', + organization: 'Tech Corp', + program: 'Cloud Computing', + phase: 'Phase 2', + cohort: 'Cohort 4', + activeUsers: 48, + droppedUsers: 4, + rating: 4.7 + }, + { + team: 'Team2', + logins: '0.9k', + users: 19, + ttlName: 'Bob', + organization: 'Tech', + program: 'DevOps', + phase: 'Phase 1', + cohort: 'Cohort 2', + activeUsers: 17, + droppedUsers: 2, + rating: 4.4 + } + ]; + + const handleViewClick = (team: TeamData) => { + setSelectedTeam(team); + setIsModalOpen(true); + }; + + return ( +
+
+

Team Analytics

+
+ +
+
+ +
+
+ + + + + + + + + + + {dummyData.map((row, index) => ( + + + + + + + ))} + +
+
+ Team Name +
+
+
+ Logins +
+
+
+ Users +
+
+
+ Actions +
+
+
+
+ {row.team} +
+
+
{row.logins}
+
+
+ {row.users} +
+
+ +
+
+ +
+
+ Showing 5 of 12 teams +
+
+ + +
+
+
+ + setIsModalOpen(false)} + teamData={selectedTeam} + /> +
+ ); +}; + +export default DashboardTableDesign; \ No newline at end of file diff --git a/src/components/AdminTeamDetails.tsx b/src/components/AdminTeamDetails.tsx new file mode 100644 index 000000000..dfe5588c1 --- /dev/null +++ b/src/components/AdminTeamDetails.tsx @@ -0,0 +1,156 @@ +import React, { useState } from 'react'; +import { FaAngleDown } from "react-icons/fa6"; + +interface TeamData { + ttlName?: string; + team?: string; + organization?: string; + program?: string; + phase?: string; + cohort?: string; + activeUsers?: number; + droppedUsers?: number; + rating?: number; +} + +interface TeamDetailsModalProps { + isOpen: boolean; + onClose: () => void; + teamData: TeamData | null; +} + +const TeamDetailsModal: React.FC = ({ isOpen, onClose, teamData }) => { + if (!isOpen) return null; + + const [showAttendanceSummary, setShowAttendanceSummary] = useState(false); + + const handleAttendanceSummaryEnter = () => { + setShowAttendanceSummary(true); + }; + + const handleAttendanceSummaryLeave = () => { + setShowAttendanceSummary(false); + }; + + return ( +
+
+
+
+

+ Overview +

+

+ Logins +

+
+ +
+ +
+
+
+ +

{teamData?.ttlName || 'Sostene'}

+
+ +
+ +

{teamData?.team}

+
+ +
+ +

{teamData?.organization || 'Organization Name'}

+
+ +
+ +

{teamData?.program || 'Program Name'}

+
+ +
+ +

{teamData?.phase || 'Current Phase'}

+
+ +
+ +

{teamData?.cohort || 'Current Cohort'}

+
+
+ +
+
+ +
+
+

Active Members

+

+ {teamData?.activeUsers || '0'} +

+
+
+

Dropped Members

+

+ {teamData?.droppedUsers || '0'} +

+
+
+
+ +
+ + {showAttendanceSummary && ( +
+

Quality: 1.5

+

Quantity: 2.3

+

Professionalism: 3.1

+
+ )} +
+ +
+ +
+

+ {teamData?.rating || '4.5'} / 5.0 +

+
+
+
+
+
+
+ ); +}; + +export default TeamDetailsModal; \ No newline at end of file diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 9b8c23926..216ac3c19 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -3,16 +3,19 @@ import { t } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useMutation } from '@apollo/client'; import { toast } from 'react-toastify'; +import PieChart from '../Chart/PieChart'; import BarChart from '../Chart/BarChart'; -import AppointmentsChart from '../Chart/AppointmentsChart'; +import UsersChart from '../Chart/usersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; +import { FaEye } from 'react-icons/fa'; +import DashboardTableDesign from '../components/AdminDashboardTable'; -function SupAdDashboard() { +function AdminDashboard() { const { user } = useContext(UserContext); const { t }: any = useTranslation(); @@ -25,7 +28,6 @@ function SupAdDashboard() { const inviteModel = () => { const newState = !inviteTraineeModel; setInviteTraineeModel(newState); - // this is true }; const [inviteUser] = useMutation(INVITE_USER_MUTATION, { @@ -125,11 +127,14 @@ function SupAdDashboard() {
+
+ +
Users
- +
@@ -140,8 +145,15 @@ function SupAdDashboard() {
+
+ +
); } -export default SupAdDashboard; +export default AdminDashboard; + + + + From 548376c8c4f28b4b8ba72f641f72c7219043e177 Mon Sep 17 00:00:00 2001 From: RWEMAREMY Date: Wed, 20 Nov 2024 08:18:51 +0200 Subject: [PATCH 7/9] Bar Chart changes according to teams workrate --- src/Chart/BarChart.tsx | 126 +++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/src/Chart/BarChart.tsx b/src/Chart/BarChart.tsx index 895a544a0..be12deb2d 100644 --- a/src/Chart/BarChart.tsx +++ b/src/Chart/BarChart.tsx @@ -9,6 +9,9 @@ import { Tooltip, Legend, } from 'chart.js'; +import { useQuery } from '@apollo/client'; +import { GET_ALL_TEAMS } from '../queries/team.queries'; +import { FETCH_ALL_RATINGS } from '../queries/ratings.queries'; ChartJS.register( CategoryScale, @@ -23,77 +26,80 @@ interface Props {} // eslint-disable-next-line react/function-component-definition const BarChart: React.FC = () => { - const data = { - labels: [ - '01', - '02', - '03', - '04', - '05', - '06', - '07', - '08', - '09', - '10', - '11', - '12', - ], + const orgToken = localStorage.getItem('orgToken'); + const { data, loading, error } = useQuery(GET_ALL_TEAMS, { + variables: { + orgToken, + }, + fetchPolicy: 'network-only', + }); + + const { + data: ratingsData, + loading: ratingsLoading, + error: ratingsError, + } = useQuery(FETCH_ALL_RATINGS, { + variables: { + orgToken, + }, + fetchPolicy: 'network-only', + }); + + if (loading) return

Loading...

; + if (error) return

Error: {error.message}

; + + if (ratingsLoading) return

Loading ratings...

; + if (ratingsError) return

Error loading ratings: {ratingsError.message}

; + + const teamNames = data?.getAllTeams?.map( + (team: { name: string }) => team.name, + ); + const ratingsArray = ratingsData?.fetchAllRatings || []; + + const professionalismData = ratingsArray.map( + (rating: { professional_Skills: string }) => + parseFloat(rating.professional_Skills), + ); + const qualityData = ratingsArray.map((rating: { quality: string }) => + parseFloat(rating.quality), + ); + const quantityData = ratingsArray.map((rating: { quantity: string }) => + parseFloat(rating.quantity), + ); + if (!teamNames || teamNames.length === 0) { + return

No team data available.

; + } + + const datas = { + labels: teamNames, datasets: [ { - label: 'Nova', - data: [12, 19, 3, 5, 2, 3, 12, 14, 5, 7, 9, 11], + label: 'Professionalism', + data: professionalismData, backgroundColor: '#5A6ACF', - borderRadius: 0, - barThickness: 8, + borderRadius: 20, + barThickness: 14, }, { - label: 'Fighters', - data: [10, 15, 5, 8, 6, 9, 13, 9, 6, 8, 7, 10], - backgroundColor: '#D1D5DB', - borderRadius: 0, - barThickness: 8, - }, - ], - }; - - const options = { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'bottom' as const, - labels: { - color: '#D1D5DB', - }, - }, - tooltip: { - enabled: true, + label: 'Quality', + data: qualityData, + backgroundColor: '#fcffa4', + borderRadius: 20, + barThickness: 14, }, - }, - scales: { - x: { - grid: { - display: false, - }, - ticks: { - color: '#737B8B', - }, - }, - y: { - grid: { - borderDash: [5, 5], - color: '#ffffff', - }, - ticks: { - color: '#ffffff', - }, + { + label: 'Quantity', + data: quantityData, + backgroundColor: '#9f5233', + borderRadius: 20, + barThickness: 14, }, - }, + ], }; return (
- +
); }; From 559a8bb674676fcfaa75c89d2f76f6062b7482b1 Mon Sep 17 00:00:00 2001 From: shebz2023 Date: Wed, 20 Nov 2024 10:50:36 +0200 Subject: [PATCH 8/9] user Growth overtime chart --- src/Chart/LineChart.tsx | 267 ++++++++++++++++++++++++++++++ src/Chart/UsersChart.tsx | 113 ------------- src/components/DashboardCards.tsx | 11 +- src/pages/AdminDashboard.tsx | 22 +-- 4 files changed, 282 insertions(+), 131 deletions(-) create mode 100644 src/Chart/LineChart.tsx delete mode 100644 src/Chart/UsersChart.tsx diff --git a/src/Chart/LineChart.tsx b/src/Chart/LineChart.tsx new file mode 100644 index 000000000..4be184702 --- /dev/null +++ b/src/Chart/LineChart.tsx @@ -0,0 +1,267 @@ +import React, { useEffect, useState } from 'react'; +import { + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { useLazyQuery, gql } from '@apollo/client'; +import { UserInterface } from '../pages/TraineeAttendanceTracker'; +import { Organization } from '../components/Organizations'; + +export function UserChart() { + const GET_ALL_ORG_USERS = gql` + query GetAllOrgUsers { + getAllOrgUsers { + totalUsers + organizations { + organization { + id + name + description + admin { + id + email + profile { + name + phoneNumber + } + } + status + } + members { + email + profile { + name + } + } + monthPercentage + loginsCount + recentLocation + } + } + } + `; + + const GET_REGISTRATION_STATS = gql` + query GetRegistrationStats { + getRegistrationStats { + year + stats { + month + users + organizations + } + } + } + `; + + interface AllOrgUsersInterface { + totalUsers: number; + organizations: { + organization: Organization; + members: UserInterface[]; + loginsCount: number; + monthPercentage: number; + recentLocation: string | null; + }[]; + } + + interface RegistrationDataStatsInterface { + month: + | 'jan' + | 'feb' + | 'mar' + | 'apr' + | 'may' + | 'jun' + | 'jul' + | 'aug' + | 'sep' + | 'oct' + | 'nov' + | 'dec' + | null; + users: number | null; + organizations: number | null; + } + + interface RegistrationDataInterface { + year: number; + stats: RegistrationDataStatsInterface[]; + } + + const [selectedRegistrationData, setSelectedRegistrationData] = + useState(); + const [allOrgsUsers, setAllOrgsUsers] = useState({ + totalUsers: 0, + organizations: [], + }); + const [registrationData, setRegistrationData] = + useState(); + const [selectedYear, setSelectedYear] = useState(); + const [registrationYears, setRegistrationYears] = useState(); + + const [getAllOrgUsers, { loading: getAllOrgUsersLoading }] = + useLazyQuery(GET_ALL_ORG_USERS); + const [getRegistrationStats, { loading: getRegistrationStatsLoading }] = + useLazyQuery(GET_REGISTRATION_STATS); + + useEffect(() => { + getAllOrgUsers({ + fetchPolicy: 'network-only', + onCompleted: (data) => { + setAllOrgsUsers(data.getAllOrgUsers); + }, + }); + + getRegistrationStats({ + fetchPolicy: 'network-only', + onCompleted: (data) => { + setRegistrationData(data.getRegistrationStats); + }, + }); + }, [getAllOrgUsers, getRegistrationStats]); + + useEffect(() => { + const years = [new Date().getFullYear()]; + if (registrationData) { + years.push(...registrationData.map((data) => data.year)); + const sanitizedYears = [...new Set(years)].sort((a, b) => b - a); + setRegistrationYears(sanitizedYears); + setSelectedYear(sanitizedYears[0]); + return; + } + + const sanitizedYears = [...new Set(years)].sort((a, b) => b - a); + setRegistrationYears(sanitizedYears); + }, [registrationData]); + + useEffect(() => { + const months = [ + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec', + ]; + let data: RegistrationDataStatsInterface[] = [ + { + month: null, + users: 0, + organizations: 0, + }, + ...months.map((month) => ({ + month: month as RegistrationDataStatsInterface['month'], + users: null, + organizations: null, + })), + ]; + if (registrationData) { + const tempData = registrationData.find( + (data) => data.year === selectedYear, + ); + if (tempData && tempData.stats.length) data = tempData.stats; + } + setSelectedRegistrationData(data); + }, [selectedYear, registrationData]); + + if (getAllOrgUsersLoading || getRegistrationStatsLoading) { + return
Loading...
; + } + + return ( +
+
+

User growth Over Time

+
+ +
+
+ 700 ? 350 : window.innerWidth > 500 ? 300 : 250 + } + > + + + + + + + + + + +
+ {registrationYears?.map((year) => ( + + ))} +
+
+ ); +} diff --git a/src/Chart/UsersChart.tsx b/src/Chart/UsersChart.tsx deleted file mode 100644 index dfe0bbf0d..000000000 --- a/src/Chart/UsersChart.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; -import { Line } from 'react-chartjs-2'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, -} from 'chart.js'; - -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, -); - -function UsersChart() { - const data = { - labels: [ - '01', - '02', - '03', - '04', - '05', - '06', - '07', - '08', - '09', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - '19', - '20', - '21', - '22', - '23', - '24', - '25', - '26', - '27', - '28', - '29', - '30', - '31', - ], - datasets: [ - { - label: 'Andela', - data: [ - 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, - 3, 8, 0, 3, 5, 7, - ], - fill: false, - borderColor: '#4F46E5', - tension: 0.4, - }, - { - label: 'NESA', - data: [ - 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, - 1, 2, 3, 4, 5, 6.5, - ], - fill: false, - borderColor: '#8C8120', - tension: 0.4, - }, - ], - }; - - const options = { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'bottom' as const, - }, - }, - scales: { - y: { - beginAtZero: true, - grid: { - color: '#D1D5DB', - }, - }, - x: { - grid: { - display: false, - }, - }, - }, - }; - - return ( -
- -
- ); -} - -export default UsersChart; diff --git a/src/components/DashboardCards.tsx b/src/components/DashboardCards.tsx index 45dbd979c..3c4a51d97 100644 --- a/src/components/DashboardCards.tsx +++ b/src/components/DashboardCards.tsx @@ -65,18 +65,21 @@ function DashboardCards() { }); const { loading: getInvitationsDataLoading } = useQuery(GET_ALL_INVITATIONS, { + variables: { + orgToken: localStorage.getItem('orgToken'), + }, onCompleted: (data) => { - const invitations = data.getAllInvitations || []; + const invitations = data.getAllInvitations?.invitations || []; setInvitationData(invitations); // Count active and closed tickets const acceptedInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'accepted', + (invitees: { status: string }) => invitees.status === 'accepted', ).length; const pendingInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'pending', + (invitees: { status: string }) => invitees.status === 'pending', ).length; const declinedInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'cancelled', + (invitees: { status: string }) => invitees.status === 'cancelled', ).length; setAcceptedTicketsCount(acceptedInvitationsCount); setPendingTicketsCount(pendingInvitationsCount); diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 5e544c978..a65f1fed4 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -8,7 +8,6 @@ import PieChart from '../Chart/PieChart'; // import PieChart from '../Chart/PieChart'; import DashboardCards from '../components/DashboardCards'; import BarChart from '../Chart/BarChart'; -import UsersChart from '../Chart/UsersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; @@ -16,6 +15,7 @@ import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; import DashboardTableDesign from '../components/AdminDashboardTable'; +import { UserChart } from '../Chart/LineChart'; function AdminDashboard() { const { user } = useContext(UserContext); @@ -131,20 +131,14 @@ function AdminDashboard() {
-
-
-
- Users -
- -
- -
-
- Teams -
- +
+ +
+
+
+ Teams
+
From fdc13ed0fc2e80c2292325ffb453cbf523895be3 Mon Sep 17 00:00:00 2001 From: shebz2023 Date: Wed, 20 Nov 2024 10:50:36 +0200 Subject: [PATCH 9/9] user Growth overtime chart --- src/Chart/LineChart.tsx | 267 +++++++++++++++++++++++++ src/Chart/TeamChart.tsx | 105 +++++++++- src/Chart/UsersChart.tsx | 113 ----------- src/components/AdminDashboardTable.tsx | 3 +- src/components/AdminTeamDetails.tsx | 126 ++++++++++-- src/components/DashboardCards.tsx | 11 +- src/pages/AdminDashboard.tsx | 22 +- 7 files changed, 487 insertions(+), 160 deletions(-) create mode 100644 src/Chart/LineChart.tsx delete mode 100644 src/Chart/UsersChart.tsx diff --git a/src/Chart/LineChart.tsx b/src/Chart/LineChart.tsx new file mode 100644 index 000000000..4be184702 --- /dev/null +++ b/src/Chart/LineChart.tsx @@ -0,0 +1,267 @@ +import React, { useEffect, useState } from 'react'; +import { + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { useLazyQuery, gql } from '@apollo/client'; +import { UserInterface } from '../pages/TraineeAttendanceTracker'; +import { Organization } from '../components/Organizations'; + +export function UserChart() { + const GET_ALL_ORG_USERS = gql` + query GetAllOrgUsers { + getAllOrgUsers { + totalUsers + organizations { + organization { + id + name + description + admin { + id + email + profile { + name + phoneNumber + } + } + status + } + members { + email + profile { + name + } + } + monthPercentage + loginsCount + recentLocation + } + } + } + `; + + const GET_REGISTRATION_STATS = gql` + query GetRegistrationStats { + getRegistrationStats { + year + stats { + month + users + organizations + } + } + } + `; + + interface AllOrgUsersInterface { + totalUsers: number; + organizations: { + organization: Organization; + members: UserInterface[]; + loginsCount: number; + monthPercentage: number; + recentLocation: string | null; + }[]; + } + + interface RegistrationDataStatsInterface { + month: + | 'jan' + | 'feb' + | 'mar' + | 'apr' + | 'may' + | 'jun' + | 'jul' + | 'aug' + | 'sep' + | 'oct' + | 'nov' + | 'dec' + | null; + users: number | null; + organizations: number | null; + } + + interface RegistrationDataInterface { + year: number; + stats: RegistrationDataStatsInterface[]; + } + + const [selectedRegistrationData, setSelectedRegistrationData] = + useState(); + const [allOrgsUsers, setAllOrgsUsers] = useState({ + totalUsers: 0, + organizations: [], + }); + const [registrationData, setRegistrationData] = + useState(); + const [selectedYear, setSelectedYear] = useState(); + const [registrationYears, setRegistrationYears] = useState(); + + const [getAllOrgUsers, { loading: getAllOrgUsersLoading }] = + useLazyQuery(GET_ALL_ORG_USERS); + const [getRegistrationStats, { loading: getRegistrationStatsLoading }] = + useLazyQuery(GET_REGISTRATION_STATS); + + useEffect(() => { + getAllOrgUsers({ + fetchPolicy: 'network-only', + onCompleted: (data) => { + setAllOrgsUsers(data.getAllOrgUsers); + }, + }); + + getRegistrationStats({ + fetchPolicy: 'network-only', + onCompleted: (data) => { + setRegistrationData(data.getRegistrationStats); + }, + }); + }, [getAllOrgUsers, getRegistrationStats]); + + useEffect(() => { + const years = [new Date().getFullYear()]; + if (registrationData) { + years.push(...registrationData.map((data) => data.year)); + const sanitizedYears = [...new Set(years)].sort((a, b) => b - a); + setRegistrationYears(sanitizedYears); + setSelectedYear(sanitizedYears[0]); + return; + } + + const sanitizedYears = [...new Set(years)].sort((a, b) => b - a); + setRegistrationYears(sanitizedYears); + }, [registrationData]); + + useEffect(() => { + const months = [ + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec', + ]; + let data: RegistrationDataStatsInterface[] = [ + { + month: null, + users: 0, + organizations: 0, + }, + ...months.map((month) => ({ + month: month as RegistrationDataStatsInterface['month'], + users: null, + organizations: null, + })), + ]; + if (registrationData) { + const tempData = registrationData.find( + (data) => data.year === selectedYear, + ); + if (tempData && tempData.stats.length) data = tempData.stats; + } + setSelectedRegistrationData(data); + }, [selectedYear, registrationData]); + + if (getAllOrgUsersLoading || getRegistrationStatsLoading) { + return
Loading...
; + } + + return ( +
+
+

User growth Over Time

+
+ +
+
+ 700 ? 350 : window.innerWidth > 500 ? 300 : 250 + } + > + + + + + + + + + + +
+ {registrationYears?.map((year) => ( + + ))} +
+
+ ); +} diff --git a/src/Chart/TeamChart.tsx b/src/Chart/TeamChart.tsx index f734fef92..56c400f99 100644 --- a/src/Chart/TeamChart.tsx +++ b/src/Chart/TeamChart.tsx @@ -1,3 +1,6 @@ +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-plusplus */ import React from 'react'; import { Line } from 'react-chartjs-2'; import { @@ -23,16 +26,102 @@ ChartJS.register( interface TeamChartProps { timeframe?: 'daily' | 'weekly' | 'monthly'; + CurrentTeam: any[]; + loginsbyDate: any[]; } -function TeamChart({ timeframe = 'daily' }: TeamChartProps) { +function TeamChart({ + timeframe = 'daily', + CurrentTeam, + loginsbyDate, +}: TeamChartProps) { + function organizeLoginData(loginData: any) { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + function getWeekNumber(date: any) { + const tempDate: any = new Date(date); + tempDate.setUTCDate( + tempDate.getUTCDate() + 4 - (tempDate.getUTCDay() || 7), + ); + const yearStart: any = new Date( + Date.UTC(tempDate.getUTCFullYear(), 0, 1), + ); + return Math.ceil(((tempDate - yearStart) / 86400000 + 1) / 7); + } + // Initialize result arrays + const weeklyData = Array(54) + .fill(0) + .map((_, i) => ({ week: i + 1, success: 0, failed: 0 })); + const monthlyData = Array(12) + .fill(0) + .map((_, i) => ({ month: i + 1, success: 0, failed: 0 })); + const dailyData = Array(7) + .fill(0) + .map((_, i) => ({ day: i, success: 0, failed: 0 })); + for (const [dateString, { success, failed }] of Object.entries( + loginData, + ) as any) { + const date = new Date(dateString); + const isoWeekNumber = getWeekNumber(date); + const month = date.getUTCMonth(); + const dayOfWeek = (date.getUTCDay() + 6) % 7; + const weekStart = new Date(currentDate); + weekStart.setUTCDate( + currentDate.getUTCDate() - currentDate.getUTCDay() + 1, + ); + const weekEnd = new Date(weekStart); + weekEnd.setUTCDate(weekStart.getUTCDate() + 6); + if (date >= weekStart && date <= weekEnd) { + dailyData[dayOfWeek].success += success; + dailyData[dayOfWeek].failed += failed; + } + // Weekly data + if (isoWeekNumber <= 54) { + weeklyData[isoWeekNumber - 1].success += success; + weeklyData[isoWeekNumber - 1].failed += failed; + } + // Monthly data + monthlyData[month].success += success; + monthlyData[month].failed += failed; + } + const weekDays = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ]; + const currentWeekData = dailyData.map((data, index) => ({ + day: weekDays[index], + success: data.success, + failed: data.failed, + })); + return { + currentWeek: currentWeekData, + weekly: weeklyData, + monthly: monthlyData.map((data, index) => ({ + month: new Date(0, index).toLocaleString('en', { month: 'long' }), + success: data.success, + failed: data.failed, + })), + }; + } + + const organizedData = organizeLoginData(loginsbyDate); + + const weeklyDataset = organizedData.weekly + .filter((_, index) => index % 3 === 0) + .map((item) => item.success); + const chartData = { daily: { labels: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], datasets: [ { - label: 'Andela', - data: [1, 3, 0, 2, 1, 3, 2], + label: CurrentTeam[0].name, + data: organizedData.currentWeek.map((item: any) => item.success), fill: false, borderColor: '#4F46E5', tension: 0.4, @@ -62,8 +151,8 @@ function TeamChart({ timeframe = 'daily' }: TeamChartProps) { ], datasets: [ { - label: 'Andela', - data: [1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4], + label: CurrentTeam[0].name, + data: weeklyDataset, fill: false, borderColor: '#4F46E5', tension: 0.4, @@ -71,13 +160,13 @@ function TeamChart({ timeframe = 'daily' }: TeamChartProps) { ], }, monthly: { - labels: Array.from({ length: 31 }, (_, i) => + labels: Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, '0'), ), datasets: [ { - label: 'Andela', - data: Array.from({ length: 31 }, () => Math.floor(Math.random() * 8)), + label: CurrentTeam[0].name, + data: organizedData.monthly.map((item: any) => item.success), fill: false, borderColor: '#4F46E5', tension: 0.4, diff --git a/src/Chart/UsersChart.tsx b/src/Chart/UsersChart.tsx deleted file mode 100644 index dfe0bbf0d..000000000 --- a/src/Chart/UsersChart.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; -import { Line } from 'react-chartjs-2'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, -} from 'chart.js'; - -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, -); - -function UsersChart() { - const data = { - labels: [ - '01', - '02', - '03', - '04', - '05', - '06', - '07', - '08', - '09', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - '19', - '20', - '21', - '22', - '23', - '24', - '25', - '26', - '27', - '28', - '29', - '30', - '31', - ], - datasets: [ - { - label: 'Andela', - data: [ - 1, 3, 0, 2, 1, 3, 2, 0, 2, 1, 3, 0, 2, 1, 4, 1, 2, 4, 7, 2, 3, 4, 4, - 3, 8, 0, 3, 5, 7, - ], - fill: false, - borderColor: '#4F46E5', - tension: 0.4, - }, - { - label: 'NESA', - data: [ - 2, 3, 6, 4, 3, 4, 2, 1, 2, 6, 2, 2, 3, 2, 3, 5, 7, 2, 1, 2, 4, 6, 6, - 1, 2, 3, 4, 5, 6.5, - ], - fill: false, - borderColor: '#8C8120', - tension: 0.4, - }, - ], - }; - - const options = { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: 'bottom' as const, - }, - }, - scales: { - y: { - beginAtZero: true, - grid: { - color: '#D1D5DB', - }, - }, - x: { - grid: { - display: false, - }, - }, - }, - }; - - return ( -
- -
- ); -} - -export default UsersChart; diff --git a/src/components/AdminDashboardTable.tsx b/src/components/AdminDashboardTable.tsx index bb5182d5d..af90dc24f 100644 --- a/src/components/AdminDashboardTable.tsx +++ b/src/components/AdminDashboardTable.tsx @@ -68,7 +68,8 @@ function DashboardTableDesign() { setIsModalOpen(false)} - teamData={selectedTeam} + selectedteam={selectedTeam} + Teams={TeamsData?.getAllTeams} />
); diff --git a/src/components/AdminTeamDetails.tsx b/src/components/AdminTeamDetails.tsx index 72656b18a..4b95d5b36 100644 --- a/src/components/AdminTeamDetails.tsx +++ b/src/components/AdminTeamDetails.tsx @@ -2,10 +2,11 @@ import React, { useState } from 'react'; import { FaAngleDown } from 'react-icons/fa6'; import TeamChart from '../Chart/TeamChart'; import ProgressBar from '../Chart/ProgressBar'; +import UsersChart from '../Chart/usersChart'; interface TeamData { ttlName?: string; - team?: string; + teams?: string; organization?: string; program?: string; phase?: string; @@ -18,7 +19,8 @@ interface TeamData { interface TeamDetailsModalProps { isOpen: boolean; onClose: () => void; - teamData: TeamData | null; + selectedteam: TeamData | null; + Teams?: any; } // Add this near the top of your TeamDetailsModal component @@ -43,7 +45,8 @@ const loginStats = { function TeamDetailsModal({ isOpen, onClose, - teamData, + selectedteam, + Teams, }: TeamDetailsModalProps) { const [activeTab, setActiveTab] = useState<'overview' | 'logins'>('overview'); const [timeframe, setTimeframe] = useState<'daily' | 'weekly' | 'monthly'>( @@ -56,6 +59,75 @@ function TeamDetailsModal({ if (!isOpen) return null; + const CurrentTeam = Teams?.filter( + (items: any) => items?.name === selectedteam?.teams, + ); + + const average = + (parseInt(CurrentTeam[0]?.avgRatings?.quality, 2) + + parseInt(CurrentTeam[0]?.avgRatings?.quantity, 2) + + parseInt(CurrentTeam[0]?.avgRatings?.professional_Skills, 2)) / + 3; + + const activeMembers = CurrentTeam[0]?.members.filter( + (item: any) => item.status.status !== 'suspended', + ); + const droppedMembers = CurrentTeam[0]?.members.filter( + (item: any) => item.status.status === 'suspended', + ); + function mapLoginsByDate(team: any) { + if (!team || !Array.isArray(team[0].members)) { + throw new Error('Invalid team object'); + } + const loginCounts: any = {}; + team[0].members.forEach((member: any) => { + const activities = member.profile?.activity; + + if (Array.isArray(activities)) { + activities.forEach((activity) => { + const rawDate = activity.date; + const timestamp = parseInt(rawDate, 10); + if (!Number.isNaN(timestamp)) { + const loginDate = new Date(timestamp).toISOString().split('T')[0]; + if (!loginCounts[loginDate]) { + loginCounts[loginDate] = { success: 0, failed: 0 }; + } + if (activity.failed === 1) { + loginCounts[loginDate].failed += 1; + } else { + loginCounts[loginDate].success += 1; + } + } + }); + } + }); + return loginCounts; + } + const loginsbyDate = mapLoginsByDate(CurrentTeam); + const orgName = localStorage.getItem('orgName'); + + function calculateLoginPercentages(data: any) { + let totalSuccess = 0; + let totalFailed = 0; + + // Sum up all successes and failures + Object.values(data).forEach(({ success, failed }: any) => { + totalSuccess += success; + totalFailed += failed; + }); + + // Calculate percentages + const total = totalSuccess + totalFailed; + const successPercentage = total > 0 ? (totalSuccess / total) * 100 : 0; + const failedPercentage = total > 0 ? (totalFailed / total) * 100 : 0; + + return { + successPercentage: successPercentage.toFixed(2), + failedPercentage: failedPercentage.toFixed(2), + totalLogins: total, + }; + } + return (
{[ - ['TTL Name', teamData?.ttlName || 'Sostene'], - ['Team Name', teamData?.team || 'Team Name'], + ['TTL Name', CurrentTeam[0]?.ttl?.profile?.name || 'Sostene'], + ['Team Name', selectedteam?.teams || 'Team Name'], + ['Organization', selectedteam?.organization || orgName], + [ + 'Program', + CurrentTeam[0]?.cohort?.program?.name || 'Program Name', + ], [ - 'Organization', - teamData?.organization || 'Organization Name', + 'Phase', + CurrentTeam[0]?.cohort?.phase?.name || 'Current Phase', ], - ['Program', teamData?.program || 'Program Name'], - ['Phase', teamData?.phase || 'Current Phase'], - ['Cohort', teamData?.cohort || 'Current Cohort'], + ['Cohort', CurrentTeam[0]?.cohort.name || 'Current Cohort'], ].map(([label, value], idx) => ( // eslint-disable-next-line react/no-array-index-key
@@ -132,7 +207,7 @@ function TeamDetailsModal({ Active Members

- {teamData?.activeUsers || '0'} + {activeMembers?.length || '0'}

@@ -140,7 +215,7 @@ function TeamDetailsModal({ Dropped Members

- {teamData?.droppedUsers || '0'} + {droppedMembers?.length || '0'}

@@ -162,13 +237,14 @@ function TeamDetailsModal({ {showAttendanceSummary && (

- Quality: 1.5 + Quality: {CurrentTeam[0]?.avgRatings?.quality || 0}

- Quantity: 2.3 + Quantity: {CurrentTeam[0]?.avgRatings?.quality || 0}

- Professionalism: 3.1 + Professionalism:{' '} + {CurrentTeam[0]?.avgRatings?.professional_Skills || 0}

)} @@ -180,7 +256,7 @@ function TeamDetailsModal({

- {teamData?.rating || '4.5'} / 5.0 + {average || '0'} / 5.0

@@ -231,20 +307,30 @@ function TeamDetailsModal({ Logins Attempt Status

Total Logins:{' '} {' '} - {loginStats[timeframe].total} + {calculateLoginPercentages(loginsbyDate).totalLogins}

- +
)}
diff --git a/src/components/DashboardCards.tsx b/src/components/DashboardCards.tsx index 45dbd979c..3c4a51d97 100644 --- a/src/components/DashboardCards.tsx +++ b/src/components/DashboardCards.tsx @@ -65,18 +65,21 @@ function DashboardCards() { }); const { loading: getInvitationsDataLoading } = useQuery(GET_ALL_INVITATIONS, { + variables: { + orgToken: localStorage.getItem('orgToken'), + }, onCompleted: (data) => { - const invitations = data.getAllInvitations || []; + const invitations = data.getAllInvitations?.invitations || []; setInvitationData(invitations); // Count active and closed tickets const acceptedInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'accepted', + (invitees: { status: string }) => invitees.status === 'accepted', ).length; const pendingInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'pending', + (invitees: { status: string }) => invitees.status === 'pending', ).length; const declinedInvitationsCount = invitations.filter( - (invite: { status: string }) => invite.status === 'cancelled', + (invitees: { status: string }) => invitees.status === 'cancelled', ).length; setAcceptedTicketsCount(acceptedInvitationsCount); setPendingTicketsCount(pendingInvitationsCount); diff --git a/src/pages/AdminDashboard.tsx b/src/pages/AdminDashboard.tsx index 5e544c978..a65f1fed4 100644 --- a/src/pages/AdminDashboard.tsx +++ b/src/pages/AdminDashboard.tsx @@ -8,7 +8,6 @@ import PieChart from '../Chart/PieChart'; // import PieChart from '../Chart/PieChart'; import DashboardCards from '../components/DashboardCards'; import BarChart from '../Chart/BarChart'; -import UsersChart from '../Chart/UsersChart'; // eslint-disable-next-line import/no-useless-path-segments import useDocumentTitle from '../hook/useDocumentTitle'; import Comingsoon from './Comingsoon'; @@ -16,6 +15,7 @@ import Button from '../components/Buttons'; import { UserContext } from '../hook/useAuth'; import { INVITE_USER_MUTATION } from '../Mutations/manageStudentMutations'; import DashboardTableDesign from '../components/AdminDashboardTable'; +import { UserChart } from '../Chart/LineChart'; function AdminDashboard() { const { user } = useContext(UserContext); @@ -131,20 +131,14 @@ function AdminDashboard() {
-
-
-
- Users -
- -
- -
-
- Teams -
- +
+ +
+
+
+ Teams
+