diff --git a/src/components/AttendanceStatusListView.tsx b/src/components/AttendanceStatusListView.tsx index 5bfc34f5..8afd35ca 100644 --- a/src/components/AttendanceStatusListView.tsx +++ b/src/components/AttendanceStatusListView.tsx @@ -19,6 +19,7 @@ import { getUserDetails } from '@/services/ProfileService'; import { useTheme } from '@mui/material/styles'; import { useTranslation } from 'next-i18next'; import DropoutLabel from './DropoutLabel'; +import { Status, names } from '@/utils/app.constant'; const AttendanceStatusListView: React.FC = ({ isDisabled = false, @@ -110,15 +111,6 @@ const AttendanceStatusListView: React.FC = ({ } }; - const names = [ - 'name', - 'age', - 'gender', - 'student_type', - 'enrollment_number', - 'primary_work', - ]; - const filteredFields = names .map((label) => customFieldsData.find((field) => field.name === label)) .filter(Boolean); @@ -165,7 +157,7 @@ const AttendanceStatusListView: React.FC = ({ userData?.name )} - {userData?.memberStatus === "dropout" ? + {userData?.memberStatus === Status.DROPOUT ? : <> diff --git a/src/components/CohortLearnerList.tsx b/src/components/CohortLearnerList.tsx index fe4e714f..98e3a5be 100644 --- a/src/components/CohortLearnerList.tsx +++ b/src/components/CohortLearnerList.tsx @@ -6,7 +6,7 @@ import { toPascalCase, } from '@/utils/Helper'; import LearnersList from '@/components/LearnersList'; -import { limit } from '@/utils/app.constant'; +import { Status, limit } from '@/utils/app.constant'; import { showToastMessage } from './Toastify'; import { useTranslation } from 'next-i18next'; import { Box, Typography } from '@mui/material'; @@ -81,10 +81,11 @@ const CohortLearnerList : React.FC = ({cohortId, reloadSt return ( = ({ } }; - const names = [ - 'name', - 'age', - 'gender', - 'student_type', - 'enrollment_number', - 'primary_work', - ]; - const filteredFields = names .map((name) => customFieldsData.find((field) => field.name === name)) .filter(Boolean); @@ -151,7 +143,7 @@ const StudentsStatsList: React.FC = ({ - {memberStatus === "dropout" ? + {memberStatus === Status.DROPOUT ? diff --git a/src/components/LearnersList.tsx b/src/components/LearnersList.tsx index a2e63699..23e7562f 100644 --- a/src/components/LearnersList.tsx +++ b/src/components/LearnersList.tsx @@ -10,22 +10,28 @@ import React, { useEffect } from 'react'; // import Woman2Icon from '@mui/icons-material/Woman2'; import { useTheme } from '@mui/material/styles'; import { useTranslation } from 'next-i18next'; -import { LearnerListProps } from '@/utils/Interfaces'; +import { LearnerListProps, UserData, updateCustomField } from '@/utils/Interfaces'; import ConfirmationModal from './ConfirmationModal'; import { updateCohortMemberStatus } from '@/services/MyClassDetailsService'; import ReactGA from 'react-ga4'; import { showToastMessage } from './Toastify'; +import Link from 'next/link'; +import { getUserDetails } from '@/services/ProfileService'; +import LearnerModal from './LearnerModal'; +import Loader from './Loader'; +import { Status, names } from '@/utils/app.constant'; type Anchor = 'bottom'; const LearnersList: React.FC = ({ + userId, learnerName, isDropout, enrollmentId, cohortMembershipId, statusReason, - reloadState, - setReloadState + reloadState, + setReloadState, }) => { const [state, setState] = React.useState({ bottom: false, @@ -33,17 +39,25 @@ const LearnersList: React.FC = ({ const [showModal, setShowModal] = React.useState(false); const [confirmationModalOpen, setConfirmationModalOpen] = React.useState(false); - const [loading, setLoading] = React.useState(false); - const theme = useTheme(); - const { t } = useTranslation(); + const [learnerState, setLearnerState] = React.useState({ + loading: false, + isModalOpenLearner: false, + userData: null as UserData | null, + userName: '', + contactNumber: '', + customFieldsData: [] as updateCustomField[] + }); + + const theme = useTheme(); + const { t } = useTranslation(); - useEffect(()=>{ + useEffect(() => { if (reloadState) { - setReloadState(false); + setReloadState(false); // window.location.reload(); } - },[reloadState, setReloadState]) + }, [reloadState, setReloadState]); const toggleDrawer = (anchor: Anchor, open: boolean) => @@ -58,45 +72,73 @@ const LearnersList: React.FC = ({ setState({ ...state, bottom: open }); }; - - const handleUnmarkDropout = async () => { - try { - setLoading(true); - - if (cohortMembershipId) { - const memberStatus = 'active'; - const membershipId = cohortMembershipId; - - const response = await updateCohortMemberStatus({ - memberStatus, - membershipId, + const setLoading = (loading: boolean) => { + setLearnerState((prevState) => ({ ...prevState, loading })); + }; + + const setIsModalOpenLearner = (isOpen: boolean) => { + setLearnerState((prevState) => ({ ...prevState, isModalOpenLearner: isOpen })); + }; + + const setUserData = (data: UserData | null) => { + setLearnerState((prevState) => ({ ...prevState, userData: data })); + }; + + const setUserName = (name: string) => { + setLearnerState((prevState) => ({ ...prevState, userName: name })); + }; + + const setContactNumber = (number: string) => { + setLearnerState((prevState) => ({ ...prevState, contactNumber: number })); + }; + + const setCustomFieldsData = (fields: updateCustomField[]) => { + setLearnerState((prevState) => ({ ...prevState, customFieldsData: fields })); + }; + + const handleUnmarkDropout = async () => { + try { + setLoading(true); + + if (cohortMembershipId) { + const memberStatus = Status.ACTIVE; + const membershipId = cohortMembershipId; + + const response = await updateCohortMemberStatus({ + memberStatus, + membershipId, + }); + + if (response?.responseCode !== 200 || response?.params?.err) { + ReactGA.event('unmark-dropout-student-error', { + cohortMembershipId: membershipId, }); - - if (response?.responseCode !== 200 || response?.params?.err) { - ReactGA.event('unmark-dropout-student-error', { cohortMembershipId: membershipId }); - throw new Error(response.params?.errmsg || 'An error occurred while updating the user.'); - } else { - ReactGA.event('unmark-dropout-student-successful', { - cohortMembershipId: membershipId, - }); - showToastMessage(t('COMMON.LEARNER_UNMARKED_DROPOUT'), 'success'); - setReloadState(true); - } + throw new Error( + response.params?.errmsg || + 'An error occurred while updating the user.' + ); + } else { + ReactGA.event('unmark-dropout-student-successful', { + cohortMembershipId: membershipId, + }); + showToastMessage(t('COMMON.LEARNER_UNMARKED_DROPOUT'), 'success'); + setReloadState(true); } - } catch (error) { - console.log(error); - showToastMessage(t('COMMON.SOMETHING_WENT_WRONG'), 'error'); - } finally { - setLoading(false); } - }; + } catch (error) { + console.log(error); + showToastMessage(t('COMMON.SOMETHING_WENT_WRONG'), 'error'); + } finally { + setLoading(false); + } + }; const listItemClick = (event: React.MouseEvent, name: string) => { if (name === 'mark-drop-out') { setShowModal(true); } else if (name === 'unmark-drop-out') { - handleUnmarkDropout() + handleUnmarkDropout(); } else { setConfirmationModalOpen(true); } @@ -107,23 +149,28 @@ const LearnersList: React.FC = ({ try { setLoading(true); if (cohortMembershipId) { - const memberStatus = 'archived'; + const memberStatus = Status.ARCHIVED; const membershipId = cohortMembershipId; - + const response = await updateCohortMemberStatus({ memberStatus, membershipId, }); - + if (response?.responseCode !== 200 || response?.params?.err) { - ReactGA.event('remove-student-error', { cohortMembershipId: membershipId }); - throw new Error(response.params?.errmsg || 'An error occurred while updating the user.'); + ReactGA.event('remove-student-error', { + cohortMembershipId: membershipId, + }); + throw new Error( + response.params?.errmsg || + 'An error occurred while updating the user.' + ); } else { ReactGA.event('remove-student-successful', { cohortMembershipId: membershipId, }); showToastMessage(t('COMMON.LEARNER_REMOVED'), 'success'); - setReloadState(true) + setReloadState(true); } } } catch (error) { @@ -149,6 +196,50 @@ const LearnersList: React.FC = ({ setShowModal(true); }; + const handleOpenModalLearner = (userId: string) => { + fetchUserDetails(userId); + setIsModalOpenLearner(true); + }; + + const handleCloseModalLearner = () => { + setIsModalOpenLearner(false); + }; + + const fetchUserDetails = async (userId: string) => { + try { + if (userId) { + setLoading(true); + const response = await getUserDetails(userId, true); + console.log('response for popup', response?.result); + if (response?.responseCode === 200) { + const data = response?.result; + if (data) { + const userData = data?.userData; + setUserData(userData); + setUserName(userData?.name); + setContactNumber(userData?.mobile); + const customDataFields = userData?.customFields; + if (customDataFields?.length > 0) { + setCustomFieldsData(customDataFields); + } + } + } + } + } catch (error) { + console.error('Error fetching user details:', error); + } finally { + setLoading(false); + } + }; + + const nameSet = new Set(names); + const filteredFields = learnerState.customFieldsData.reduce((acc, field) => { + if (field.name && nameSet.has(field.name)) { + acc.push(field); + } + return acc; + }, [] as updateCustomField[]); + const renderCustomContent = () => { if (isDropout) { return ( @@ -182,9 +273,20 @@ const LearnersList: React.FC = ({ return ( <> + {learnerState.loading ? ( + + ) : ( + + )} = ({ }} > - {/* + {/* */} - - {learnerName} - + {isDropout ? ( + + {learnerName} + + ) : ( + + { + handleOpenModalLearner(userId!); + ReactGA.event('learner-details-link-clicked', { + userId: userId, + }); + }} + sx={{ + textAlign: 'left', + fontSize: '16px', + fontWeight: '400', + color: theme.palette.secondary.main, + }} + > + {learnerName} + + + )} + = ({ justifyContent: 'left', }} > - {/* 19 y/o @@ -241,7 +365,9 @@ const LearnersList: React.FC = ({ }} onClick={handleDroppedOutLabelClick} > - Dropped Out + + {t('COMMON.DROPPED_OUT')} + ) : ( @@ -298,27 +424,26 @@ const LearnersList: React.FC = ({ renderCustomContent={renderCustomContent} /> - { - isDropout? + {isDropout ? ( setShowModal(false)} - cohortMembershipId={cohortMembershipId} - isButtonAbsent = {true} - statusReason = {statusReason} - reloadState={reloadState} - setReloadState={setReloadState} - /> - : + open={showModal} + onClose={() => setShowModal(false)} + cohortMembershipId={cohortMembershipId} + isButtonAbsent={true} + statusReason={statusReason} + reloadState={reloadState} + setReloadState={setReloadState} + /> + ) : ( setShowModal(false)} - cohortMembershipId={cohortMembershipId} - reloadState={reloadState} - setReloadState={setReloadState} - /> - } - + open={showModal} + onClose={() => setShowModal(false)} + cohortMembershipId={cohortMembershipId} + reloadState={reloadState} + setReloadState={setReloadState} + /> + )} + = ({ }); if (newArray.length != 0) { setNumberOfCohortMembers(newArray?.length); - const hasDropout = newArray.some((user) => user.memberStatus === 'dropout'); + const hasDropout = newArray.some((user) => user.memberStatus === Status.DROPOUT); if (hasDropout){ - setCohortMemberList(newArray.filter(user => user.memberStatus === 'active')); - setDropoutMemberList(newArray.filter(user => user.memberStatus === 'dropout')); + setCohortMemberList(newArray.filter(user => user.memberStatus === Status.ACTIVE)); + setDropoutMemberList(newArray.filter(user => user.memberStatus === Status.DROPOUT)); setPresentCount( newArray.filter( (user) => user.attendance === 'present' @@ -239,13 +240,13 @@ const MarkBulkAttendance: React.FC = ({ ); setDropoutCount( newArray.filter( - (user) => user.memberStatus === 'dropout' + (user) => user.memberStatus === Status.DROPOUT ).length ); } } else { - setCohortMemberList(nameUserIdArray.filter(user => user.memberStatus === 'active')); - setDropoutMemberList(nameUserIdArray.filter(user => user.memberStatus === 'dropout')) + setCohortMemberList(nameUserIdArray.filter(user => user.memberStatus === Status.ACTIVE)); + setDropoutMemberList(nameUserIdArray.filter(user => user.memberStatus === Status.DROPOUT)) setNumberOfCohortMembers(nameUserIdArray?.length); } updateBulkAttendanceStatus(newArray); diff --git a/src/pages/attendance-history.tsx b/src/pages/attendance-history.tsx index 19d35b73..c5265d92 100644 --- a/src/pages/attendance-history.tsx +++ b/src/pages/attendance-history.tsx @@ -52,6 +52,7 @@ import { useTheme } from '@mui/material/styles'; import { useTranslation } from 'next-i18next'; import { logEvent } from '@/utils/googleAnalytics'; import { showToastMessage } from '@/components/Toastify'; +import { Status } from '@/utils/app.constant'; interface user { memberStatus: string; @@ -507,8 +508,8 @@ const UserAttendanceHistory = () => { switch (sortByAttendance) { case 'pre': sortedData.sort((a, b) => { - if (a.memberStatus === 'dropout' && b.memberStatus !== 'dropout') return 1; - if (a.memberStatus !== 'dropout' && b.memberStatus === 'dropout') return -1; + if (a.memberStatus === Status.DROPOUT && b.memberStatus !== Status.DROPOUT) return 1; + if (a.memberStatus !== Status.DROPOUT && b.memberStatus === Status.DROPOUT) return -1; if (a.attendance === 'present' && b.attendance === 'absent') return -1; if (a.attendance === 'absent' && b.attendance === 'present') return 1; @@ -517,8 +518,8 @@ const UserAttendanceHistory = () => { break; case 'abs': sortedData.sort((a, b) => { - if (a.memberStatus === 'dropout' && b.memberStatus !== 'dropout') return 1; - if (a.memberStatus !== 'dropout' && b.memberStatus === 'dropout') return -1; + if (a.memberStatus === Status.DROPOUT && b.memberStatus !== Status.DROPOUT) return 1; + if (a.memberStatus !== Status.DROPOUT && b.memberStatus === Status.DROPOUT) return -1; if (a.attendance === 'absent' && b.attendance === 'present') return -1; if (a.attendance === 'present' && b.attendance === 'absent') return 1; diff --git a/src/pages/centers/[cohortId].tsx b/src/pages/centers/[cohortId].tsx index 3c188580..33d6f48a 100644 --- a/src/pages/centers/[cohortId].tsx +++ b/src/pages/centers/[cohortId].tsx @@ -143,6 +143,7 @@ const TeachingCenterDetails = () => { {router.push('/attendance-overview')}} > {t('COMMON.REVIEW_ATTENDANCE')} diff --git a/src/pages/centers/index.tsx b/src/pages/centers/index.tsx index 8a9e979d..c89289da 100644 --- a/src/pages/centers/index.tsx +++ b/src/pages/centers/index.tsx @@ -95,6 +95,7 @@ const TeachingCenters = () => { { router.push(`/centers/${cohort.cohortId}`); + localStorage.setItem('classId', cohort.cohortId) }} sx={{ cursor: 'pointer', marginBottom: '20px' }} > diff --git a/src/pages/manageUser.tsx b/src/pages/manageUser.tsx index 29a32b96..0695e780 100644 --- a/src/pages/manageUser.tsx +++ b/src/pages/manageUser.tsx @@ -34,6 +34,7 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import { editEditUser } from '@/services/ProfileService'; import ConfirmationModal from '@/components/ConfirmationModal'; +import { Status } from '@/utils/app.constant'; interface Cohort { cohortId: string; @@ -232,7 +233,7 @@ const manageUsers = () => { if (userId) { const userData = { name: name, - status: 'archived', + status: Status.ARCHIVED, }; const response = await editEditUser(userId, { userData }); console.log(response); diff --git a/src/utils/Helper.ts b/src/utils/Helper.ts index d43204ba..ad11676c 100644 --- a/src/utils/Helper.ts +++ b/src/utils/Helper.ts @@ -1,4 +1,5 @@ import FingerprintJS from 'fingerprintjs2'; +import { Status } from './app.constant'; export const ATTENDANCE_ENUM = { PRESENT: 'present', @@ -203,8 +204,8 @@ export const handleKeyDown = (event: React.KeyboardEvent) => { export const sortAttendanceNumber = (data: any[], order: string) => { return data.sort((a: { memberStatus: string; present_percent: string; }, b: { memberStatus: string; present_percent: string; }) => { - if (a.memberStatus === 'dropout' && b.memberStatus !== 'dropout') return 1; - if (a.memberStatus !== 'dropout' && b.memberStatus === 'dropout') return -1; + if (a.memberStatus === Status.DROPOUT && b.memberStatus !== Status.DROPOUT) return 1; + if (a.memberStatus !== Status.DROPOUT && b.memberStatus === Status.DROPOUT) return -1; const aPercent = parseFloat(a.present_percent); const bPercent = parseFloat(b.present_percent); if (isNaN(aPercent) && isNaN(bPercent)) return 0; @@ -216,8 +217,8 @@ export const sortAttendanceNumber = (data: any[], order: string) => { export const sortClassesMissed = (data: any[], order: string) => { return data.sort((a: { memberStatus: string; absent: string; }, b: { memberStatus: string; absent: string; }) => { - if (a.memberStatus === 'dropout' && b.memberStatus !== 'dropout') return 1; - if (a.memberStatus !== 'dropout' && b.memberStatus === 'dropout') return -1; + if (a.memberStatus === Status.DROPOUT && b.memberStatus !== Status.DROPOUT) return 1; + if (a.memberStatus !== Status.DROPOUT && b.memberStatus === Status.DROPOUT) return -1; const aClassMissed = parseFloat(a.absent); const bClassMissed = parseFloat(b.absent); if (isNaN(aClassMissed) && isNaN(bClassMissed)) return 0; diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts index 3101c4d8..fc8b3f24 100644 --- a/src/utils/Interfaces.ts +++ b/src/utils/Interfaces.ts @@ -244,6 +244,7 @@ export interface updateCohortMemberStatusParams { } export interface LearnerListProps { + userId: string; isDropout: boolean; enrollmentId?: string | number; cohortMembershipId: string | number; diff --git a/src/utils/app.constant.ts b/src/utils/app.constant.ts index 10fb70aa..f3846bf4 100644 --- a/src/utils/app.constant.ts +++ b/src/utils/app.constant.ts @@ -1,16 +1,23 @@ export const limit: number = 300; export const refetchInterval: number = 5 * 60 * 1000; export const gcTime: number = 10 * 60 * 1000; +export const names = [ + 'name', + 'age', + 'gender', + 'student_type', + 'enrollment_number', + 'primary_work', +]; export enum Role { - STUDENT = 'Student', - TEACHER = 'Teacher', - ADMIN = 'Admin', - }; - - export enum Status { - DROPOUT = 'dropout', - ACTIVE = 'active', - ARCHIVED = 'archived' - } - \ No newline at end of file + STUDENT = 'Student', + TEACHER = 'Teacher', + ADMIN = 'Admin', +} + +export enum Status { + DROPOUT = 'dropout', + ACTIVE = 'active', + ARCHIVED = 'archived', +}