Skip to content

Commit 75e9f3f

Browse files
Tuyisenge2JacquelineTuyisenge
authored andcommitted
(fix): add coordinator dashboard page
1 parent 2e47213 commit 75e9f3f

File tree

6 files changed

+2020
-161
lines changed

6 files changed

+2020
-161
lines changed

src/components/DataTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function DataTable({ data, columns, title, loading, className }: TableData) {
6868

6969
return (
7070
<div
71-
className={`relative font-serif bg-indigo-100 dark:bg-dark-bg shadow-lg h-fit px-5 py-8 rounded-md w-full lg:w-auto mx-auto mb-10 ${className}`}
71+
className={`relative font-serif bg-indigo-100 dark:bg-dark-bg shadow-lg h-fit px-5 py-8 rounded-md w-[100%] overflow-scroll "lg:ml-60 mx-auto"} mb-10 ${className}`}
7272
>
7373
<div className="flex flex-col md:flex-row items-center justify-between pb-6 space-y-4 md:space-y-0">
7474
<div>

src/pages/CoordinatorDashboard.tsx

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/* eslint-disable jsx-a11y/no-redundant-roles */
2+
3+
import React, { useContext } from 'react';
4+
import { useTranslation } from 'react-i18next';
5+
// eslint-disable-next-line import/no-useless-path-segments
6+
// import { Link } from 'react-router-dom';
7+
import { useQuery } from '@apollo/client';
8+
// import { use } from 'i18next';
9+
import { UserContext } from '../hook/useAuth';
10+
// import AdminTraineeDashboard from './AdminTraineeDashboard';
11+
import CoordinatorTraineeDashboard from './coordinatorTraineDashboard';
12+
import { FETCH_ALL_RATINGS } from '../queries/ratings.queries';
13+
import InvitationCardSkeleton from '../Skeletons/InvitationCardSkeleton';
14+
15+
interface Trainee {
16+
id: string;
17+
total: number;
18+
count: number;
19+
email: string;
20+
profile: {
21+
firstName: string;
22+
lastName: string;
23+
id: string;
24+
profileImage: string;
25+
};
26+
[key: string]: any; // for additional properties from rating.user
27+
}
28+
29+
function CoordinatorDashboard() {
30+
const organizationToken = localStorage.getItem('orgToken');
31+
32+
const { user } = useContext(UserContext);
33+
const { t }: any = useTranslation();
34+
35+
const { loading, error, data } = useQuery(FETCH_ALL_RATINGS, {
36+
variables: { orgToken: organizationToken },
37+
skip: !organizationToken,
38+
});
39+
40+
if (loading) {
41+
return (
42+
<div className="flex flex-col items-center w-[80%] mx-auto mdl:w-[100%] mdl:justify-between gap-5 xmd:flex-row xmd:flex-wrap bg-light-bg dark:bg-dark-frame-bg py-4 font-serif">
43+
<InvitationCardSkeleton />
44+
<InvitationCardSkeleton />
45+
<InvitationCardSkeleton />
46+
</div>
47+
);
48+
}
49+
if (error) {
50+
return <p>Error loading data</p>;
51+
}
52+
53+
const traineeAverages = data?.fetchAllRatings.reduce(
54+
(acc: Record<string, Trainee>, rating: any) => {
55+
const { id } = rating.user;
56+
if (!acc[id]) acc[id] = { ...rating.user, total: 0, count: 0 };
57+
acc[id].total += parseFloat(rating.average);
58+
acc[id].count += 1;
59+
return acc;
60+
},
61+
{} as Record<string, Trainee>,
62+
);
63+
64+
const trainees = Object.values(traineeAverages).map((trainee) => {
65+
const typedTrainee = trainee as Trainee; // Cast to Trainee type
66+
return {
67+
...typedTrainee,
68+
average: typedTrainee.total / typedTrainee.count,
69+
};
70+
});
71+
72+
const sortedTrainees = trainees.sort((a, b) => b.average - a.average);
73+
const topThree = sortedTrainees.slice(0, 3);
74+
const lastThree = sortedTrainees.slice(-3);
75+
76+
const ratings = data?.fetchAllRatings || [];
77+
78+
const cohorts = ratings.reduce((acc: any, rating: any) => {
79+
const cohortName = rating.cohort.name;
80+
const averageRating = parseFloat(rating.average);
81+
82+
if (!acc[cohortName]) {
83+
acc[cohortName] = {
84+
totalRating: 0,
85+
traineeCount: 0,
86+
coordinator: rating.coordinator,
87+
};
88+
}
89+
acc[cohortName].totalRating += averageRating;
90+
acc[cohortName].traineeCount += 1;
91+
92+
return acc;
93+
}, {});
94+
95+
const cohortPerformances = Object.keys(cohorts).map((cohortName) => ({
96+
cohortName,
97+
averageRating:
98+
cohorts[cohortName].totalRating / cohorts[cohortName].traineeCount,
99+
coordinator: cohorts[cohortName].coordinator,
100+
}));
101+
102+
const topCohorts = cohortPerformances
103+
.sort((a, b) => b.averageRating - a.averageRating)
104+
.slice(0, 3);
105+
106+
return (
107+
<div className="flex flex-col grow bg-light-bg dark:bg-dark-frame-bg">
108+
<div className="flex flex-row">
109+
<div className="flex flex-col items-center w-[80%] mx-auto mdl:w-[100%] mdl:justify-between gap-5 xmd:flex-row xmd:flex-wrap bg-light-bg dark:bg-dark-frame-bg py-4 font-serif">
110+
<div className="w-[20rem] p-4 bg-indigo-100 border border-gray-200 rounded-lg shadow sm:p-8 dark:bg-gray-800 dark:border-gray-700">
111+
<div className="flex items-center justify-between mb-4 h-8">
112+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
113+
Top Performing Cohorts
114+
</h5>
115+
</div>
116+
<div className="flow-root">
117+
<ul
118+
role="list"
119+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
120+
>
121+
{topCohorts.map((cohort) => (
122+
<li
123+
key={cohort.cohortName}
124+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
125+
>
126+
<div className="flex items-center">
127+
<div className="flex-shrink-0">
128+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
129+
{cohort.cohortName.charAt(0).toUpperCase()}
130+
</div>
131+
</div>
132+
<div className="flex-1 min-w-0 ms-4">
133+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
134+
{cohort.cohortName}
135+
</p>
136+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
137+
Average Rating: {cohort.averageRating.toFixed(2)}
138+
</p>
139+
</div>
140+
</div>
141+
</li>
142+
))}
143+
</ul>
144+
</div>
145+
</div>
146+
147+
<div className="w-[20rem] p-4 bg-indigo-100 border border-gray-200 rounded-lg shadow sm:p-8 dark:bg-gray-800 dark:border-gray-700">
148+
<div className="flex items-center justify-between mb-4 h-8">
149+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
150+
Top Performing Trainees
151+
</h5>
152+
</div>
153+
<div className="flow-root">
154+
<ul
155+
role="list"
156+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
157+
>
158+
{topThree.map((trainee) => (
159+
<li
160+
key={trainee.id}
161+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
162+
>
163+
<div className="flex items-center">
164+
<div className="flex-shrink-0">
165+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
166+
{trainee.profile?.firstName?.charAt(0).toUpperCase()}
167+
</div>
168+
</div>
169+
<div className="flex-1 min-w-0 ms-4">
170+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
171+
{trainee.profile?.firstName}{' '}
172+
{trainee.profile?.lastName}
173+
</p>
174+
{/* <p className="text-sm text-gray-500 truncate dark:text-gray-400">
175+
Average Rating: {trainee.average.toFixed(2)}
176+
</p> */}
177+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
178+
{trainee.email}
179+
</p>
180+
</div>
181+
</div>
182+
</li>
183+
))}
184+
</ul>
185+
</div>
186+
</div>
187+
188+
<div className="w-[20rem] p-4 bg-indigo-100 border border-gray-200 rounded-lg shadow sm:p-8 dark:bg-gray-800 dark:border-gray-700">
189+
<div className="flex items-center justify-between mb-4 h-8">
190+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
191+
Last Performing Trainees
192+
</h5>
193+
</div>
194+
<div className="flow-root">
195+
<ul
196+
role="list"
197+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
198+
>
199+
{lastThree.map((trainee) => (
200+
<li
201+
key={trainee.id}
202+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
203+
>
204+
<div className="flex items-center">
205+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
206+
{trainee.profile?.firstName?.charAt(0).toUpperCase()}
207+
</div>
208+
<div className="flex-1 min-w-0 ms-4">
209+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
210+
{trainee.profile?.firstName}{' '}
211+
{trainee.profile?.lastName}
212+
</p>
213+
{/* <p className="text-sm text-gray-500 truncate dark:text-gray-400">
214+
Average Rating: {trainee.average.toFixed(2)}
215+
</p> */}
216+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
217+
{trainee.email}
218+
</p>
219+
</div>
220+
</div>
221+
</li>
222+
))}
223+
</ul>
224+
</div>
225+
</div>
226+
</div>
227+
</div>
228+
<div className=" fex-row justify-ceter w-[100%] pb8">
229+
<CoordinatorTraineeDashboard />
230+
</div>
231+
</div>
232+
);
233+
}
234+
235+
export default CoordinatorDashboard;

src/pages/Dashboard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import SupAdDashboard from './SupAdDashboard';
44
import AdminDashboard from './AdminDashboard';
55
import TraineeDashboard from './TraineeDashboard';
66
import ManagerCard from '../components/ManagerCard';
7+
import CoordinatorDashboard from './CoordinatorDashboard';
78

89
export function Dashboard() {
910
return (
@@ -21,7 +22,7 @@ export function Dashboard() {
2122
<TraineeDashboard />
2223
</CheckRole>
2324
<CheckRole roles={['coordinator']}>
24-
<AdminDashboard />
25+
<CoordinatorDashboard />
2526
</CheckRole>
2627
<CheckRole roles={['manager']}>
2728
<ManagerCard />

0 commit comments

Comments
 (0)