From 78b4b3b6c13b3ca7c3516f30137f48470ee7c03d Mon Sep 17 00:00:00 2001 From: berniceu <113672733+berniceu@users.noreply.github.com> Date: Mon, 14 Oct 2024 21:12:57 +0200 Subject: [PATCH 1/7] #105 Ft applicant dashboard (#197) * add applicant dashboard page * add dashboard cards * integrate with backend * fetch all data from their respective queries * remove console logs * update cohort types and query * fix routes * fix code climate * add graph * fix chart * delete yarn * fix graph and login * fix * trigger * revert --------- Co-authored-by: uwituzeb --- package.json | 10 +- src/assets/assets/calendar.svg | 9 + src/assets/assets/collaborative-learning.svg | 9 + src/assets/assets/performance.svg | 9 + src/assets/assets/strategy.svg | 9 + src/components/form/SignInForm.tsx | 5 + src/components/sidebar/sidebarItems.tsx | 5 + src/pages/Applicant/ApplicantDashboard.tsx | 295 +++++++++++++++++++ src/redux/actions/TraineeAction.ts | 48 ++- src/redux/actions/attendanceAction.ts | 31 ++ src/redux/actions/cohortActions.ts | 49 ++- src/redux/actions/login.ts | 6 + src/redux/actions/performanceAction.ts | 32 ++ src/redux/actiontypes/TraineeType.ts | 33 +++ src/redux/actiontypes/attendanceTypes.ts | 10 + src/redux/actiontypes/cohortTypes.ts | 14 + src/redux/actiontypes/performanceTypes.ts | 10 + src/redux/index.ts | 4 + src/redux/reducers/attendanceReducer.ts | 30 ++ src/redux/reducers/cohortReducer.ts | 11 +- src/redux/reducers/index.ts | 5 + src/redux/reducers/performanceReducer.ts | 30 ++ src/redux/reducers/traineeReducer.ts | 23 +- src/routes/routes.tsx | 19 +- 24 files changed, 678 insertions(+), 28 deletions(-) create mode 100644 src/assets/assets/calendar.svg create mode 100644 src/assets/assets/collaborative-learning.svg create mode 100644 src/assets/assets/performance.svg create mode 100644 src/assets/assets/strategy.svg create mode 100644 src/pages/Applicant/ApplicantDashboard.tsx create mode 100644 src/redux/actions/attendanceAction.ts create mode 100644 src/redux/actions/performanceAction.ts create mode 100644 src/redux/actiontypes/attendanceTypes.ts create mode 100644 src/redux/actiontypes/cohortTypes.ts create mode 100644 src/redux/actiontypes/performanceTypes.ts create mode 100644 src/redux/reducers/attendanceReducer.ts mode change 100644 => 100755 src/redux/reducers/index.ts create mode 100644 src/redux/reducers/performanceReducer.ts diff --git a/package.json b/package.json index a72961457..f14e538fc 100755 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@babel/preset-env": "^7.19.3", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", - "@expo/webpack-config": "^0.17.2", + "@expo/webpack-config": "^19.0.1", "@iconify/react": "^4.0.0", "@testing-library/dom": "^8.19.0", "@testing-library/jest-dom": "^5.16.5", @@ -53,7 +53,7 @@ "html-webpack-plugin": "^5.6.0", "i": "^0.3.7", "node-polyfill-webpack-plugin": "^2.0.1", - "npm": "^8.19.2", + "npm": "^10.8.3", "postcss": "^8.4.14", "postcss-loader": "^7.0.0", "process": "^0.11.10", @@ -83,7 +83,7 @@ "@mui/x-date-pickers": "^5.0.6", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", - "@tinymce/tinymce-react": "^4.2.0", + "@tinymce/tinymce-react": "^5.1.1", "@types/react-i18next": "^8.1.0", "@types/react-router": "^5.1.19", "@types/react-router-dom": "^5.3.3", @@ -120,14 +120,14 @@ "react-hot-toast": "^2.4.1", "react-i18next": "^11.18.6", "react-icons": "^4.6.0", - "react-js-pagination": "^3.0.3", + "react-js-pagination": "^3.0.2", "react-loader-spinner": "^6.1.6", "react-modal": "^3.16.1", "react-paginate": "^8.1.3", "react-redux": "^8.0.4", "react-render-html": "^0.6.0", "react-router-dom": "^6.4.2", - "react-scripts": "^5.0.1", + "react-scripts": "^3.0.1", "react-select": "^5.7.4", "react-table": "^7.8.0", "react-toastify": "^9.0.8", diff --git a/src/assets/assets/calendar.svg b/src/assets/assets/calendar.svg new file mode 100644 index 000000000..7856f1d1f --- /dev/null +++ b/src/assets/assets/calendar.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/assets/collaborative-learning.svg b/src/assets/assets/collaborative-learning.svg new file mode 100644 index 000000000..467a7d6b8 --- /dev/null +++ b/src/assets/assets/collaborative-learning.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/assets/performance.svg b/src/assets/assets/performance.svg new file mode 100644 index 000000000..0223d35cc --- /dev/null +++ b/src/assets/assets/performance.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/assets/strategy.svg b/src/assets/assets/strategy.svg new file mode 100644 index 000000000..8df00f27a --- /dev/null +++ b/src/assets/assets/strategy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/form/SignInForm.tsx b/src/components/form/SignInForm.tsx index 0d070bf0b..3c80f7848 100644 --- a/src/components/form/SignInForm.tsx +++ b/src/components/form/SignInForm.tsx @@ -77,8 +77,13 @@ const LoginForm = () => { const response = await loginAction(validatedData.email, validatedData.password); const token = response?.data?.data?.login?.token; + const userId = response?.data?.data?.login?.userId; + if (token) { localStorage.setItem("access_token", token); + if (userId) { + localStorage.setItem("userId", userId); + } await redirectAfterLogin(); } else { toast.error(response?.data?.errors[0].message); diff --git a/src/components/sidebar/sidebarItems.tsx b/src/components/sidebar/sidebarItems.tsx index 5802b939e..1f3752cd5 100644 --- a/src/components/sidebar/sidebarItems.tsx +++ b/src/components/sidebar/sidebarItems.tsx @@ -58,6 +58,11 @@ export const sidebarItems1 = [ ]; export const applicantSidebarItems = [ + { + path: "/applicant", + icon: , + title: "Dashboard", + }, { path: "myApplications", icon: , diff --git a/src/pages/Applicant/ApplicantDashboard.tsx b/src/pages/Applicant/ApplicantDashboard.tsx new file mode 100644 index 000000000..0439cc857 --- /dev/null +++ b/src/pages/Applicant/ApplicantDashboard.tsx @@ -0,0 +1,295 @@ +import React from "react"; +import { useState, useEffect } from "react"; +import NavBar from "components/sidebar/navHeader"; +import { getTraineeApplicant } from "../../redux/actions/TraineeAction"; +import { getTraineeAttendance } from "../../redux/actions/attendanceAction"; +import { getTraineePerformance } from "../../redux/actions/performanceAction"; +import { getTraineeByUserId } from "../../redux/actions/TraineeAction"; +import { getCohort } from "../../redux/actions/cohortActions"; +import { useDispatch, useSelector } from "react-redux"; +import { + Area, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + Scatter, + ComposedChart, + Label, + Line, + AreaChart, + CartesianGrid + } from "recharts"; + +const calendar: string = require("../../assets/assets/calendar.svg").default; +const collaboration: string = + require("../../assets/assets/collaborative-learning.svg").default; +const performanceIcon: string = + require("../../assets/assets/performance.svg").default; +const strategy: string = require("../../assets/assets/strategy.svg").default; + +const DashboardCard = ({ title, value, img }) => { + return ( + <> +
+
+
+ {title} +
+
+

{title}

+

{value}

+
+
+
+ + ); +}; + +const ApplicantChart = ({ performance }) => { + const transformPerformanceData = (performanceData) => { + if (!performanceData?.performances) return []; + + const sortedPerformances = [...performanceData.performances].sort((a, b) => { + return parseInt(a.date) - parseInt(b.date); + }); + + + return sortedPerformances.map((performance, index) => ({ + name: index + 1, + score: performance.score || 0, + + })); + + }; + + const chartData = transformPerformanceData(performance); + + if (chartData.length === 0) { + return
No performance data available
; + } + + + + return ( + + {chartData.length === 1 ? ( + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + )} + + ); + } + + +const useTraineeData = () => { + const dispatch = useDispatch(); + const traineeId = useSelector((state: any) => state.traineeApplicant.currentTrainee); + const traineeData = useSelector((state: any) => state.traineeApplicant.data); + const attendance = useSelector((state: any) => state.traineeAttendance); + const performance = useSelector((state: any) => state.traineePerformance); + const cohort = useSelector((state: any) => state.cohorts); + + useEffect(() => { + const userId = localStorage.getItem("userId"); + if (userId) { + dispatch(getTraineeByUserId(userId)); + } + }, [dispatch]); + + useEffect(() => { + const fetchTraineeData = async () => { + if (!traineeId) return; + try { + await dispatch(getTraineeApplicant(traineeId)); + await dispatch(getTraineeAttendance(traineeId)); + await dispatch(getTraineePerformance(traineeId)); + } catch (err) { + console.error("Failed to fetch trainee details:", err); + } + }; + fetchTraineeData(); + }, [dispatch, traineeId]); + + useEffect(() => { + const fetchCohortData = async () => { + if (traineeData && traineeData.cohort) { + try { + await dispatch(getCohort(traineeData.cohort)); + } catch (err) { + console.error("Failed to fetch cohort data:", err); + } + } + }; + fetchCohortData(); + }, [dispatch, traineeData]); + + + return { traineeData, attendance, performance, cohort }; + +} + +const formatters = { + extractCohortNumber: (cohortTitle: string): string => { + if (!cohortTitle) return "N/A"; + const match = cohortTitle.match(/\d+/); + return match ? match[0] : "N/A"; + }, + + formatPercentage: (value: number | string | undefined): string => { + if (typeof value === 'number') { + return value % 1 === 0 ? `${value}%` : `${value.toFixed(1)}%`; + } + return 'N/A'; + }, + + calculateAttendancePercentage: (ratio: string | undefined): string => { + if (!ratio) return 'N/A'; + const [attended, total] = ratio.split('/').map(Number); + if (isNaN(attended) || isNaN(total) || total === 0) return 'N/A'; + return formatters.formatPercentage(attended / total * 100); + } +} + +const ApplicantDashboard = (props: any) => { + + const { cohort, performance, attendance } = useTraineeData(); + const cohortData = cohort?.traineeCohort || {}; + const { extractCohortNumber, formatPercentage, calculateAttendancePercentage } = formatters; + + const dashboardData = [ + { + title: "Cohort", + value: cohortData.title ? extractCohortNumber(cohortData.title) : "N/A", + img: collaboration, + }, + { + title: "Current Phase", + value: cohortData.phase || "N/A", + img: strategy, + }, + { + title: "Performance", + value: formatPercentage(performance?.averageScore), + img: performanceIcon, + }, + { + title: "Attendance", + value: calculateAttendancePercentage(attendance?.attendanceRatio), + img: calendar, + }, + ]; + + return ( + <> +
+
+
+
+
+ {dashboardData.map((item, index) => ( + + ))} +
+
+
+

+ My overall performance +

+

+ As of {new Date().toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric' + })} +

+
+ +
+
+
+
+
+ + ); +}; + +export default ApplicantDashboard; diff --git a/src/redux/actions/TraineeAction.ts b/src/redux/actions/TraineeAction.ts index 24a307f43..3618d7614 100644 --- a/src/redux/actions/TraineeAction.ts +++ b/src/redux/actions/TraineeAction.ts @@ -1,5 +1,5 @@ import creator from "./creator"; -import { GET_TRAINEE, CREATE_TRAINEES, CREATE_CYCLE_ERROR } from ".."; +import { GET_TRAINEE, CREATE_TRAINEES, CREATE_CYCLE_ERROR, SET_TRAINEE } from ".."; import { toast } from "react-toastify"; import axios from "axios"; @@ -108,3 +108,49 @@ export const createTrainee = return dispatch(creator(CREATE_CYCLE_ERROR, error)); } }; + + +export const getTraineeApplicant = (traineeId: string) => async(dispatch: any) => { + try{ + const response = await axios.post(`${process.env.BACKEND_URL}`, { + query: ` + query GetOneTrainee($ID: ID!) { + getOneTrainee(ID: $ID) { + _id + applicationPhase + cohort + } + } + `, + variables: { ID: traineeId } + }); + if (response.data.errors) { + console.error('GraphQL Errors:', response.data.errors); + return; + } + const trainee = response.data.data.getOneTrainee; + dispatch(creator(GET_TRAINEE, trainee)); + + }catch (error: any) { + console.error('Error fetching trainee:', error); + console.error('Error response:', error.response?.data); + } +} + +export const getTraineeByUserId = (userId: string) => async (dispatch: any) => { + try { + const response = await axios.post(`${process.env.BACKEND_URL}`, { + query: ` + query GetTraineeByUserId($userId: ID!) { + getTraineeByUserId(userId: $userId) + } + `, + variables: { userId }, + }); + + const traineeData = response.data.data.getTraineeByUserId; + dispatch(creator(SET_TRAINEE, traineeData)); + } catch (error) { + console.error("Error fetching trainee:", error); + } +}; diff --git a/src/redux/actions/attendanceAction.ts b/src/redux/actions/attendanceAction.ts new file mode 100644 index 000000000..d7ceeca62 --- /dev/null +++ b/src/redux/actions/attendanceAction.ts @@ -0,0 +1,31 @@ +import axios from "axios"; +import creator from "./creator"; +import { GET_TRAINEE_ATTENDANCE } from ".."; + +export const getTraineeAttendance = (traineeId: any) => async (dispatch: any) => { + try { + const response = await axios.post(`${process.env.BACKEND_URL}`, { + query: ` + query GetTraineeAttendance($traineeId: ID!) { + getTraineeAttendance(traineeId: $traineeId) { + attendances { + id + date + status + } + attendanceRatio + } + } + `, + variables: { traineeId }, + }); + if (response.data.errors) { + console.error('GraphQL Errors:', response.data.errors); + return; + } + const attendanceData = response.data.data.getTraineeAttendance; + dispatch(creator(GET_TRAINEE_ATTENDANCE, attendanceData)); + } catch (err: any) { + console.error("Error fetching trainee attendance", err); + } +} \ No newline at end of file diff --git a/src/redux/actions/cohortActions.ts b/src/redux/actions/cohortActions.ts index adeb54a6f..1cddbb8be 100644 --- a/src/redux/actions/cohortActions.ts +++ b/src/redux/actions/cohortActions.ts @@ -1,7 +1,7 @@ -import creator from './creator'; -import { GET_COHORTS } from '..'; -import axios from './axiosconfig'; -import { toast } from 'react-toastify'; +import creator from "./creator"; +import { GET_COHORTS, GET_TRAINEE_COHORT } from ".."; +import axios from "./axiosconfig"; +import { toast } from "react-toastify"; export const getAllCohorts = () => async (dispatch: any) => { try { @@ -37,3 +37,44 @@ export const getAllCohorts = () => async (dispatch: any) => { return 0; } }; + +export const getCohort =(getCohortId: any) => async (dispatch: any) => { + try { + const response= await axios.post(`${process.env.BACKEND_URL}`, { + query: ` + query GetCohort($getCohortId: ID!) { + getCohort(id: $getCohortId) { + title + trainees + end + id + phase + start + cycle { + endDate + id + name + startDate + } + program { + _id + description + duration + mainObjective + modeOfExecution + requirements + title + } + } +} + `, + variables: { getCohortId } + }); + const cohortData = await response.data.data.getCohort; + dispatch(creator(GET_TRAINEE_COHORT, cohortData)); + } catch (error) { + if (error) { + return console.log(error); + } + } +}; diff --git a/src/redux/actions/login.ts b/src/redux/actions/login.ts index dc1bcc2bd..1f57dce07 100644 --- a/src/redux/actions/login.ts +++ b/src/redux/actions/login.ts @@ -7,6 +7,7 @@ export const loginAction = async (email, password) => { mutation Mutation($email: String!, $password: String!) { login(email: $email, password: $password) { token + userId } } `, @@ -21,3 +22,8 @@ export const loginAction = async (email, password) => { console.log(error); } }; + +export const setUser = (userData) => ({ + type: 'SET_USER', + payload: userData, +}); \ No newline at end of file diff --git a/src/redux/actions/performanceAction.ts b/src/redux/actions/performanceAction.ts new file mode 100644 index 000000000..6b496a48d --- /dev/null +++ b/src/redux/actions/performanceAction.ts @@ -0,0 +1,32 @@ +import axios from "axios"; +import { GET_TRAINEE_PERFORMANCE } from ".."; +import creator from "./creator"; + +export const getTraineePerformance = (traineeId: any) => async (dispatch: any) => { + try{ + const response = await axios.post(`${process.env.BACKEND_URL}`, { + query: ` + query GetTraineePerformance($traineeId: ID!) { + getTraineePerformance(traineeId: $traineeId) { + performances { + id + score + date + } + averageScore + + } + + } + + `, + variables: { traineeId } + }) + + const performanceData = response.data.data.getTraineePerformance; + dispatch(creator(GET_TRAINEE_PERFORMANCE, performanceData)); + + } catch (error) { + console.error('Error fetching trainee performance:', error); + } +} \ No newline at end of file diff --git a/src/redux/actiontypes/TraineeType.ts b/src/redux/actiontypes/TraineeType.ts index f2f2a6a14..09984cc8f 100755 --- a/src/redux/actiontypes/TraineeType.ts +++ b/src/redux/actiontypes/TraineeType.ts @@ -45,5 +45,38 @@ // export type add = addactionPending | addactionSuccess | addactionFail; // export type view = viewactionPending | viewactionSuccess | viewactionFail; +interface User { + id: string; + email: string; + firstName: string; + lastName: string; + } +export interface Trainee { + id: string; + firstName: string; + lastName: string; + email: string; + status: string; + applicationPhase: string; + user?: User; + cohort?: { + id: string; + title: string; + program: string; + cycle: string; + start: string; + end: string; + phase: string; + }; + } +export const setTrainee = (traineeData) => ({ + type: 'SET_TRAINEE', + payload: traineeData + }); + + export const setUser = (userData) => ({ + type: 'SET_USER', + payload: userData + }); diff --git a/src/redux/actiontypes/attendanceTypes.ts b/src/redux/actiontypes/attendanceTypes.ts new file mode 100644 index 000000000..beed729ee --- /dev/null +++ b/src/redux/actiontypes/attendanceTypes.ts @@ -0,0 +1,10 @@ +export interface Attendance { + id: string; + date: string; + status: string; + } + +export interface AttendanceData { + attendances: Attendance[]; + attendanceRatio: number; +} \ No newline at end of file diff --git a/src/redux/actiontypes/cohortTypes.ts b/src/redux/actiontypes/cohortTypes.ts new file mode 100644 index 000000000..0a0aa74c7 --- /dev/null +++ b/src/redux/actiontypes/cohortTypes.ts @@ -0,0 +1,14 @@ +export interface Cohort { + id: string; + title: string | null; + program: string | null; + cycle: string | null; + start: string | null; + end: string | null; + phase: 'core' | 'team' | 'apprenticeship' | null; + trainees: string[] | null; +} + +export interface CohortData { + cohort: Cohort[]; + } diff --git a/src/redux/actiontypes/performanceTypes.ts b/src/redux/actiontypes/performanceTypes.ts new file mode 100644 index 000000000..e9422916b --- /dev/null +++ b/src/redux/actiontypes/performanceTypes.ts @@ -0,0 +1,10 @@ +export interface Performance { + id: string; + score: number; + date: string; + } + + export interface PerformanceData { + performances: Performance[]; + averageScore: number; + } \ No newline at end of file diff --git a/src/redux/index.ts b/src/redux/index.ts index 39dcafc96..4bb885277 100755 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -2,6 +2,7 @@ export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const GET_ONE_TRAINEES_ALL_DETAILS = 'GET_ONE_TRAINEES_ALL_DETAILS'; export const GET_TRAINEE = 'GET_TRAINEE'; +export const SET_TRAINEE = 'SET_TRAINEE'; export const CREATE_TRAINEES = 'CREATE_TRAINEES'; export const GET_CYCLES = 'GET_CYCLES'; export const CREATE_CYCLES = 'CREATE_CYCLES'; @@ -56,4 +57,7 @@ export const MY_APPLICATIONS = 'MY_APPLICATIONS'; export const GET_PROGRAMS = 'GET_PROGRAMS'; export const GET_COHORTS = 'GET_COHORTS'; +export const GET_TRAINEE_COHORT = 'GET__TRAINEE_COHORT'; export const GET_ONE_JOB_POST_ALL_DETAILS = 'GET_ONE_JOB_POST_ALL_DETAILS'; +export const GET_TRAINEE_ATTENDANCE = 'GET_TRAINEE_ATTENDANCE'; +export const GET_TRAINEE_PERFORMANCE = 'GET_TRAINEE_PERFORMANCE'; \ No newline at end of file diff --git a/src/redux/reducers/attendanceReducer.ts b/src/redux/reducers/attendanceReducer.ts new file mode 100644 index 000000000..2904605f9 --- /dev/null +++ b/src/redux/reducers/attendanceReducer.ts @@ -0,0 +1,30 @@ +import { GET_TRAINEE_ATTENDANCE } from ".."; + +interface AttendanceState { + attendances: any[]; + attendanceRatio: number | null; + loading: boolean; + error: string | null; + } + + const initialState: AttendanceState = { + attendances: [], + attendanceRatio: null, + loading: false, + error: null + }; + + export const attendanceReducer = (state = initialState, action: any) => { + switch (action.type) { + case GET_TRAINEE_ATTENDANCE: + return { + ...state, + attendances: action.payload.attendances, + attendanceRatio: action.payload.attendanceRatio, + loading: false, + error: null + }; + default: + return state; + } + }; \ No newline at end of file diff --git a/src/redux/reducers/cohortReducer.ts b/src/redux/reducers/cohortReducer.ts index 9b26bee1e..09ca61906 100644 --- a/src/redux/reducers/cohortReducer.ts +++ b/src/redux/reducers/cohortReducer.ts @@ -1,10 +1,11 @@ -import { GET_COHORTS } from '..'; +import { GET_COHORTS, GET_TRAINEE_COHORT } from '..'; const initialState = { isLoading: true, isLoaded: false, errors: null, data: [], + traineeCohort: null, }; export default (state = initialState, { type, payload }: any) => { @@ -16,6 +17,14 @@ export default (state = initialState, { type, payload }: any) => { data: payload, }; + case GET_TRAINEE_COHORT: + return { + ...state, + isLoading: false, + isLoaded: true, + traineeCohort: payload, + }; + default: return state; } diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts old mode 100644 new mode 100755 index 7a09e0767..cca554ae9 --- a/src/redux/reducers/index.ts +++ b/src/redux/reducers/index.ts @@ -41,6 +41,8 @@ import { singleApplicationReducer, } from './applicationReducer'; import { assessmentsReducer } from './assessmentReducer'; +import { attendanceReducer } from './attendanceReducer'; +import { performanceReducer } from './performanceReducer'; import filterProgramsReducer from './filterProgramsReducer'; import filterRoleReducer from './filterRoleReducer'; import fetchSearchDataReducer from './fetchSearchDataReducer'; @@ -86,6 +88,9 @@ const allReducers = combineReducers({ currentApplication: singleApplicationReducer, assessments: assessmentsReducer, searchData: fetchSearchDataReducer, + traineeApplicant: traineeReducer, + traineeAttendance: attendanceReducer, + traineePerformance: performanceReducer }); export type RootState = ReturnType; diff --git a/src/redux/reducers/performanceReducer.ts b/src/redux/reducers/performanceReducer.ts new file mode 100644 index 000000000..3fadabe60 --- /dev/null +++ b/src/redux/reducers/performanceReducer.ts @@ -0,0 +1,30 @@ +import { GET_TRAINEE_PERFORMANCE } from ".."; + +interface PerformanceState { + performances: any[]; + averageScore: number | null; + loading: boolean; + error: string | null; + } + + const initialState: PerformanceState = { + performances: [], + averageScore: null, + loading: false, + error: null + }; + + export const performanceReducer = (state = initialState, action: any) => { + switch (action.type) { + case GET_TRAINEE_PERFORMANCE: + return { + ...state, + performances: action.payload.performances, + averageScore: action.payload.averageScore, + loading: false, + error: null + }; + default: + return state; + } + }; \ No newline at end of file diff --git a/src/redux/reducers/traineeReducer.ts b/src/redux/reducers/traineeReducer.ts index 11fa0847c..b6744a4b3 100755 --- a/src/redux/reducers/traineeReducer.ts +++ b/src/redux/reducers/traineeReducer.ts @@ -1,10 +1,13 @@ -import { GET_TRAINEE, CREATE_TRAINEES} from ".."; +import { GET_TRAINEE, CREATE_TRAINEES, SET_TRAINEE, GET_TRAINEE_ATTENDANCE, GET_TRAINEE_PERFORMANCE} from ".."; const initialState = { loading: false, error: null, data: [], + currentTrainee: null, + traineeAttendance: null, + traineePerformance: null }; export default (state = initialState, { type, payload }: any) => { @@ -23,6 +26,24 @@ export default (state = initialState, { type, payload }: any) => { loading: false, data: [...state.data, payload], }; + + case SET_TRAINEE: + return { + ...state, + loading: false, + currentTrainee: payload + }; + + case GET_TRAINEE_ATTENDANCE: + return { + ...state, + data: payload + }; + case GET_TRAINEE_PERFORMANCE: + return { + ...state, + data: payload + }; default: return state; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 654a7f505..3ad24eba7 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -48,6 +48,7 @@ import ApplicationDetails from "../pages/Applications/ViewSingleApplication"; import Dashboard from "../pages/Dashboard"; import ApplicantLayout from "../pages/Applicant/ApplicantLayout"; import AdminLayout from "../components/Layout/Admins/AdminLayout"; +import ApplicantDashboard from "../pages/Applicant/ApplicantDashboard"; import UpdateJobPost from "../pages/JobPost/updateJobPost"; import VerifyEmail from "../pages/verifyEmail"; import Search from "./../pages/search"; @@ -306,22 +307,8 @@ function Navigation() { {/* Applicant Routes (Protected) */} - - - - } - > - - - - } - /> + }> + } /> Date: Mon, 14 Oct 2024 21:32:23 +0200 Subject: [PATCH 2/7] Fix job Search (#199) Co-authored-by: vegetason --- src/components/sidebar/sidebarItems.tsx | 6 +- src/pages/Applications.tsx | 70 +++++++++++++++--- .../Applications/AdminViewApplications.tsx | 71 ++++++++++++++++--- src/pages/JobPost/job.tsx | 2 +- src/redux/actions/filterJobPost.ts | 52 +++++++------- src/redux/actions/filterProgramActions.ts | 3 - 6 files changed, 153 insertions(+), 51 deletions(-) diff --git a/src/components/sidebar/sidebarItems.tsx b/src/components/sidebar/sidebarItems.tsx index 1f3752cd5..c85018f6d 100644 --- a/src/components/sidebar/sidebarItems.tsx +++ b/src/components/sidebar/sidebarItems.tsx @@ -34,6 +34,11 @@ export const sidebarItems1 = [ icon: , title: "Applications", }, + { + path: "Trainee-applicants", + icon: , + title: "Trainees-Applicants", + }, { path: "cohort", icon: , @@ -44,7 +49,6 @@ export const sidebarItems1 = [ icon: , title: "Application Cycles", }, - { path: "grading", icon: , diff --git a/src/pages/Applications.tsx b/src/pages/Applications.tsx index ec6b06add..521bc2b5f 100644 --- a/src/pages/Applications.tsx +++ b/src/pages/Applications.tsx @@ -15,7 +15,7 @@ import { connect, useSelector } from "react-redux"; import { HiDotsVertical } from "react-icons/hi"; import Box from "@mui/material/Box"; import { useCustomPagination } from "components/Pagination/useCustomPagination"; - +import { useTheme } from "../hooks/darkmode"; type Props = {}; interface Update { @@ -35,7 +35,30 @@ const Applications = (props: any) => { pageSize: 5, page: 1, }); + const { theme, setTheme } = useTheme(); + const customTheme = (theme: any) => { + return { + ...theme, + colors: { + ...theme.colors, + text: "light-gray", + primary25: "#E5E7EB", + primary: "#d6dfdf", + neutral0: "white", + }, + }; + }; + const darkTheme = (theme: any) => { + return { + ...theme, + colors: { + primary25: "#404657", + primary: "#d6dfdf", + neutral0: "#293647", + }, + }; + }; const [deleteWarn, setDeleteWarn] = useState({ id: "", open: false, @@ -89,15 +112,42 @@ const Applications = (props: any) => {
- - - +
+
+ +
+ + + +
+
+
+
+
{/* Filter Bar */}
diff --git a/src/pages/Applications/AdminViewApplications.tsx b/src/pages/Applications/AdminViewApplications.tsx index 3d2921e9c..03feda845 100644 --- a/src/pages/Applications/AdminViewApplications.tsx +++ b/src/pages/Applications/AdminViewApplications.tsx @@ -4,10 +4,13 @@ import NavBar from '../../components/sidebar/navHeader'; import { fetchApplications } from '../../redux/actions/adminListApplications'; import { HiDotsVertical } from 'react-icons/hi'; import { Pagination } from 'flowbite-react'; - +import Select from "react-select"; +import { useTheme } from "../../hooks/darkmode"; +import * as icons from "react-icons/ai"; const ListApplications = () => { const navigate = useNavigate(); const [applications, setApplications]: any = useState(); + const { theme, setTheme } = useTheme(); const [activePage, setActivePage] = useState(1); const [itemsCountPerPage, setItemsCountPerPage] = useState(10); @@ -47,7 +50,29 @@ const ListApplications = () => { ) => { setItemsCountPerPage(Number(event.target.value)); }; + const customTheme = (theme: any) => { + return { + ...theme, + colors: { + ...theme.colors, + text: "light-gray", + primary25: "#E5E7EB", + primary: "#d6dfdf", + neutral0: "white", + }, + }; + }; + const darkTheme = (theme: any) => { + return { + ...theme, + colors: { + primary25: "#404657", + primary: "#d6dfdf", + neutral0: "#293647", + }, + }; + }; const totalApplications = applications?.length; return ( @@ -57,14 +82,42 @@ const ListApplications = () => {
-
-
- - - -
+
+
+ +
+ + + +
+
+
diff --git a/src/pages/JobPost/job.tsx b/src/pages/JobPost/job.tsx index 2ba639300..e98a9501e 100644 --- a/src/pages/JobPost/job.tsx +++ b/src/pages/JobPost/job.tsx @@ -409,7 +409,7 @@ const Jobs = (props: any) => { - {fetchJobPostStates?.data?.map((item: any) => ( + {allfilteredjobPosts?.data?.map((item: any) => ( async (dispatch: any) => { try { - if(filterAttribute==='' || filterAttribute===null){ - toast.error("Please insert a filter attribute") - } const datas = await axios.post("/", { query: ` query Query($input: FilterOptions) { From 0383d141d734809e95001ba8d0b1426bea349894 Mon Sep 17 00:00:00 2001 From: Niyonshuti Jean De Dieu <152473876+Jadowacu1@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:35:54 +0200 Subject: [PATCH 3/7] * 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 <110295158+Ismaelmurekezi@users.noreply.github.com> Co-authored-by: MUGISHA Emmanuel Co-authored-by: Mugisha Co-authored-by: ISHIMWE Jean Baptiste Co-authored-by: ceelogre Co-authored-by: ManziPatrick <144239912+ManziPatrick@users.noreply.github.com> Co-authored-by: Prince-Kid Co-authored-by: Mucyo Prince <134399659+Prince-Kid@users.noreply.github.com> Co-authored-by: Aime-Patrick --- .DS_Store | Bin 8196 -> 8196 bytes .codeclimate.yml | 38 +- .gitignore | 4 +- package.json | 13 +- src/.DS_Store | Bin 12292 -> 10244 bytes src/assets/Group (1).svg | 3 - src/assets/Group.svg | 4 - src/assets/Vector.svg | 3 - src/assets/assets/calendar.svg | 9 - src/assets/assets/collaborative-learning.svg | 9 - src/assets/assets/performance.svg | 9 - src/assets/assets/strategy.svg | 9 - .../material-symbols_manage-accounts.svg | 3 - src/components/GoogleForm/ApplicationForm.tsx | 6 +- .../GoogleForm/DeleteConfirmationModal.tsx | 62 -- src/components/GoogleForm/InputField.tsx | 47 -- src/components/GoogleForm/RecentForms.tsx | 210 +++--- src/components/GoogleForm/SaveForm.tsx | 311 +++++--- src/components/GoogleForm/SelectField.tsx | 49 -- src/components/GoogleForm/UpdateSavedForm.tsx | 228 +++--- .../GoogleForm/ViewApplicationModal.tsx | 63 -- src/components/GoogleForm/customModal.tsx | 40 -- .../Performance/PerformanceChart.tsx | 31 - .../Performance/PerformanceChartGraph.tsx | 71 -- .../Performance/PerformanceChartHeader.tsx | 29 - src/components/ReusableComponents/Select.tsx | 2 +- src/components/SearchBar.tsx | 49 -- src/components/SearchResultItem.tsx | 20 - src/components/StatCard.tsx | 25 - .../application/ApplicationActions.tsx | 38 + .../application/ApplicationFilter.tsx | 65 ++ src/components/application/ApplicationRow.tsx | 49 ++ .../application/ApplicationTable.tsx | 51 ++ src/components/application/WarningModal.tsx | 40 ++ src/components/application/statusHelper.ts | 32 + src/components/form/ForgotPasswordForm.tsx | 69 -- src/components/form/RegisterForm.tsx | 2 +- src/components/form/ResetPasswordForm.tsx | 56 -- src/components/form/SignInForm.tsx | 23 +- src/components/form/SuccessMessage.tsx | 28 - src/components/form/useForgotPassword.ts | 30 - src/components/form/useResetPassword.tsx | 112 --- src/components/iconss/SvgIcons.tsx | 73 -- src/components/search/applicationcycles.tsx | 32 - src/components/search/cohorts.tsx | 40 -- src/components/search/jobs.tsx | 36 - src/components/search/programs.tsx | 40 -- src/components/search/roles.tsx | 36 - src/components/search/trainees.tsx | 44 -- src/components/search/users.tsx | 40 -- src/components/sidebar/Header.tsx | 61 -- src/components/sidebar/navHeader.tsx | 8 +- src/components/sidebar/sidebarItems.tsx | 13 +- src/components/validation/forgetvalidation.ts | 7 - src/components/validation/resetvalidation.ts | 10 - src/hooks/useDashboardData.ts | 76 -- src/index.css | 5 +- src/pages/Applicant/ApplicantDashboard.tsx | 295 -------- .../ApplicationForms/UpdateSavedForm.tsx | 2 +- src/pages/Applications.tsx | 378 +++++----- .../Applications/AdminViewApplications.tsx | 379 ++++------ .../Applications/ViewSingleApplication.tsx | 429 +++-------- src/pages/Dashboard.tsx | 67 +- src/pages/JobPost/applicantJobFiltering.tsx | 500 ------------- src/pages/JobPost/fiterJopPost.tsx | 676 ------------------ src/pages/JobPost/job.tsx | 140 +--- src/pages/JobPost/jobTypes.ts | 55 -- src/pages/JobPost/updateJobPost.tsx | 398 ++++++----- src/pages/ResetPasswordPage.tsx | 41 -- src/pages/TraineApplicant/Trainee.tsx | 479 ++++++------- src/pages/forgetpassword.tsx | 27 - src/pages/programs/Programs.tsx | 160 +---- src/pages/programs/filterPrograms.tsx | 642 ----------------- .../roles&permissions/RolePermission.tsx | 12 +- .../roles&permissions/filterRolesAccess.tsx | 628 ---------------- src/pages/search.tsx | 86 --- src/pages/sharedPosts.tsx | 135 ++-- src/pages/verifyEmail.tsx | 58 -- src/redux/actions/TraineeAction.ts | 48 +- src/redux/actions/adminListApplications.ts | 119 ++- src/redux/actions/applications.ts | 11 +- src/redux/actions/attendanceAction.ts | 31 - src/redux/actions/cohortActions.ts | 71 +- src/redux/actions/fetchJobPost.ts | 83 ++- src/redux/actions/fetchProgramsAction.ts | 6 +- src/redux/actions/fetchSearchDataAction.ts | 41 -- src/redux/actions/fetchSingleJobPostAction.ts | 5 - src/redux/actions/filterJobPost.ts | 122 ---- src/redux/actions/filterProgramActions.ts | 112 --- src/redux/actions/filterRolesAccess.ts | 111 --- src/redux/actions/filterTraineeActions.ts | 5 +- src/redux/actions/login.ts | 6 - src/redux/actions/performanceAction.ts | 32 - src/redux/actions/programsActions.ts | 5 +- src/redux/actions/updateJobPostAction.ts | 10 +- src/redux/actions/users.ts | 37 +- src/redux/actions/verifyEmailAction.ts | 35 - src/redux/actiontypes/TraineeType.ts | 33 - src/redux/actiontypes/attendanceTypes.ts | 10 - src/redux/actiontypes/cohortTypes.ts | 14 - src/redux/actiontypes/deleteactiontype.ts | 49 -- .../actiontypes/fetchSearchDataActionTypes.ts | 24 - src/redux/actiontypes/performanceTypes.ts | 10 - src/redux/index.ts | 7 - src/redux/reducers/applicationReducer.ts | 11 +- src/redux/reducers/attendanceReducer.ts | 30 - src/redux/reducers/cohortReducer.ts | 11 +- src/redux/reducers/fetchSearchDataReducer.ts | 51 -- src/redux/reducers/filterJobPostReducer.ts | 20 - src/redux/reducers/filterProgramsReducer.ts | 20 - src/redux/reducers/filterRoleReducer.ts | 20 - src/redux/reducers/index.ts | 13 - src/redux/reducers/performanceReducer.ts | 30 - src/redux/reducers/traineeReducer.ts | 23 +- src/routes/routes.tsx | 64 +- 115 files changed, 1842 insertions(+), 7538 deletions(-) delete mode 100644 src/assets/Group (1).svg delete mode 100644 src/assets/Group.svg delete mode 100644 src/assets/Vector.svg delete mode 100644 src/assets/assets/calendar.svg delete mode 100644 src/assets/assets/collaborative-learning.svg delete mode 100644 src/assets/assets/performance.svg delete mode 100644 src/assets/assets/strategy.svg delete mode 100644 src/assets/material-symbols_manage-accounts.svg delete mode 100644 src/components/GoogleForm/DeleteConfirmationModal.tsx delete mode 100644 src/components/GoogleForm/InputField.tsx delete mode 100644 src/components/GoogleForm/SelectField.tsx delete mode 100644 src/components/GoogleForm/ViewApplicationModal.tsx delete mode 100644 src/components/GoogleForm/customModal.tsx delete mode 100644 src/components/Performance/PerformanceChart.tsx delete mode 100644 src/components/Performance/PerformanceChartGraph.tsx delete mode 100644 src/components/Performance/PerformanceChartHeader.tsx delete mode 100644 src/components/SearchBar.tsx delete mode 100644 src/components/SearchResultItem.tsx delete mode 100644 src/components/StatCard.tsx create mode 100644 src/components/application/ApplicationActions.tsx create mode 100644 src/components/application/ApplicationFilter.tsx create mode 100644 src/components/application/ApplicationRow.tsx create mode 100644 src/components/application/ApplicationTable.tsx create mode 100644 src/components/application/WarningModal.tsx create mode 100644 src/components/application/statusHelper.ts delete mode 100644 src/components/form/ForgotPasswordForm.tsx delete mode 100644 src/components/form/ResetPasswordForm.tsx delete mode 100644 src/components/form/SuccessMessage.tsx delete mode 100644 src/components/form/useForgotPassword.ts delete mode 100644 src/components/form/useResetPassword.tsx delete mode 100644 src/components/iconss/SvgIcons.tsx delete mode 100644 src/components/search/applicationcycles.tsx delete mode 100644 src/components/search/cohorts.tsx delete mode 100644 src/components/search/jobs.tsx delete mode 100644 src/components/search/programs.tsx delete mode 100644 src/components/search/roles.tsx delete mode 100644 src/components/search/trainees.tsx delete mode 100644 src/components/search/users.tsx delete mode 100644 src/components/sidebar/Header.tsx mode change 100644 => 100755 src/components/sidebar/navHeader.tsx delete mode 100644 src/components/validation/forgetvalidation.ts delete mode 100644 src/components/validation/resetvalidation.ts delete mode 100644 src/hooks/useDashboardData.ts delete mode 100644 src/pages/Applicant/ApplicantDashboard.tsx delete mode 100644 src/pages/JobPost/applicantJobFiltering.tsx delete mode 100644 src/pages/JobPost/fiterJopPost.tsx delete mode 100644 src/pages/JobPost/jobTypes.ts delete mode 100644 src/pages/ResetPasswordPage.tsx delete mode 100644 src/pages/forgetpassword.tsx delete mode 100644 src/pages/programs/filterPrograms.tsx delete mode 100644 src/pages/roles&permissions/filterRolesAccess.tsx delete mode 100644 src/pages/search.tsx delete mode 100644 src/pages/verifyEmail.tsx delete mode 100644 src/redux/actions/attendanceAction.ts delete mode 100644 src/redux/actions/fetchSearchDataAction.ts delete mode 100644 src/redux/actions/filterJobPost.ts delete mode 100644 src/redux/actions/filterProgramActions.ts delete mode 100644 src/redux/actions/filterRolesAccess.ts delete mode 100644 src/redux/actions/performanceAction.ts delete mode 100644 src/redux/actions/verifyEmailAction.ts delete mode 100644 src/redux/actiontypes/attendanceTypes.ts delete mode 100644 src/redux/actiontypes/cohortTypes.ts delete mode 100644 src/redux/actiontypes/fetchSearchDataActionTypes.ts delete mode 100644 src/redux/actiontypes/performanceTypes.ts delete mode 100644 src/redux/reducers/attendanceReducer.ts delete mode 100644 src/redux/reducers/fetchSearchDataReducer.ts delete mode 100644 src/redux/reducers/filterJobPostReducer.ts delete mode 100644 src/redux/reducers/filterProgramsReducer.ts delete mode 100644 src/redux/reducers/filterRoleReducer.ts delete mode 100644 src/redux/reducers/performanceReducer.ts diff --git a/.DS_Store b/.DS_Store index 8ca29f8281410802697c5685b7636d44d7b11d1a..4ad0e4cad0b06753fe66b70d59f0486eab204822 100644 GIT binary patch delta 92 zcmZp1XmOa}&&avCT>XhD?+5g|Zm8PfidhR1>eRHZwHTQ7|#7)lsN6 vu`mHKO=@d7IYgE9t%KsTb8_?YJ174TkZ0_gJW2TGWt%$wOI{;~rA;6of# delta 78 zcmV-U0I~msK!iY$PXQ3IP`eKS5R*O-7PBG{F#?k(5)BM?Q*v%>Z*FG*0004#P!esE kUl4T&F)%bNAT%*BliUzLlXw%>vveGg1Cf9Xvj-Uc2Xb>4bpQYW diff --git a/.codeclimate.yml b/.codeclimate.yml index 1f904f18f..5ca2477db 100755 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -11,11 +11,11 @@ checks: file-lines: enabled: true config: - threshold: 25000 + threshold: 250 method-complexity: enabled: true config: - threshold: 50 + threshold: 5 method-count: enabled: true config: @@ -23,7 +23,7 @@ checks: method-lines: enabled: true config: - threshold: 900 + threshold: 90 nested-control-flow: enabled: true config: @@ -41,20 +41,20 @@ checks: config: threshold: #language-specific defaults. overrides affect all languages. plugins: - rubocop: - enabled: true - eslint: - enabled: false - channel: eslint-8 + rubocop: + enabled: true + eslint: + enabled: false + channel: eslint-8 exclude_patterns: - - "config/" - - "db/" - - "dist/" - - "features/" - - "**/node_modules/" - - "script/" - - "**/spec/" - - "**/test/" - - "**/tests/" - - "**/vendor/" - - "**/*.d.ts" +- "config/" +- "db/" +- "dist/" +- "features/" +- "**/node_modules/" +- "script/" +- "**/spec/" +- "**/test/" +- "**/tests/" +- "**/vendor/" +- "**/*.d.ts" \ No newline at end of file diff --git a/.gitignore b/.gitignore index dbc7ea49f..db5330e1c 100755 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,5 @@ coverage dist buildcoverage package-lock.json -yarn.lock .DS_Store -build/ -yarn.lock +build/ \ No newline at end of file diff --git a/package.json b/package.json index f14e538fc..4358a2787 100755 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@babel/preset-env": "^7.19.3", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", - "@expo/webpack-config": "^19.0.1", + "@expo/webpack-config": "^0.17.2", "@iconify/react": "^4.0.0", "@testing-library/dom": "^8.19.0", "@testing-library/jest-dom": "^5.16.5", @@ -53,7 +53,7 @@ "html-webpack-plugin": "^5.6.0", "i": "^0.3.7", "node-polyfill-webpack-plugin": "^2.0.1", - "npm": "^10.8.3", + "npm": "^8.19.2", "postcss": "^8.4.14", "postcss-loader": "^7.0.0", "process": "^0.11.10", @@ -83,11 +83,10 @@ "@mui/x-date-pickers": "^5.0.6", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", - "@tinymce/tinymce-react": "^5.1.1", + "@tinymce/tinymce-react": "^4.2.0", "@types/react-i18next": "^8.1.0", "@types/react-router": "^5.1.19", "@types/react-router-dom": "^5.3.3", - "antd": "^5.21.2", "axios": "^1.1.2", "bootstrap": "^5.2.2", "browser": "^0.2.6", @@ -120,18 +119,16 @@ "react-hot-toast": "^2.4.1", "react-i18next": "^11.18.6", "react-icons": "^4.6.0", - "react-js-pagination": "^3.0.2", + "react-js-pagination": "^3.0.3", "react-loader-spinner": "^6.1.6", - "react-modal": "^3.16.1", "react-paginate": "^8.1.3", "react-redux": "^8.0.4", "react-render-html": "^0.6.0", "react-router-dom": "^6.4.2", - "react-scripts": "^3.0.1", + "react-scripts": "^5.0.1", "react-select": "^5.7.4", "react-table": "^7.8.0", "react-toastify": "^9.0.8", - "recharts": "^2.12.7", "redux": "^4.2.0", "redux-devtools-extension": "^2.13.9", "redux-state-sync": "^3.1.4", diff --git a/src/.DS_Store b/src/.DS_Store index 5207b9bb68aa4b41ef3dd59dccbebe824d285c51..84ea2a30ffb4c60e84eb68d0298c7624ea529193 100644 GIT binary patch delta 163 zcmZokXbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~3gIoUvmMH0wo5CvjJpg03V2}3bM zg=bEFa#GG_LCIfilNFd&Zf56T;b0V<%%kv{-O@xy!O+rp@;?Qi$-L^0lLZ-XO%`C3 y*sLPZ#Vg1RG7D%0H;`}zS+iM?={xgeejUrn_B;|COb{1nOis|5xp{>UGcy3to*(`I delta 1968 zcmeH{U2GIp6vyv>cH6x(E%a_bcS;8rTS~vETWFC2V(GTDLYpGA_JfZ)+a2h{?d-?Q zF4V>>O4S!bXa=JsN>nsH3D!m)j2h7w;s+0=4^|T&#Q1`VJ{TW-5bt!hO=EmfUwrUR z?wothoOAD)dw%!M9-4hLNC-h^I@&@=9UoHO9mxv0nJdA9x!@MX<2dnN!8;!_LZ(Q9 z#EC&PV$3D4*r6J4p5x%PzbFgXulYZb)f5$}IL-nyGcBRkreJGJ=!vG_O#8O^q$hvD zN4*q9+V8#(I(d#UJ0)5t`8YKo_C=9caZQ_60fa2D@VO=}>b z8LMga^zv^v$IMAJr45`mv{Wvpr;pNcRXUz2ahx8yeYk^Dk_gBN-5fx?e$B{rZ68_|eXv|$H2aS(mz#{dR# z949ai6(6E{^+JQtC^QLe!Y(1~WH@uzU`ltJW)x3KKZ+uG*}GBK zPPVJ>k@iZAi2P)^`aUJBFbC6rK+8ONk9@>wS>&|*K<<#A$z4v!dIUHewWvcqny{4v zlHG}R?8Y8+qX)e>zLWxRq{ zaRsk0v$KHr-J-9ea9{-jSK@=UDA@b`-S(UE^(t=6a~ diff --git a/src/assets/Group (1).svg b/src/assets/Group (1).svg deleted file mode 100644 index 4af498f96..000000000 --- a/src/assets/Group (1).svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/Group.svg b/src/assets/Group.svg deleted file mode 100644 index bc71b635c..000000000 --- a/src/assets/Group.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/Vector.svg b/src/assets/Vector.svg deleted file mode 100644 index a8b0d3e49..000000000 --- a/src/assets/Vector.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/assets/calendar.svg b/src/assets/assets/calendar.svg deleted file mode 100644 index 7856f1d1f..000000000 --- a/src/assets/assets/calendar.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/assets/collaborative-learning.svg b/src/assets/assets/collaborative-learning.svg deleted file mode 100644 index 467a7d6b8..000000000 --- a/src/assets/assets/collaborative-learning.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/assets/performance.svg b/src/assets/assets/performance.svg deleted file mode 100644 index 0223d35cc..000000000 --- a/src/assets/assets/performance.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/assets/strategy.svg b/src/assets/assets/strategy.svg deleted file mode 100644 index 8df00f27a..000000000 --- a/src/assets/assets/strategy.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/material-symbols_manage-accounts.svg b/src/assets/material-symbols_manage-accounts.svg deleted file mode 100644 index 8961f142b..000000000 --- a/src/assets/material-symbols_manage-accounts.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/GoogleForm/ApplicationForm.tsx b/src/components/GoogleForm/ApplicationForm.tsx index 0f438c276..bd3406c7c 100644 --- a/src/components/GoogleForm/ApplicationForm.tsx +++ b/src/components/GoogleForm/ApplicationForm.tsx @@ -10,9 +10,9 @@ const ApplicationForm = () => { }; return ( -
-
-
+
+
+
- -
-
-
-
-
- ); -}; - -export default DeleteConfirmationModal; diff --git a/src/components/GoogleForm/InputField.tsx b/src/components/GoogleForm/InputField.tsx deleted file mode 100644 index bdf44dc8d..000000000 --- a/src/components/GoogleForm/InputField.tsx +++ /dev/null @@ -1,47 +0,0 @@ -interface InputFieldProps { - id: string; - name: string; - label: string; - type?: string; - value: string; - onChange: (e: React.ChangeEvent) => void; - onBlur: (e: React.FocusEvent) => void; - error?: string; - touched?: boolean; - classname?: string -} - -const InputField = ({ - id, - name, - label, - type = 'text', - value, - onChange, - onBlur, - error, - touched, - classname -}: InputFieldProps) => ( -
- -
- - {touched && error &&
{error}
} -
-
-); - -export default InputField; diff --git a/src/components/GoogleForm/RecentForms.tsx b/src/components/GoogleForm/RecentForms.tsx index 1a4c05305..3ec2945d3 100644 --- a/src/components/GoogleForm/RecentForms.tsx +++ b/src/components/GoogleForm/RecentForms.tsx @@ -2,14 +2,8 @@ import { useEffect, useState } from 'react'; import axios from '../../redux/actions/axiosconfig'; import * as icons from 'react-icons/ai'; import { Link } from 'react-router-dom'; -import CustomModal from './customModal'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import { toast } from 'react-toastify'; -import ViewApplicationModal from './ViewApplicationModal'; -import DeleteConfirmationModal from './DeleteConfirmationModal'; -export interface Application { +interface Application { id: string; link: string; title: string; @@ -20,19 +14,16 @@ export interface Application { const RecentForms = () => { const [applications, setApplications] = useState([]); const [loading, setLoading] = useState(true); - const [updateLoading, setUpdateLoading] = useState(false) - const [jobposts, setJobPosts] = useState([]); + const [jobposts, setjobposts] = useState([]); const [error, setError] = useState(''); - const [viewApplicationModalIsOpen, setViewApplicationModalIsOpen] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); - const [applicationToDelete, setApplicationToDelete] = useState(null); - const [modalIsOpen, setIsOpen] = useState(false); - const [selectedApplication, setSelectedApplication] = useState(null); - const [viewedApplication, setViewedApplication] = useState(null); + const [applicationToDelete, setApplicationToDelete] = useState( + null + ); useEffect(() => { - const fetchJobPosts = async () => { + const fetchjobposts = async () => { const graphqlQuery = ` query GetAllJobApplication($input: pagination) { getAllJobApplication(input: $input) { @@ -58,7 +49,7 @@ const RecentForms = () => { } if (response.data.data && response.data.data.getAllJobApplication) { - setJobPosts(response.data.data.getAllJobApplication); + setjobposts(response.data.data.getAllJobApplication); } } catch (error: any) { console.error('An error occurred:', error); @@ -66,40 +57,38 @@ const RecentForms = () => { } }; - fetchJobPosts(); + fetchjobposts(); }, []); useEffect(() => { - const fetchApplications = async () => { - try { - const response = await axios.post(`${process.env.BACKEND_URL}`, { - query: ` - query { - getAllApplications { - id - link - title - jobpost - description - } + axios + .post(`${process.env.BACKEND_URL}`, { + query: ` + query { + getAllApplications { + id + link + title + jobpost + description } - `, - }); + } + `, + }) + .then((response) => { const data = response.data?.data?.getAllApplications ?? []; setApplications(data); console.log(response); + setLoading(false); - } catch (error) { + }) + .catch((error) => { console.error('Error fetching applications:', error); setLoading(false); - } - }; - - fetchApplications(); + }); }, []); - - const handleDelete= async (id: string) : Promise => { + const handleDelete = async (id: string) => { try { await axios.post(`${process.env.BACKEND_URL}`, { query: ` @@ -121,36 +110,6 @@ const RecentForms = () => { } }; - const openViewApplicationModal = async (id: string) => { - try { - const response = await axios.post(`${process.env.BACKEND_URL}`, { - query: ` - query GetApplication($id: ID!) { - getApplication(id: $id) { - id - link - title - jobpost - description - } - } - `, - variables: { id }, - }); - - const applicationData = response.data?.data?.getApplication; - setViewedApplication(applicationData); - setViewApplicationModalIsOpen(true); - } catch (error) { - console.error('Error fetching application:', error); - } - }; - - const closeViewApplicationModal = () => { - setViewedApplication(null); - setViewApplicationModalIsOpen(false); - }; - const showDeleteConfirmation = (id: string) => { setApplicationToDelete(id); setShowDeleteModal(true); @@ -162,7 +121,9 @@ const RecentForms = () => { }; function findJobPostTitle(application: Application) { - const jobpostObject = jobposts.find((jobpost) => jobpost.id === application.jobpost); + const jobpostObject = jobposts.find( + (jobpost) => jobpost.id === application.jobpost + ); return jobpostObject ? jobpostObject.title : 'Loading...'; } @@ -173,63 +134,63 @@ const RecentForms = () => {
-
-

- Application Forms +
+

+ Saved Application Forms

-
+
{!loading && ( -
    +
      {reversedApplications.map((application) => (
    • -
      -

      + className='col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg bg-white text-center shadow'> +
      +

      {application.title}

      -
      -
      - {application.description.length > 155 - ? `${application.description.substring(0, 155)}...` - : application.description} +
      +
      + {application.description}
      - + {findJobPostTitle(application)}
      -
      - -
      + -
      +
      @@ -241,22 +202,55 @@ const RecentForms = () => { )}
      {showDeleteModal && ( - handleDelete(applicationToDelete!)} - /> +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      + Delete the Form +
      +
      +

      + Are you sure you want to delete this item? All of + the form data will be permanently removed. This + action cannot be undone. +

      +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      )}
      -
      ); }; -export default RecentForms; \ No newline at end of file +export default RecentForms; diff --git a/src/components/GoogleForm/SaveForm.tsx b/src/components/GoogleForm/SaveForm.tsx index 9b47f3472..232f0b568 100644 --- a/src/components/GoogleForm/SaveForm.tsx +++ b/src/components/GoogleForm/SaveForm.tsx @@ -3,8 +3,7 @@ import axios from '../../redux/actions/axiosconfig'; import { showSuccessToast, showErrorToast } from './../../utils/toast'; import { useFormik } from 'formik'; import * as Yup from 'yup'; -import InputField from './InputField'; -import SelectField from './SelectField'; +import SelectField from "../ReusableComponents/Select" const validationSchema = Yup.object().shape({ link: Yup.string().required('Please enter a Google Form link'), @@ -17,10 +16,10 @@ function SaveFormDetails() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); - const [jobposts, setJobposts] = useState([]); + const [jobposts, setjobposts] = useState([]); useEffect(() => { - const fetchJobPosts = async () => { + const fetchjobposts = async () => { const graphqlQuery = ` query GetAllJobApplication($input: pagination) { getAllJobApplication(input: $input) { @@ -29,29 +28,45 @@ function SaveFormDetails() { } } `; + try { const response = await axios.post('/', { query: graphqlQuery, - variables: { input: { All: true, page: 1 } }, + variables: { + input: { + All: true, + page: 1, + }, + }, }); - if (response.data.data) { - setJobposts(response.data.data.getAllJobApplication); - } else { + + if (response.data.errors) { throw new Error(response.data.errors[0].message); } - } catch (err: any) { - console.error('Error fetching job posts:', err); - setError(err.message); + + if (response.data.data && response.data.data.getAllJobApplication) { + setjobposts(response.data.data.getAllJobApplication); + } + } catch (error: any) { + console.error('An error occurred:', error); + setError(`Error fetching job posts: ${error}`); } }; - fetchJobPosts(); + + fetchjobposts(); }, []); const formik = useFormik({ - initialValues: { link: '', title: '', jobpost: '', description: '' }, + initialValues: { + link: '', + title: '', + jobpost: '', + description: '', + }, validationSchema, onSubmit: async (values) => { setLoading(true); + const graphqlQuery = ` mutation CreateApplication($link: String!, $title: String!, $jobpost: String!, $description: String!) { createApplication(link: $link, title: $title, jobpost: $jobpost, description: $description) { @@ -61,103 +76,211 @@ function SaveFormDetails() { jobpost description } - }`; + } + `; + + const variables = { + link: values.link, + title: values.title, + jobpost: values.jobpost, + description: values.description, + }; try { const response = await axios.post('/', { query: graphqlQuery, - variables: values, + variables, }); + if (response.data.errors) { - const errorMessage = response.data.errors[0].error; - if (errorMessage.toLowerCase().includes("a record with link")) - showErrorToast('The link is already in use'); - } else { + throw new Error(response.data.errors[0].message); + } + + if (response.data.data && response.data.data.createApplication) { setSuccess(true); - showSuccessToast('Application created successfully!'); + + console.log('Application created successfully'); formik.resetForm(); - window.location.href = '/#/admin/view-forms'; + showSuccessToast('Application created successfully!'); + } else { + setError( + `Error creating application: ${response.data.errors[0].message}` + ); + } + window.location.href = '/#/view-forms'; + } catch (error: any) { + console.error('An error occurred:', error); + if (error.response) { + if (error.response.status === 403) { + setError('You do not have permission to perform this action'); + } else if (error.response.status === 400) { + setError('Invalid request'); + } else if (error.response.status === 500) { + setError('Server error'); + } else { + setError('An error occurred while submitting the form.'); + } + } else { + setError(`Error creating application: ${error}`); } - } catch (err: any) { - console.error('Error submitting form:', err); - showErrorToast('An error occurred'); } finally { setLoading(false); + if (success) { + showSuccessToast('Application created successfully!'); + } else if (error) { + showErrorToast(error); + } } }, }); return ( -
      -
      -

      - Save Application Form -

      -

      - Copy Google Form URL and paste it in the input field below. -

      - -
      - - ({ value: job.id, label: job.title }))]} - touched={formik.touched.jobpost} - error={formik.errors.jobpost} - /> - -
      - -