From b37dc089a48fa957f2d56a9d4caa84a061668807 Mon Sep 17 00:00:00 2001 From: "Gisa M. Caleb Pacifique" Date: Mon, 11 Nov 2024 12:57:10 +0200 Subject: [PATCH] fix-coordinators-table --- src/Mutations/User.tsx | 11 + src/components/DataTable.tsx | 2 +- src/components/DropOrUndropUser.tsx | 120 ++++ src/components/ProgramUsersModal.tsx | 28 +- .../admin-dashBoard/CoordinatorModal.tsx | 551 +++++++++++++++++- src/containers/admin-dashBoard/TtlsModal.tsx | 451 +++++++------- src/queries/manageStudent.queries.tsx | 3 + 7 files changed, 900 insertions(+), 266 deletions(-) create mode 100644 src/components/DropOrUndropUser.tsx diff --git a/src/Mutations/User.tsx b/src/Mutations/User.tsx index d8276d8d8..f275fcbbd 100644 --- a/src/Mutations/User.tsx +++ b/src/Mutations/User.tsx @@ -6,6 +6,17 @@ export const DROP_TTL_USER = gql` } `; +export const DROP_COORDINATOR = gql` + mutation DropCordinator($id: String!, $reason: String!) { + dropCordinator(id: $id, reason: $reason) + } +`; +export const UNDROP_COORDINATOR = gql` + mutation UndropCordinator($id: String!) { + undropCordinator(id: $id) + } +`; + export const UNDROP_TTL_USER = gql` mutation UnDropTTLUser($email: String!) { undropTTLUser(email: $email) diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx index 1b222d9bd..f015f35e6 100644 --- a/src/components/DataTable.tsx +++ b/src/components/DataTable.tsx @@ -91,7 +91,7 @@ function DataTable({ data, columns, title, loading, className }: TableData) { > {headerGroups.map((headerGroup) => ( - + {headerGroup.headers.map((column) => ( >; + onSubmit: (data: React.MouseEvent) => void; + onClose: () => void; +} + +function DropOrUndropUser({ + subject, + title, + drop, + loading, + setRemovalReason, + onSubmit, + onClose, +}: DropOrUndropUserProps) { + const { t } = useTranslation(); + const [reason, setReason] = useState(''); + + const handleConfirm = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onSubmit) onSubmit(e); + }; + + return ( +
+
+
+

+ {t(subject)} +

+
+
+
+
+
+

+ {t(title)} +

+
+ {/* Reason input field */} + {drop && ( +
+ ) => + setReason(e.target.value) + } + id="removalReason" + /> +

+ Reason is required! +

+
+ )} + +
+ + +
+
+
+
+
+ ); +} + +export default DropOrUndropUser; diff --git a/src/components/ProgramUsersModal.tsx b/src/components/ProgramUsersModal.tsx index 64c47b1f2..dbdd1a78f 100644 --- a/src/components/ProgramUsersModal.tsx +++ b/src/components/ProgramUsersModal.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { gql, useQuery } from '@apollo/client'; -import DataTable from './DataTable'; import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import { styled } from '@mui/material/styles'; +import DataTable from './DataTable'; interface User { email: string; @@ -70,7 +70,7 @@ export function ProgramUsersModal({ isOpen, onClose, // defaultProgram = 'default', - programName + programName, }: ProgramUsersModalProps) { const { data, loading, error } = useQuery(GET_ALL_USERS, { variables: { @@ -79,10 +79,11 @@ export function ProgramUsersModal({ skip: !isOpen, }); - const programUsers = data?.getAllUsers.filter( - (user: User) => user.team?.cohort?.program?.name === programName + const programUsers = + data?.getAllUsers.filter( + (user: User) => user.team?.cohort?.program?.name === programName, // || (user.team === null && programName === defaultProgram) - ) || []; + ) || []; const columns = [ { @@ -91,7 +92,11 @@ export function ProgramUsersModal({ Cell: ({ value }: CellProps) => (
- + @@ -119,7 +124,7 @@ export function ProgramUsersModal({ if (loading) { return (
-
+
); } @@ -152,12 +157,7 @@ export function ProgramUsersModal({ }; return ( - +
{programName} - Users @@ -168,4 +168,4 @@ export function ProgramUsersModal({
); -} \ No newline at end of file +} diff --git a/src/containers/admin-dashBoard/CoordinatorModal.tsx b/src/containers/admin-dashBoard/CoordinatorModal.tsx index 9ed47d13d..fc0937127 100644 --- a/src/containers/admin-dashBoard/CoordinatorModal.tsx +++ b/src/containers/admin-dashBoard/CoordinatorModal.tsx @@ -1,16 +1,33 @@ -import React, { useState, useEffect } from 'react'; -import { gql, useQuery } from '@apollo/client'; +import React, { useState, useEffect, useMemo } from 'react'; +import { gql, useMutation, useQuery } from '@apollo/client'; import { useTranslation } from 'react-i18next'; -import DataTable from '../../components/DataTable'; -import Spinner from '../../components/Spinner'; +import { Icon } from '@iconify/react'; +import Draggable from 'react-draggable'; +import Dialog from '@mui/material/Dialog'; +import Paper, { PaperProps } from '@mui/material/Paper'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import { toast } from 'react-toastify'; +import Avatar from '../../assets/avatar.png'; +import { GET_COHORTS_QUERY } from '../../queries/manageStudent.queries'; +import ControlledSelect from '../../components/ControlledSelect'; +import Button from '../../components/Buttons'; import TtlSkeleton from '../../Skeletons/ttl.skeleton'; +import DataTable from '../../components/DataTable'; +import { DROP_COORDINATOR, UNDROP_COORDINATOR } from '../../Mutations/User'; +import DropOrUndropUser from '../../components/DropOrUndropUser'; const GET_COORDINATORS = gql` query Query($orgToken: String) { getAllCoordinators(orgToken: $orgToken) { + id email profile { name + avatar + } + status { + status } organizations role @@ -18,40 +35,237 @@ const GET_COORDINATORS = gql` } `; +const GIVE_COORDINATOR_COHORT = gql` + mutation GiveCoordinatorCohort($coordinatorId: String!, $cohortId: String!) { + giveCoordinatorCohort(coordinatorId: $coordinatorId, cohortId: $cohortId) + } +`; + +interface StatusType { + status: string; +} + +interface Cohort { + name: string; + coordinator: any; + id: string; +} interface Coordinator { - email: string; - profile: { + id?: string; + email?: string; + profile?: { name: string | null; + avatar?: string; }; - organizations: string[]; - role: string; + status: StatusType; + organizations?: string[]; + role?: string; + cohorts?: Cohort[]; } export default function CoordinatorsPage() { const { t } = useTranslation(); const orgToken = localStorage.getItem('orgToken'); - const { loading, error, data } = useQuery(GET_COORDINATORS, { + const { + loading, + data, + refetch: refetchCoordinators, + } = useQuery(GET_COORDINATORS, { variables: { orgToken, }, - pollInterval: 3000, // Refresh every 3 seconds + pollInterval: 3000, }); const [coordinators, setCoordinators] = useState([]); + const [cohorts, setCohorts] = useState([]); + const [viewCoordinator, setViewCoordinator] = useState( + null, + ); + const cohortOptions: any = []; + const [coordinatorModle, setCoordinatorModle] = useState(false); + const [unAssignedCohorts, setUnAssignedCohorts] = useState([]); + const [editModel, setEditModel] = useState(false); + const [removalReason, setRemovalReason] = useState(''); + const [dropModle, setDropModel] = useState(false); + const [undropModle, setUndropModel] = useState(false); + const [coordinatorId, setCoordinatorId] = useState(null); + const [cohortId, setCohortId] = useState(''); + + const { data: cohortsData, refetch: refetchCohorts } = useQuery( + GET_COHORTS_QUERY, + { + variables: { + orgToken, + }, + }, + ); + + const [giveCoordinatorCohort, { loading: givingLoading }] = useMutation( + GIVE_COORDINATOR_COHORT, + ); + + const [dropCordinator, { loading: dropLoading }] = + useMutation(DROP_COORDINATOR); + + const [undropCordinator, { loading: undropLoading }] = + useMutation(UNDROP_COORDINATOR); + + useEffect(() => { + if (cohortsData) { + setCohorts(cohortsData.getCohorts); + } + }, [cohortsData]); useEffect(() => { - if (data) { + if (data && cohorts.length > 0) { const extractedCoordinators = data.getAllCoordinators.map( - (coordinator: any) => ({ - email: coordinator.email, - profile: coordinator.profile || { name: null }, - organizations: coordinator.organizations || [], - role: coordinator.role, - }), + (coordinator: any) => { + const coordinatorCohorts = cohorts.filter( + (cohort) => + cohort.coordinator && cohort.coordinator.id === coordinator.id, + ); + return { + email: coordinator.email, + id: coordinator.id, + status: coordinator.status, + profile: coordinator.profile || { name: null }, + organizations: coordinator.organizations || [], + role: coordinator.role, + cohorts: coordinatorCohorts, + }; + }, ); - setCoordinators(extractedCoordinators); } - }, [data]); + }, [data, cohorts]); + + function PaperComponent(props: PaperProps) { + return ( + + + + ); + } + + const handleEdit = (id: string) => { + const unAssgnedCohorts = cohorts.filter((cohort, index) => { + if (cohort.coordinator == null) { + return true; + } + return cohort.coordinator.id !== id; + }); + + setUnAssignedCohorts(unAssgnedCohorts); + setEditModel(true); + }; + + useEffect(() => { + if (unAssignedCohorts.length > 0) { + unAssignedCohorts.forEach((cohort: any, index: any) => { + cohortOptions[index] = {}; + cohortOptions[index].value = cohort.id; + cohortOptions[index].label = cohort.name; + }); + } + }, [unAssignedCohorts]); + + const handleCloseDropModle = () => { + setDropModel(false); + }; + const handleCloseUnropModle = () => { + setUndropModel(false); + }; + + const handleViewCoordinator = (id: string) => { + const coordinatorToView = coordinators.find( + (coordinator) => coordinator.id === id, + ); + if (coordinatorToView) { + setViewCoordinator(coordinatorToView); + setCoordinatorModle(true); + } + }; + + const handleGiveCoordinatorCohort = () => { + if (!coordinatorId) return; + if (!cohortId) { + toast.error('First select cohort'); + return; + } + const coordinatorToGive = coordinators.find( + (coordinator) => coordinator.id === coordinatorId, + ); + + if ( + coordinatorToGive?.cohorts && + coordinatorToGive?.cohorts.length && + coordinatorToGive?.cohorts[0].id === cohortId + ) { + return; + } + giveCoordinatorCohort({ + variables: { + coordinatorId, + cohortId, + }, + }) + .then((response) => { + toast.success(response.data.giveCoordinatorCohort); + refetchCohorts(); + refetchCoordinators(); + setEditModel(false); + setCoordinatorId(null); + }) + .catch((error) => { + toast.error(error.message || 'An error occurred'); + }); + }; + + const handleDropCoordinator = (reason: string) => { + if (!coordinatorId) return; + dropCordinator({ + variables: { + id: coordinatorId, + reason, + }, + }) + .then((response) => { + toast.success('Coordinator Dropped Successfully'); + refetchCohorts(); + refetchCoordinators(); + handleCloseDropModle(); + setCoordinatorId(null); + }) + .catch((error) => { + toast.error(error.message || 'An error occurred'); + }); + }; + + const handleUndropCoordinator = () => { + if (!coordinatorId) return; + undropCordinator({ + variables: { + id: coordinatorId, + }, + }) + .then((response) => { + toast.success('Coordinator Undropped Successfully'); + refetchCohorts(); + refetchCoordinators(); + handleCloseUnropModle(); + setCoordinatorId(null); + }) + .catch((error) => { + toast.error(error.message || 'An error occurred'); + }); + }; + + const handleClose = () => { + setCoordinatorModle(false); + }; const columns = [ { @@ -60,8 +274,90 @@ export default function CoordinatorsPage() { Cell: ({ value }: any) => value || '-', }, { Header: t('Email'), accessor: 'email' }, - { Header: t('Organizations'), accessor: 'organizations' }, - { Header: t('Role'), accessor: 'role' }, + { + Header: t('Cohorts'), + accessor: 'cohorts', + Cell: ({ value }: any) => ( +
+ {value && value.length > 0 ? ( + value.map((cohort: Cohort) => ( +
{cohort.name}
+ )) + ) : ( +
{t('Not assigned')}
+ )} +
+ ), + }, + { + Header: t('actions'), + accessor: '', + /* istanbul ignore next */ + Cell: useMemo( + () => + function ({ row }: any) { + return ( +
+ { + if (row.original.status?.status === 'active') { + setCoordinatorId(row.original.id); + handleEdit(row.original.id); + } else { + toast.error('This Coordinator is Dropped out'); + } + }} + /> + + {row.original.status?.status === 'active' ? ( + { + setCoordinatorId(row.original.id); + setDropModel(true); + }} + /> + ) : ( + { + setCoordinatorId(row.original.id); + setUndropModel(true); + }} + /> + )} + { + handleViewCoordinator(row.original.id); + }} + /> +
+ ); + }, + [coordinators], + ), + }, ]; return ( @@ -71,7 +367,7 @@ export default function CoordinatorsPage() {
{loading ? ( - + ) : ( )}
+
+ + + +
+
+ Logo +
+ +

+ {viewCoordinator && viewCoordinator.profile + ? viewCoordinator.profile.name + : 'Unavailable'} +

+ +
+ {' '} +

+ EMAIL{' '} +

+

+ + {' '} + {viewCoordinator ? viewCoordinator.email : 'Unavailable'} + +

+
+ +
+ {' '} +

+ COHORT{' '} +

+

+ + {' '} + {viewCoordinator?.cohorts && + viewCoordinator.cohorts.length > 0 + ? viewCoordinator?.cohorts.map((cohort) => ( +

+ {cohort.name} +
+ )) + : 'Not assigned'} + +

+
+ +
+ {' '} +

+ STATUS{' '} +

+

+ {viewCoordinator?.status?.status} +

+
+ + +
+
+
+
+
+ {editModel && ( +
+
+
+

+ {t('Edit Coordinator')} +

+
+
+
+
+
+

+ {t( + 'Choose a cohort to assign Coordinator from the dropdown below.', + )} +

+
+ +
+
+ { + setCohortId(e.value); + }, + }} + options={cohortOptions} + /> +
+
+ +
+ + +
+
+
+
+
+ )} + + {dropModle && ( + { + handleDropCoordinator(removalReason); + }} + /> + )} + {undropModle && ( + + )}
); } diff --git a/src/containers/admin-dashBoard/TtlsModal.tsx b/src/containers/admin-dashBoard/TtlsModal.tsx index 2f86d74c1..556c43d87 100644 --- a/src/containers/admin-dashBoard/TtlsModal.tsx +++ b/src/containers/admin-dashBoard/TtlsModal.tsx @@ -26,7 +26,7 @@ import { import ControlledSelect from '../../components/ControlledSelect'; import GitHubActivityChart from '../../components/chartGitHub'; import { toast } from 'react-toastify'; -import TtlSkeleton from '../../Skeletons/ttl.skeleton' +import TtlSkeleton from '../../Skeletons/ttl.skeleton'; /* istanbul ignore next */ export default function TtlsPage() { const { t } = useTranslation(); @@ -79,7 +79,8 @@ export default function TtlsPage() { const [isLoaded, setIsLoaded] = useState(false); const [gitHubStatistics, setGitHubStatistics] = useState({}); const [dropTTLUser, { loading: dropLoading }] = useMutation(DROP_TTL_USER); - const [undropTTLUser, { loading: undropLoading }] = useMutation(UNDROP_TTL_USER); + const [undropTTLUser, { loading: undropLoading }] = + useMutation(UNDROP_TTL_USER); function PaperComponent(props: PaperProps) { return ( { + const undropTTLMod = () => { let newState = !undropTTLModel; setUndropTTLModel(newState); }; @@ -223,57 +224,53 @@ export default function TtlsPage() { cursor="pointer" color="#9e85f5" /* istanbul ignore next */ - onClick={() => { - if (row.original.status?.status === "active") { - setSelectedOptionUpdate({ - value: row.original.team?.cohort?.name, - label: row.original.team?.cohort?.name, - }); - setSelectedTeamOptionUpdate({ - value: row.original?.team?.name, - label: row.original?.team?.name, - }); - removeEditModel(); - setEditEmail(row.original?.email); - setEditCohort(row.original.team?.cohort?.name); - setEditTeam(row.original.team?.name); - } - else { - toast.error("This TTL is Dropped out") - } - }} - /> - - {row.original.status?.status === "active" ? ( - { - - removeTraineeMod(); - setDeleteEmail(row.original?.email); - - }} - /> - ) : ( - { - console.log(row.original.status?.status); - - undropTTLMod(); - setDeleteEmail(row.original?.email); - - }} - /> - )} + onClick={() => { + if (row.original.status?.status === 'active') { + setSelectedOptionUpdate({ + value: row.original.team?.cohort?.name, + label: row.original.team?.cohort?.name, + }); + setSelectedTeamOptionUpdate({ + value: row.original?.team?.name, + label: row.original?.team?.name, + }); + removeEditModel(); + setEditEmail(row.original?.email); + setEditCohort(row.original.team?.cohort?.name); + setEditTeam(row.original.team?.name); + } else { + toast.error('This TTL is Dropped out'); + } + }} + /> + + {row.original.status?.status === 'active' ? ( + { + removeTraineeMod(); + setDeleteEmail(row.original?.email); + }} + /> + ) : ( + { + console.log(row.original.status?.status); + + undropTTLMod(); + setDeleteEmail(row.original?.email); + }} + /> + )} { setButtonLoading(true); setButtonLoading(true); - - if (editEmail) { - editMemberMutation(); - } else { - toast.error('Please select the trainee again '); - } - + + if (editEmail) { + editMemberMutation(); + } else { + toast.error('Please select the trainee again '); + } }} loading={buttonLoading} > @@ -506,7 +502,7 @@ export default function TtlsPage() {

{' '} - {traineeDetails.team!==undefined + {traineeDetails.team !== undefined ? traineeDetails.team.name : 'Not assigned'} @@ -554,10 +550,7 @@ export default function TtlsPage() { STATUS{' '}

- - {' '} - {traineeDetails.status?.status} - + {traineeDetails.status?.status}

@@ -592,10 +585,8 @@ export default function TtlsPage() { 'Unavailable' )}

- - + )} - - + className={`h-screen w-screen bg-black bg-opacity-30 backdrop-blur-sm fixed top-0 left-0 z-20 flex items-center justify-center px-4 ${ + removeTraineeModel === true ? 'block' : 'hidden' + }`} + > +
+
+

+ {t('Remove TTL')} +

+
+
+
+ +
+

+ {t('Are you sure you want to remove TTL from this cohort?')} +

+
+ {/* Reason input field */} +
+ ) => + setRemovalReason(e.target.value) + } + id="removalReason" + /> +

+ Reason is required! +

+
+
+ + +
+ +
- - - - + {/* =========================== End:: RemoveTraineeModel =============================== */} - {/* =========================== Start:: UndropTTLModel =============================== */}
-
-
-

- {t('Undrop TTL')} -

-
-
-
-
-
-

- {t('Are you sure you want to Undrop this TTL ?')} -

-
- {/* Reason input field */} -
- - - + className={`h-screen w-screen bg-black bg-opacity-30 backdrop-blur-sm fixed top-0 left-0 z-20 flex items-center justify-center px-4 ${ + undropTTLModel === true ? 'block' : 'hidden' + }`} + > +
+
+

+ {t('Undrop TTL')} +

+
+
+
+ +
+

+ {t('Are you sure you want to Undrop this TTL ?')} +

+
+ {/* Reason input field */} +
+ + +
+ +
- -
-
-
+
{/* =========================== End:: UndropTTLModel =============================== */}
- {loading ? ( + {loading ? ( ) : (