Skip to content

Commit 7df865f

Browse files
Jadowacu1IsmaelmurekeziMugisha146Mugishahbapte
committed
* feature: improve trainee details page (#172)
* handling missing application info, also adding download functionality * fixing error related to download and refactoring * Update TrainneeDetails.tsx * handling issues related to deployment * Fix number can't be shared (#130) * #102 sidebar links review (#128) * fix: remove placeholder property * fix duplicate links --------- * Ft minimize dashboard menu #110 (#140) * fix: remove placeholder property * ft minimize dashboard menu * fix minimize dashboard by icon and categorize into section * fix minimize dashboard by icon and categorize into section * fix minimize dashboard by icon and categorize into section * fix minimize dashboard by icon and categorize into section * fix minimize dashboard by icon and categorize into section * fix minimize dashboard by icon and categorize into section * fix minimize dashboard scrollbar * fix minimize dashboard scrollbar * fix minimize dashboard scrollbar * Fix layout spacing between sidebar and main content in AdminLayout * new * Fix layout spacing between sidebar and main content in AdminLayout * fix layout --------- * #118 fx: builtinSuperAdminCreateProgram (#126) * fix: remove placeholder property * The built-in superadmin account cannot create a program --------- * feature: improve trainee details page * handling missing application info, also adding download functionality * Update TrainneeDetails.tsx * adding way to send email and other adjustments * refining and fixing some issues * Update webpack.config.js * customizing way of sending email * refactoring code to fix issue related to code climate * fixing issue for deployment * fixing issues related to refactoring --------- Co-authored-by: MUREKEZI Ismael <[email protected]> Co-authored-by: MUGISHA Emmanuel <[email protected]> Co-authored-by: Mugisha <[email protected]> Co-authored-by: ISHIMWE Jean Baptiste <[email protected]> Co-authored-by: ceelogre <[email protected]> Co-authored-by: ManziPatrick <[email protected]> Co-authored-by: Prince-Kid <[email protected]> Co-authored-by: Mucyo Prince <[email protected]> Co-authored-by: Aime-Patrick <[email protected]>
1 parent bd88713 commit 7df865f

21 files changed

+626
-18142
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"@fortawesome/react-fontawesome": "^0.2.0",
7979
"@heroicons/react": "^1.0.6",
8080
"@hookform/resolvers": "^3.3.0",
81+
"@mui/icons-material": "^6.1.1",
8182
"@mui/material": "^5.10.11",
8283
"@mui/x-date-pickers": "^5.0.6",
8384
"@testing-library/jest-dom": "^5.16.5",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import { Link } from "react-router-dom";
3+
import { HiDotsVertical } from "react-icons/hi";
4+
5+
interface ApplicationActionsProps {
6+
_id: string;
7+
isDrop: string | number;
8+
setDrop: (id: string | number) => void;
9+
}
10+
11+
const ApplicationActions: React.FC<ApplicationActionsProps> = ({ _id, isDrop, setDrop }) => {
12+
const toggleDropdown = () => {
13+
if (isDrop === _id) {
14+
setDrop("");
15+
} else {
16+
setDrop(_id);
17+
}
18+
};
19+
20+
return (
21+
<div className="relative">
22+
<HiDotsVertical className="text-gray-600 dark:text-white cursor-pointer" onClick={toggleDropdown} />
23+
<div className={`${isDrop === _id ? "block" : "hidden"} absolute right-12 z-10 mt-2 w-32 bg-white dark:bg-gray-700 rounded-md shadow-lg py-1`}>
24+
<Link to={`/admin/application-details/${_id}`} className="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-500">
25+
View Details
26+
</Link>
27+
<button className="block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-500">
28+
Soft Delete
29+
</button>
30+
<button className="block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-500">
31+
Hard Delete
32+
</button>
33+
</div>
34+
</div>
35+
);
36+
};
37+
38+
export default ApplicationActions;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* eslint-disable */
2+
import React from "react";
3+
4+
interface ApplicationFilterProps {
5+
searchTerm: string;
6+
setSearchTerm: (value: string) => void;
7+
filterStatus: string;
8+
setFilterStatus: (value: string) => void;
9+
sortBy: string;
10+
setSortBy: (value: string) => void;
11+
sortOrder: string;
12+
setSortOrder: (value: string) => void;
13+
}
14+
15+
16+
17+
const ApplicationFilter: React.FC<ApplicationFilterProps> = ({
18+
searchTerm, setSearchTerm, filterStatus, setFilterStatus, sortBy, setSortBy, sortOrder, setSortOrder
19+
}) => {
20+
return (
21+
<div className="flex items-center justify-between space-x-4 mb-4">
22+
<input
23+
type="text"
24+
placeholder="Search applicants..."
25+
value={searchTerm}
26+
onChange={(e) => setSearchTerm(e.target.value)}
27+
className="border text-sm dark:border-slate-400 dark:bg-dark-bg dark:text-white p-[0.5rem] rounded-md w-1/4"
28+
/>
29+
30+
<div className="flex gap-4 items-center">
31+
<select value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}
32+
className="border dark:border-slate-400 p-[0.5rem] text-sm rounded-md dark:bg-dark-bg dark:text-white " >
33+
<option value="All">All Status</option>
34+
<option value="submitted">Submitted</option>
35+
<option value="Shortlisted">Shortlisted</option>
36+
<option value="English assessment">English Assessment</option>
37+
<option value="Technical assessment">Technical Assessment</option>
38+
<option value="Done Technical assessment">Done Technical Assessment</option>
39+
<option value="Invited for Home Challenge">Invited for Home Challenge</option>
40+
<option value="Done Home Challenge">Done Home Challenge</option>
41+
<option value="Invited for Interview">Invited for Interview</option>
42+
<option value="Accepted">Accepted</option>
43+
<option value="Reject">Rejected</option>
44+
<option value="Missed English assessment">Missed English Assessment</option>
45+
<option value="Missed Technical assessment">Missed Technical Assessment</option>
46+
<option value="Missed Interview">Missed Interview</option>
47+
</select>
48+
49+
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)} className="border text-sm dark:border-slate-400 p-[0.5rem] rounded-md dark:bg-dark-bg dark:text-white">
50+
<option value="dateOfSubmission">Date</option>
51+
<option value="firstName">First Name</option>
52+
<option value="lastName">Last Name</option>
53+
<option value="status">Status</option>
54+
</select>
55+
<select value={sortOrder} onChange={(e) => setSortOrder(e.target.value)} className="border text-sm dark:border-slate-400 p-[0.5rem] rounded-md dark:bg-dark-bg dark:text-white">
56+
<option value="desc">DESC</option>
57+
<option value="asc">ASC</option>
58+
</select>
59+
60+
</div>
61+
</div>
62+
);
63+
};
64+
65+
export default ApplicationFilter;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import { getStatusClass } from "./statusHelper";
3+
import ApplicationActions from "./ApplicationActions";
4+
5+
interface Application {
6+
_id: string;
7+
firstName: string;
8+
lastName: string;
9+
email: string;
10+
gender: string;
11+
status: string;
12+
dateOfSubmission: string;
13+
}
14+
15+
interface ApplicationRowProps {
16+
application: Application;
17+
isDrop: string | number;
18+
setDrop: (id: string | number) => void;
19+
}
20+
21+
const ApplicationRow: React.FC<ApplicationRowProps> = ({ application, isDrop, setDrop }) => (
22+
<tr className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
23+
<td className="px-5 py-3 border-b-2 border-gray-200 dark:border-dark-tertiary text-left text-[0.8rem] font-semibold text-gray-600 dark:text-white uppercase tracking-wider">
24+
{application.firstName}
25+
</td>
26+
<td className="px-5 py-3 border-b-2 border-gray-200 dark:border-dark-tertiary text-left text-[0.85rem] font-semibold text-gray-600 dark:text-white tracking-wider">
27+
{application.lastName}
28+
</td>
29+
<td className="px-5 py-3 border-b-2 border-gray-200 dark:border-dark-tertiary text-left text-[0.85rem] font-semibold text-gray-600 dark:text-white tracking-wider">
30+
{application.email}
31+
</td>
32+
<td className="px-5 py-3 border-b-2 border-gray-200 dark:border-dark-tertiary text-left text-[0.85rem] font-semibold text-gray-600 dark:text-white tracking-wider">
33+
{application.gender}
34+
</td>
35+
<td className="px-5 py-5 border-b border-gray-200 dark:border-dark-tertiary">
36+
<span className={`inline-block py-1 px-3 rounded-full text-sm ${getStatusClass(application.status)}`}>
37+
{application.status}
38+
</span>
39+
</td>
40+
<td className="px-5 py-3 border-b-2 border-gray-200 dark:border-dark-tertiary text-left text-[0.85rem] font-semibold text-gray-600 dark:text-white tracking-wider">
41+
{application.dateOfSubmission}
42+
</td>
43+
<td className="px-5 py-5 border-b border-gray-200 dark:border-dark-tertiary text-sm">
44+
<ApplicationActions _id={application._id} isDrop={isDrop} setDrop={setDrop} />
45+
</td>
46+
</tr>
47+
);
48+
49+
export default ApplicationRow;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from "react";
2+
import ApplicationRow from "./ApplicationRow";
3+
4+
interface Application {
5+
_id: string;
6+
firstName: string;
7+
lastName: string;
8+
email: string;
9+
gender: string;
10+
status: string;
11+
dateOfSubmission: string;
12+
}
13+
14+
interface ApplicationTableProps {
15+
filteredApplications: Application[];
16+
}
17+
18+
const ApplicationTable: React.FC<ApplicationTableProps> = ({ filteredApplications }) => {
19+
const [isDrop, setDrop] = React.useState<string | number>("");
20+
21+
if (filteredApplications.length === 0) {
22+
return (
23+
<div className="flex justify-center items-center p-10">
24+
<p className="text-gray-500 dark:text-gray-300">No applicants found. Try adjusting your search or filters.</p>
25+
</div>
26+
);
27+
}
28+
29+
return (
30+
<table className="min-w-full leading-normal">
31+
<thead className="bg-gray-100 dark:bg-dark-tertiary">
32+
<tr>
33+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">First Name</th>
34+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Last Name</th>
35+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Email</th>
36+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Gender</th>
37+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Status</th>
38+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Submitted At</th>
39+
<th className="px-5 py-3 text-gray-600 text-[0.85rem] dark:text-white uppercase text-left">Action</th>
40+
</tr>
41+
</thead>
42+
<tbody>
43+
{filteredApplications.map((application) => (
44+
<ApplicationRow key={application._id} application={application} isDrop={isDrop} setDrop={setDrop} />
45+
))}
46+
</tbody>
47+
</table>
48+
);
49+
};
50+
51+
export default ApplicationTable;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-disable */
2+
import React from 'react';
3+
4+
interface WarningModalProps {
5+
isOpen: boolean;
6+
message: string;
7+
onConfirm: () => void;
8+
onCancel: () => void;
9+
}
10+
11+
const WarningModal: React.FC<WarningModalProps> = ({ isOpen, message, onConfirm, onCancel }) => {
12+
if (!isOpen) return null;
13+
14+
return (
15+
<div className="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
16+
<div className="bg-white border dark:border-gray-500 dark:bg-dark-bg rounded-lg p-6">
17+
<h3 className="text-lg font-semibold text-red-600">Warning: Irreversible Action</h3>
18+
<p className="mt-4 text-gray-700 dark:text-gray-300">
19+
{message}
20+
</p>
21+
<div className="mt-6 flex justify-end space-x-4">
22+
<button
23+
className="px-4 py-2 bg-gray-300 hover:bg-gray-400 text-gray-700 rounded-md"
24+
onClick={onCancel}
25+
>
26+
Cancel
27+
</button>
28+
<button
29+
className="px-4 py-2 bg-red-600 hover:bg-red-500 text-white rounded-md"
30+
onClick={onConfirm}
31+
>
32+
Confirm
33+
</button>
34+
</div>
35+
</div>
36+
</div>
37+
);
38+
};
39+
40+
export default WarningModal;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const getStatusClass = (status: string): string => {
2+
switch (status) {
3+
case "submitted":
4+
return "bg-blue-500 text-white";
5+
case "Shortlisted":
6+
return "bg-teal-500 text-white";
7+
case "English assessment":
8+
return "bg-yellow-500 text-white";
9+
case "Technical assessment":
10+
return "bg-orange-500 text-white";
11+
case "Done Technical assessment":
12+
return "bg-purple-500 text-white";
13+
case "Invited for Home Challenge":
14+
return "bg-teal-500 text-white";
15+
case "Done Home Challenge":
16+
return "bg-indigo-500 text-white";
17+
case "Invited for Interview":
18+
return "bg-pink-500 text-white";
19+
case "Accepted":
20+
return "bg-teal-700 text-white";
21+
case "Rejected":
22+
return "bg-red-500 text-white";
23+
case "Missed English assessment":
24+
case "Missed Technical assessment":
25+
case "Missed Interview":
26+
case "Missed Home Challenge":
27+
return "bg-gray-500 text-white";
28+
default:
29+
return "bg-gray-300 text-black";
30+
}
31+
};
32+

src/components/form/SignInForm.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,18 @@ const LoginForm = () => {
5151
});
5252

5353
const redirectAfterLogin = async () => {
54-
const lastAttemptedRoute = localStorage.getItem('lastAttemptedRoute');
55-
if (lastAttemptedRoute) {
56-
localStorage.removeItem('lastAttemptedRoute');
57-
navigate(lastAttemptedRoute);
58-
} else {
5954
await Token();
6055
const role = localStorage.getItem("roleName") as string;
56+
6157
if (role === "applicant") {
6258
navigate("/applicant");
63-
} else if (role === "superAdmin" || role === "admin") {
59+
} else if (role === "superAdmin" || "Admin") {
6460
navigate("/admin");
6561
} else {
6662
const searchParams = new URLSearchParams(location.search);
6763
const returnUrl = searchParams.get('returnUrl') || '/';
6864
navigate(returnUrl);
6965
}
70-
}
7166
}
7267

7368
const onSubmit = async (data: loginFormData) => {
@@ -188,7 +183,7 @@ const LoginForm = () => {
188183
</div>
189184
<p className="text-sm mt-3 mb-2 text-[#616161] dark:text-gray-300">
190185
Don't have an account?{" "}
191-
<Link to="/register" className="text-[#56C870]">
186+
<Link to={'/signup'} className="text-[#56C870]">
192187
Sign up
193188
</Link>
194189
</p>

src/components/sidebar/navHeader.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const logo: string = require("../../assets/logo.svg").default;
1212
const profile: string = require("../../assets/avatar.png").default;
1313
const LogoWhite: string = require("../../assets/logoWhite.svg").default;
1414
import jwtDecode from "jwt-decode";
15+
import {destination} from '../../utils/utils'
1516

1617
const placeholderImage = profile;
1718

@@ -20,6 +21,7 @@ const onImageError = (e) => {
2021
}
2122

2223
function NavBar() {
24+
const userDestination = destination();
2325
const access_token = localStorage.getItem("access_token");
2426
//@ts-ignore
2527
const user = access_token ? jwtDecode(access_token).picture : profile;
@@ -35,7 +37,10 @@ function NavBar() {
3537
const handleShowProfileDropdown = () =>
3638
setShowprofileDropdown(!showProfileDropdown);
3739

40+
41+
3842
return (
43+
3944
<div className="flex items-center dark:bg-zinc-800 ">
4045
{showProfileDropdown && (
4146
<ProfileDropdown
@@ -59,8 +64,9 @@ function NavBar() {
5964
<IoClose className="w-7 text-9xl dark:text-dark-text-fill" />
6065
)}
6166
</span>
67+
6268
<span>
63-
<Link to="/" className="flex items-center">
69+
<Link to={userDestination} className="flex items-center">
6470
{theme ? (
6571
<img
6672
className="cursor-pointer mx-2 fill-[blue]"
@@ -79,6 +85,7 @@ function NavBar() {
7985
</h1>
8086
</Link>
8187
</span>
88+
8289
</div>
8390
<div className="flex items-center mr-4">
8491
<span className="flex items-center">

src/components/sidebar/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const Sidebar = ({ expanded, setExpanded }) => {
6969
</ul>
7070
<button
7171
onClick={handleLogout}
72-
className="flex items-center p-1 font-semibold hover:font-bold text-white focus:outline-none hover:text-[#56c770] mt-4 ml-4 mt-3"
72+
className="flex items-center p-1 font-semibold hover:font-bold text-white focus:outline-none hover:text-[#56c770] mt-4 ml-4"
7373
>
7474
<Icon icon="hugeicons:logout-circle-02" className="mr-3" />
7575
{expanded && <span>Logout</span>}

0 commit comments

Comments
 (0)