Skip to content

Commit 8c6b1b4

Browse files
committed
(fix): add coordinator dashboard page
1 parent 4321d94 commit 8c6b1b4

File tree

7 files changed

+1874
-40
lines changed

7 files changed

+1874
-40
lines changed

src/components/DataTable.tsx

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

6767
return (
6868
<div
69-
className={`relative font-serif bg-indigo-100 dark:bg-dark-bg shadow-lg h-fit px-5 py-8 rounded-md w-[100%] "lg:ml-60 mx-auto"} mb-10`}
69+
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`}
7070
>
7171
<div className="flex items-center justify-between pb-6 ">
7272
<div>
@@ -112,7 +112,6 @@ function DataTable({ data, columns, title, loading, className }: TableData) {
112112
!loading &&
113113
page.map((row) => {
114114
prepareRow(row);
115-
// console.log(row.getRowProps());
116115
return (
117116
<tr
118117
className={`border-b dark:border-gray-700 ${

src/components/InvitationTable.tsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useMemo } from "react";
1+
import React, { useMemo } from 'react';
22
import {
33
createColumnHelper,
44
flexRender,
55
getCoreRowModel,
66
useReactTable,
7-
} from "@tanstack/react-table";
8-
import Pagination from "../components/InvitationDataPagination";
7+
} from '@tanstack/react-table';
8+
import Pagination from '../components/InvitationDataPagination';
99
import SkeletonTable from '../Skeletons/SkeletonTable';
1010

1111
interface Column {
@@ -19,7 +19,10 @@ interface TableProps {
1919
data: any[];
2020
loading: boolean;
2121
rowCount: number;
22-
onPaginationChange: (pagination: { pageIndex: number; pageSize: number }) => void;
22+
onPaginationChange: (pagination: {
23+
pageIndex: number;
24+
pageSize: number;
25+
}) => void;
2326
pagination: { pageSize: number; pageIndex: number };
2427
}
2528

@@ -42,22 +45,29 @@ function InvitationTable({
4245
cols.map((column) => {
4346
if (typeof column.accessor === 'string') {
4447
return columnHelper.accessor(column.accessor, {
45-
header: typeof column.header === 'string' ? column.header : column.header(),
48+
header:
49+
typeof column.header === 'string'
50+
? column.header
51+
: column.header(),
4652
cell: column.cell,
4753
});
48-
} else if (typeof column.accessor === 'function') {
54+
}
55+
if (typeof column.accessor === 'function') {
4956
return columnHelper.accessor((row) => column.accessor(row), {
50-
header: typeof column.header === 'string' ? column.header : column.header(),
51-
cell: column.cell,
52-
});
53-
} else {
54-
return columnHelper.display({
55-
header: typeof column.header === 'string' ? column.header : column.header(),
57+
header:
58+
typeof column.header === 'string'
59+
? column.header
60+
: column.header(),
5661
cell: column.cell,
5762
});
5863
}
64+
return columnHelper.display({
65+
header:
66+
typeof column.header === 'string' ? column.header : column.header(),
67+
cell: column.cell,
68+
});
5969
}),
60-
[cols]
70+
[cols],
6171
);
6272

6373
const tableLib = useReactTable({
@@ -83,11 +93,11 @@ function InvitationTable({
8393
{tableLib.getHeaderGroups().map((headerGroup) => (
8494
<tr key={headerGroup.id}>
8595
{headerGroup.headers.map((header) => (
86-
<th
87-
key={header.id}
88-
className="thead"
89-
>
90-
{flexRender(header.column.columnDef.header, header.getContext())}
96+
<th key={header.id} className="thead">
97+
{flexRender(
98+
header.column.columnDef.header,
99+
header.getContext(),
100+
)}
91101
</th>
92102
))}
93103
</tr>
@@ -116,18 +126,21 @@ function InvitationTable({
116126
}`}
117127
>
118128
{row.getVisibleCells().map((cell) => (
119-
<td
120-
key={cell.id}
121-
className="data-cell "
122-
>
123-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
129+
<td key={cell.id} className="data-cell ">
130+
{flexRender(
131+
cell.column.columnDef.cell,
132+
cell.getContext(),
133+
)}
124134
</td>
125135
))}
126136
</tr>
127137
))}
128138
{!loading && data.length === 0 && (
129139
<tr>
130-
<td colSpan={tableColumns.length || 100} className="text-center p-4">
140+
<td
141+
colSpan={tableColumns.length || 100}
142+
className="text-center p-4"
143+
>
131144
<div className="flex flex-col items-center justify-center space-y-4">
132145
<p className="text-gray-600 dark:text-gray-400 text-lg font-medium">
133146
No records available

src/components/NotFoundPage.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
21
import React from 'react';
32
import { Link } from 'react-router-dom';
43

5-
const NotFound=() => {
4+
function NotFound() {
65
return (
76
<div className="flex items-center justify-center h-screen bg-gray-100">
87
<div className="text-center">
98
<h1 className="text-4xl font-bold text-red-600">404</h1>
109
<p className="text-lg text-gray-700 mt-4">Page not found</p>
11-
<Link to="/" className="mt-6 inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
10+
<Link
11+
to="/"
12+
className="mt-6 inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
13+
>
1214
Go Back Home
1315
</Link>
1416
</div>
1517
</div>
1618
);
17-
};
19+
}
1820

1921
export default NotFound;

src/pages/CoordinatorDashboard.tsx

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

0 commit comments

Comments
 (0)