Skip to content

Commit 88d0c96

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

File tree

6 files changed

+2022
-161
lines changed

6 files changed

+2022
-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: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
// Step 3: Type assert `trainee` in the `map` function
65+
// Step 3: Type cast `trainee` in the `map` function
66+
const trainees = Object.values(traineeAverages).map((trainee) => {
67+
const typedTrainee = trainee as Trainee; // Cast to Trainee type
68+
return {
69+
...typedTrainee,
70+
average: typedTrainee.total / typedTrainee.count,
71+
};
72+
});
73+
74+
const sortedTrainees = trainees.sort((a, b) => b.average - a.average);
75+
const topThree = sortedTrainees.slice(0, 3);
76+
const lastThree = sortedTrainees.slice(-3);
77+
78+
const ratings = data?.fetchAllRatings || [];
79+
80+
const cohorts = ratings.reduce((acc: any, rating: any) => {
81+
const cohortName = rating.cohort.name;
82+
const averageRating = parseFloat(rating.average);
83+
84+
if (!acc[cohortName]) {
85+
acc[cohortName] = {
86+
totalRating: 0,
87+
traineeCount: 0,
88+
coordinator: rating.coordinator,
89+
};
90+
}
91+
acc[cohortName].totalRating += averageRating;
92+
acc[cohortName].traineeCount += 1;
93+
94+
return acc;
95+
}, {});
96+
97+
const cohortPerformances = Object.keys(cohorts).map((cohortName) => ({
98+
cohortName,
99+
averageRating:
100+
cohorts[cohortName].totalRating / cohorts[cohortName].traineeCount,
101+
coordinator: cohorts[cohortName].coordinator,
102+
}));
103+
104+
const topCohorts = cohortPerformances
105+
.sort((a, b) => b.averageRating - a.averageRating)
106+
.slice(0, 3);
107+
108+
return (
109+
<div className="flex flex-col grow bg-light-bg dark:bg-dark-frame-bg">
110+
<div className="flex flex-row">
111+
<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">
112+
<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">
113+
<div className="flex items-center justify-between mb-4 h-8">
114+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
115+
Top Performing Cohorts
116+
</h5>
117+
</div>
118+
<div className="flow-root">
119+
<ul
120+
role="list"
121+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
122+
>
123+
{topCohorts.map((cohort) => (
124+
<li
125+
key={cohort.cohortName}
126+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
127+
>
128+
<div className="flex items-center">
129+
<div className="flex-shrink-0">
130+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
131+
{cohort.cohortName.charAt(0).toUpperCase()}
132+
</div>
133+
</div>
134+
<div className="flex-1 min-w-0 ms-4">
135+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
136+
{cohort.cohortName}
137+
</p>
138+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
139+
Average Rating: {cohort.averageRating.toFixed(2)}
140+
</p>
141+
</div>
142+
</div>
143+
</li>
144+
))}
145+
</ul>
146+
</div>
147+
</div>
148+
149+
<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">
150+
<div className="flex items-center justify-between mb-4 h-8">
151+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
152+
Top Performing Trainees
153+
</h5>
154+
</div>
155+
<div className="flow-root">
156+
<ul
157+
role="list"
158+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
159+
>
160+
{topThree.map((trainee) => (
161+
<li
162+
key={trainee.id}
163+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
164+
>
165+
<div className="flex items-center">
166+
<div className="flex-shrink-0">
167+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
168+
{trainee.profile?.firstName?.charAt(0).toUpperCase()}
169+
</div>
170+
</div>
171+
<div className="flex-1 min-w-0 ms-4">
172+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
173+
{trainee.profile?.firstName}{' '}
174+
{trainee.profile?.lastName}
175+
</p>
176+
{/* <p className="text-sm text-gray-500 truncate dark:text-gray-400">
177+
Average Rating: {trainee.average.toFixed(2)}
178+
</p> */}
179+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
180+
Email: {trainee.email}
181+
</p>
182+
</div>
183+
</div>
184+
</li>
185+
))}
186+
</ul>
187+
</div>
188+
</div>
189+
190+
<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">
191+
<div className="flex items-center justify-between mb-4 h-8">
192+
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
193+
Last Performing Trainees
194+
</h5>
195+
</div>
196+
<div className="flow-root">
197+
<ul
198+
role="list"
199+
className="divide-y h-[12.5rem] divide-gray-200 dark:divide-gray-700"
200+
>
201+
{lastThree.map((trainee) => (
202+
<li
203+
key={trainee.id}
204+
className="py-3 sm:py-4 border-t border-t-black dark:border-t-gray-200"
205+
>
206+
<div className="flex items-center">
207+
<div className="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center">
208+
{trainee.profile?.firstName?.charAt(0).toUpperCase()}
209+
</div>
210+
<div className="flex-1 min-w-0 ms-4">
211+
<p className="text-sm font-medium text-gray-900 truncate dark:text-white">
212+
{trainee.profile?.firstName}{' '}
213+
{trainee.profile?.lastName}
214+
</p>
215+
{/* <p className="text-sm text-gray-500 truncate dark:text-gray-400">
216+
Average Rating: {trainee.average.toFixed(2)}
217+
</p> */}
218+
<p className="text-sm text-gray-500 truncate dark:text-gray-400">
219+
Email: {trainee.email}
220+
</p>
221+
</div>
222+
</div>
223+
</li>
224+
))}
225+
</ul>
226+
</div>
227+
</div>
228+
</div>
229+
</div>
230+
<div className=" fex-row justify-ceter w-[100%] pb8">
231+
<CoordinatorTraineeDashboard />
232+
</div>
233+
</div>
234+
);
235+
}
236+
237+
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)