From c21759d87d9bc24f0074986fbbddf1ecfc345c6a Mon Sep 17 00:00:00 2001 From: Rushikesh-Sonawane99 Date: Thu, 21 Nov 2024 19:14:15 +0530 Subject: [PATCH] Issue #PS-1232 feat: Developed Board Enrollment Feature --- public/locales/en/common.json | 19 +- .../BoardEnrollmentProfile.tsx} | 128 ++- src/components/MenuDrawer.tsx | 8 +- src/components/PieChartGraph.tsx | 26 +- src/components/SortingModal.tsx | 72 +- src/pages/attendance-history.tsx | 19 +- src/pages/attendance-overview.tsx | 19 +- src/pages/board-enrollment/index.tsx | 756 ++++++++++++----- .../student-detail/[userId].tsx | 793 ++++++++++++++++++ .../board-enrollment/student-detail/index.tsx | 465 ---------- src/services/BoardEnrollmentServics.ts | 24 - src/services/MyClassDetailsService.ts | 38 +- src/store/boardEnrollmentStore.js | 19 + src/utils/Helper.ts | 90 +- src/utils/Interfaces.ts | 54 +- src/utils/app.constant.ts | 2 + 16 files changed, 1735 insertions(+), 797 deletions(-) rename src/{pages/board-enrollment/student-detail/profile-overview/index.tsx => components/BoardEnrollmentProfile.tsx} (59%) create mode 100644 src/pages/board-enrollment/student-detail/[userId].tsx delete mode 100644 src/pages/board-enrollment/student-detail/index.tsx delete mode 100644 src/services/BoardEnrollmentServics.ts create mode 100644 src/store/boardEnrollmentStore.js diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 68c28db5..6c6c8fa5 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -139,7 +139,8 @@ "NO_CENTER_FOUND": "No Center found", "FILTER_BY": "Filter By", "ALL": "All", - "NOW": "Now" + "ACTIVE": "Active", + "NA": "N.A." }, "LOGIN_PAGE": { "USERNAME": "Username", @@ -579,16 +580,24 @@ "BOARD_ENROLLMENT": "Board Enrollment", "BOARD_SELECTION": "Board Selection", "SUBJECTS_SELECTION": "Subjects Selection", - "REGISTRATION_COMPLETED": "Registration Completed", "DROPPED_OUT": " Dropped Out", "FEE_PAYMENT": "Fee Payment", - "BOARD_ENROLLMENT_NUMBER": "Board Enrolment Number", - "EXAM_FEES_PAID": " Exam Fees Paid? (optional)", + "BOARD_ENROLLMENT_NUMBER": "Board Enrollment Number", + "EXAM_FEES_PAID": "Exam Fees Paid?", "SAVE_AND_NEXT": "Save and Next", "TO_SAVE_YOUR_PROGRESS": "“Save & Next” to save your progress. You can come back and change it anytime.", "MANDATORY": "Mandatory for {{FeesStepBoards}} only. For other Boards, this step is automatically marked completed. Please click on Save to submit the data.", "COMPLETED": "Completed", - "MARK_AS_COMPLETE": "Mark as Complete" + "MARK_AS_COMPLETE": "Mark as Complete", + "BY_CURRENT_STAGES": "By Current Stages", + "REGISTRATION_NUMBER": "Registration Number", + "TOTAL_LEARNERS": "Total Learners", + "BOARD": "Board", + "CHOOSE_BOARD": "Choose which Board the Learner is going to be enrolled in", + "CHOOSE_SUBJECT": "Choose which subjects the learner is enrolling for", + "SUBJECTS_ENROLLED":"Subjects Enrolled", + "EXAM_REGISTRATION_FEES_PAID":"Exam Registration Fees Paid?", + "EDIT": "Edit" }, "OBSERVATION": { "OBSERVATION_START": "Start Observation", diff --git a/src/pages/board-enrollment/student-detail/profile-overview/index.tsx b/src/components/BoardEnrollmentProfile.tsx similarity index 59% rename from src/pages/board-enrollment/student-detail/profile-overview/index.tsx rename to src/components/BoardEnrollmentProfile.tsx index d85149ce..f65bfa10 100644 --- a/src/pages/board-enrollment/student-detail/profile-overview/index.tsx +++ b/src/components/BoardEnrollmentProfile.tsx @@ -1,14 +1,23 @@ import Header from '@/components/Header'; import { Box, Button, Divider, Typography } from '@mui/material'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +// import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import React from 'react'; import KeyboardBackspaceOutlinedIcon from '@mui/icons-material/KeyboardBackspaceOutlined'; import { useTheme } from '@mui/material/styles'; import { useTranslation } from 'next-i18next'; -import { useDirection } from '../../../../hooks/useDirection'; +import { useDirection } from '../hooks/useDirection'; import { logEvent } from '@/utils/googleAnalytics'; +import { BoardEnrollmentProfileProps } from '@/utils/Interfaces'; -const ProfileEnrolment = () => { +const BoardEnrollmentProfile: React.FC = ({ + learnerName, + centerName, + board, + subjects, + registrationNum, + feesPaidStatus, + setActiveStep = () => {}, +}) => { const theme = useTheme(); const { t } = useTranslation(); const { dir, isRTL } = useDirection(); @@ -21,6 +30,10 @@ const ProfileEnrolment = () => { label: 'Back Button Clicked', }); }; + + const handleEditClick = () => { + setActiveStep(0); + }; return ( <>
@@ -49,7 +62,7 @@ const ProfileEnrolment = () => { fontSize={'22px'} fontWeight={400} > - Student Name {/*will come from Api */} + {learnerName} { fontSize={'11px'} fontWeight={500} > - Khapari Dharmu (Chimur, Chandrapur) {/*will come from Api */} + {centerName} @@ -69,74 +82,44 @@ const ProfileEnrolment = () => { p: '16px', borderRadius: '16px', maxWidth: '60%', + '@media (max-width: 900px)': { + maxWidth: '100%', + }, }} > - - Board + + {t('BOARD_ENROLMENT.BOARD')} - ICSE + {board} - Subjects Enrolled + {t('BOARD_ENROLMENT.SUBJECTS_ENROLLED')} - - Hindi - - - Science - - - Social Science - - - Life Skills - + {subjects.map((subject) => ( + + {subject.name} + + ))} - - Board Enrolment Number + + {t('BOARD_ENROLMENT.BOARD_ENROLLMENT_NUMBER')} { mt: 1, }} > - 100245673 + {registrationNum} - - Date of Registration + + {t('BOARD_ENROLMENT.EXAM_REGISTRATION_FEES_PAID')} { mt: 1, }} > - 24 Aug, 2024 + {feesPaidStatus.toLocaleUpperCase()} @@ -179,20 +162,21 @@ const ProfileEnrolment = () => { }} variant="contained" color="primary" + onClick={handleEditClick} > - Edit + {t('BOARD_ENROLMENT.EDIT')} ); }; -export async function getStaticProps({ locale }: any) { - return { - props: { - ...(await serverSideTranslations(locale, ['common'])), - }, - }; -} +// export async function getStaticProps({ locale }: any) { +// return { +// props: { +// ...(await serverSideTranslations(locale, ['common'])), +// }, +// }; +// } -export default ProfileEnrolment; +export default BoardEnrollmentProfile; diff --git a/src/components/MenuDrawer.tsx b/src/components/MenuDrawer.tsx index 3851cdb2..a9a4d4a3 100644 --- a/src/components/MenuDrawer.tsx +++ b/src/components/MenuDrawer.tsx @@ -81,8 +81,8 @@ const MenuDrawer: React.FC = ({ session: ( <> {item.session}   - - ({t('COMMON.NOW')}) + + ({t('COMMON.ACTIVE')}) ), @@ -452,7 +452,7 @@ const MenuDrawer: React.FC = ({ )} - {/* + - */} + {isActiveYear && ( + + + {/* Button end here */} + + + + + {activeStep > 2 + ? t('BOARD_ENROLMENT.MANDATORY', { + FeesStepBoards: FeesStepBoards.join(', '), + }) + : t('BOARD_ENROLMENT.TO_SAVE_YOUR_PROGRESS')} + + + )} + + ); +}; + +export async function getStaticProps({ locale }: any) { + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + }, + }; +} +export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => { + return { + paths: [], //indicates that no page needs be created at build time + fallback: 'blocking', //indicates the type of fallback + }; +}; + +export default BoardEnrollmentDetail; diff --git a/src/pages/board-enrollment/student-detail/index.tsx b/src/pages/board-enrollment/student-detail/index.tsx deleted file mode 100644 index 766d6cb8..00000000 --- a/src/pages/board-enrollment/student-detail/index.tsx +++ /dev/null @@ -1,465 +0,0 @@ -import Header from '@/components/Header'; -import { - Box, - Button, - Checkbox, - FormControl, - FormControlLabel, - FormLabel, - InputLabel, - MenuItem, - Radio, - RadioGroup, - Select, - TextField, - Typography, -} from '@mui/material'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import React from 'react'; -import KeyboardBackspaceOutlinedIcon from '@mui/icons-material/KeyboardBackspaceOutlined'; -import { logEvent } from '@/utils/googleAnalytics'; -import { useTheme } from '@mui/material/styles'; -import { useTranslation } from 'react-i18next'; -import HorizontalLinearStepper from '@/components/HorizontalLinearStepper'; -import { FeesStepBoards } from '@/utils/app.constant'; -import { useDirection } from '../../../hooks/useDirection'; - -const BoardEnrollmentDetail = () => { - const theme = useTheme(); - const { t, i18n } = useTranslation(); - const { dir, isRTL } = useDirection(); - const handleBackEvent = () => { - window.history.back(); - logEvent({ - action: 'back-button-clicked-attendance-overview', - category: 'Board enrolment page', - label: 'Back Button Clicked', - }); - }; - const [selectedValue, setSelectedValue] = React.useState('a'); - const [activeStep, setActiveStep] = React.useState(0); - const [checked, setChecked] = React.useState([false, false]); - - const handleChange = (event: React.ChangeEvent) => { - setSelectedValue(event.target.value); - }; - - const handleNext = () => { - setActiveStep((prevActiveStep) => { - if (prevActiveStep < 3) { - return prevActiveStep + 1; - } else { - return prevActiveStep; - } - }); - }; - - const handleBack = () => { - setActiveStep((prevActiveStep) => { - if (prevActiveStep > 0) { - return prevActiveStep - 1; - } else { - return prevActiveStep; - } - }); - }; - - const handleParentChange = (event: React.ChangeEvent) => { - const isChecked = event.target.checked; - setChecked([isChecked, isChecked]); - }; - - const handleChildChange = - (index: number) => (event: React.ChangeEvent) => { - const updatedChecked = [...checked]; - updatedChecked[index] = event.target.checked; - setChecked(updatedChecked); - }; - return ( - <> - -
- - - - - Student Name {/*will come from Api */} - - - Khapari Dharmu (Chimur, Chandrapur) {/*will come from Api */} - - - - - - - - - - - {activeStep > 0 && ( - - Board: NIOS {/* will come from API */} - - )} - - {activeStep <= 1 && ( - - Choose which Board the Learner is going to be enrolled in - {/* will come from API */} - - )} - - {activeStep === 0 && ( - <> - - - - ICSE - - - - - - - - CBSE - - - - - - )} - - {activeStep === 1 && ( - <> - - - } - /> - - - - } - /> - - - - } - /> - - - )} - - - {activeStep === 2 && ( - <> - - - - - )} - - {activeStep === 3 && ( - - - - {t('BOARD_ENROLMENT.EXAM_FEES_PAID')} - - - } - label={t('COMMON.YES')} - sx={{ color: theme.palette.warning['300'] }} - /> - } - label={t('FORM.NO')} - sx={{ color: theme.palette.warning['300'] }} - /> - - - - )} - - {/* Button starts form here */} - - - - - {/* Button end here */} - - - - - {activeStep > 2 - ? t('BOARD_ENROLMENT.MANDATORY', { - FeesStepBoards: FeesStepBoards.join(', '), - }) - : t('BOARD_ENROLMENT.TO_SAVE_YOUR_PROGRESS')} - - - ); -}; - -export async function getStaticProps({ locale }: any) { - return { - props: { - ...(await serverSideTranslations(locale, ['common'])), - }, - }; -} - -export default BoardEnrollmentDetail; diff --git a/src/services/BoardEnrollmentServics.ts b/src/services/BoardEnrollmentServics.ts deleted file mode 100644 index 64624ce3..00000000 --- a/src/services/BoardEnrollmentServics.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BoardEnrollment } from '@/utils/Interfaces'; - -export const boardEnrollment = (): BoardEnrollment[] => { - return [ - { - userId: 1, - studentName: 'Aanya Gupta', - center: 'Khapari Dharmu (Chimur, Chandrapur)', - isDropout: false, - }, - { - userId: 2, - studentName: 'Aisha Bhatt', - center: 'Khapari Dharmu (Chimur, Chandrapur)', - isDropout: false, - }, - { - userId: 3, - studentName: 'Ankita Kulkarni', - center: 'Khapari Dharmu (Chimur, Chandrapur)', - isDropout: true, - }, - ]; -}; diff --git a/src/services/MyClassDetailsService.ts b/src/services/MyClassDetailsService.ts index 939e226e..4f5e8df4 100644 --- a/src/services/MyClassDetailsService.ts +++ b/src/services/MyClassDetailsService.ts @@ -86,13 +86,42 @@ export const updateCohortMemberStatus = async ({ memberStatus, statusReason, membershipId, + dynamicBody = {}, }: UpdateCohortMemberStatusParams): Promise => { const apiUrl: string = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/cohortmember/update/${membershipId}`; - try { - const response = await put(apiUrl, { - status: memberStatus, - statusReason, + + // Utility to stringify only the values of the customFields + const prepareCustomFields = (customFields: any[]): any[] => { + return customFields.map((field) => { + if (field && field.value !== undefined) { + return { + ...field, + value: typeof field.value === 'object' ? JSON.stringify(field.value) : field.value, + }; + } + return field; }); + }; + + // Build the request body dynamically + const requestBody = { + ...(memberStatus && { status: memberStatus }), + ...(statusReason && { statusReason }), + ...Object.entries(dynamicBody).reduce( + (acc, [key, value]) => { + acc[key] = typeof value === 'object' && value !== null ? JSON.stringify(value) : value; + return acc; + }, + {} as Record + ), + // Only stringify the `value` field of customFields if needed + ...(dynamicBody?.customFields && { + customFields: prepareCustomFields(dynamicBody.customFields), + }), + }; + + try { + const response = await put(apiUrl, requestBody); console.log('data', response?.data); return response?.data; } catch (error) { @@ -100,3 +129,4 @@ export const updateCohortMemberStatus = async ({ // throw error; } }; + diff --git a/src/store/boardEnrollmentStore.js b/src/store/boardEnrollmentStore.js new file mode 100644 index 00000000..87953d43 --- /dev/null +++ b/src/store/boardEnrollmentStore.js @@ -0,0 +1,19 @@ +/* eslint-disable no-unused-vars */ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +const boardEnrollmentStore = create( + persist( + (set) => ({ + boardEnrollmentData: '', + setBoardEnrollmentData: (newBoardEnrollmentData) => set(() => ({ boardEnrollmentData: newBoardEnrollmentData })), + }), + { + name: 'boardEnrollment', + getStorage: () => localStorage + // storage: typeof window !== 'undefined' ? localStorage : undefined, + } + ) +); + +export default boardEnrollmentStore; diff --git a/src/utils/Helper.ts b/src/utils/Helper.ts index d0cf6e18..956d7220 100644 --- a/src/utils/Helper.ts +++ b/src/utils/Helper.ts @@ -2,7 +2,7 @@ import { Role, Status, labelsToExtractForMiniProfile } from './app.constant'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import FingerprintJS from 'fingerprintjs2'; -import { CustomField, UpdateCustomField } from './Interfaces'; +import { BoardEnrollmentStageCounts, CustomField, UpdateCustomField } from './Interfaces'; dayjs.extend(utc); import { format, parseISO } from 'date-fns'; import manageUserStore from '@/store/manageUserStore'; @@ -118,17 +118,29 @@ export const debounce = any>( immediate?: boolean ) => { let timeout: ReturnType | undefined; - return function (this: ThisParameterType, ...args: Parameters) { + + const debounced = function (this: ThisParameterType, ...args: Parameters) { const context = this; clearTimeout(timeout); + if (immediate && !timeout) func.apply(context, args); + timeout = setTimeout(() => { timeout = undefined; if (!immediate) func.apply(context, args); }, wait); }; + + // Add a cancel method to clear any pending timeout + debounced.cancel = () => { + if (timeout) clearTimeout(timeout); + timeout = undefined; + }; + + return debounced; }; + //Function to convert names in capitalize case export const toPascalCase = (name: string | any) => { if (typeof name !== 'string') { @@ -574,6 +586,34 @@ export const filterAndMapAssociationsNew = ( })); }; +export const extractCategory = (data: any[] | any, category: string) => { + const items = Array.isArray(data) ? data : [data]; + return items.flatMap((item) => + item.associations + .filter((association: { category: string; }) => association.category === category) + .map( + ({ + name, + code, + identifier, + }: { + name: string; + code: string; + identifier: string; + }) => ({ + name, + code, + identifier, + }) + ) + ); +}; + +export const findCommon = (data1: any[], data2: any[]) => { + const data1Codes = new Set(data1.map((item) => item.code)); + return data2.filter((item) => data1Codes.has(item.code)); +}; + export function deepClone(obj: T): T { // Check if structuredClone is available if (typeof structuredClone === 'function') { @@ -649,3 +689,49 @@ export function formatEndDate({diffDays}: any) { } +//TODO: Modify Helper with correct logic +export const calculateStageCounts = (data: { completedStep: any; }[]): BoardEnrollmentStageCounts => { + const stagesCount: BoardEnrollmentStageCounts = { + board: 0, + subjects: 0, + registration: 0, + fees: 0, + completed: 0, + }; + + const stageKeys: Record = { + 0: "board", + 1: "subjects", + 2: "registration", + 3: "fees", + 4: "completed", + }; + + data.forEach(({ completedStep }) => { + const key = stageKeys[completedStep]; + if (key) stagesCount[key] += 1; + }); + + return stagesCount; +}; + + +export function getCohortNameById( + cohorts: { cohortId: string; name: string }[], + cohortId: string +): string | null { + const cohort = cohorts.find(c => c.cohortId === cohortId); + return cohort ? cohort.name : null; +} +// const isFieldFilled = (key: string, value: any): boolean => { +// if (key === "SUBJECTS") { +// return Array.isArray(value) && value.length > 0; +// } +// return value && value !== ""; // General check for other fields +// }; + +// export const calculateStageCount = (formData: Record): number => +// Object.entries(formData).reduce( +// (count, [key, value]) => count + (isFieldFilled(key, value) ? 1 : 0), +// 0 +// ); \ No newline at end of file diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts index b927d51f..b9117917 100644 --- a/src/utils/Interfaces.ts +++ b/src/utils/Interfaces.ts @@ -335,9 +335,10 @@ export interface AllCenterAttendancePercentParam { } export interface UpdateCohortMemberStatusParams { - memberStatus: string; + memberStatus?: string; statusReason?: string; membershipId: string | number; + dynamicBody?: Record; } export interface LearnerListProps { @@ -547,12 +548,6 @@ export interface Assessment { progress: string; score?: number; } -export interface BoardEnrollment { - userId: number; - studentName: string; - center: string; - isDropout: boolean; -} export interface AssessmentSubject { userId: number; @@ -841,3 +836,48 @@ export interface PlayerConfig { metadata?: Metadata; data?: any; } + +export interface BoardEnrollmentStageCounts { + board: number; + subjects: number; + registration: number; + fees: number; + completed: number; +}; + +// export interface BoardEnrollmentFieldsType { +// BOARD?: string; +// SUBJECTS?: string; +// REGISTRATION?: string; +// FEES?: string; +// }; + +export interface BoardEnrollmentData { + userId: string; + cohortMembershipId: string; + name: string; + memberStatus: string; + statusReason: string | null; + customField: { + fieldId: string; + label: string; + value: string; + }[]; + completedStep: number; +}; + +interface Subject { + name: string; + code?: string; + identifier?: string; +} + +export interface BoardEnrollmentProfileProps { + learnerName: string | undefined; + centerName: string; + board: string; + subjects: Subject[]; + registrationNum: string; + feesPaidStatus: string; + setActiveStep?: React.Dispatch> +} diff --git a/src/utils/app.constant.ts b/src/utils/app.constant.ts index 080a5d9a..090a25a0 100644 --- a/src/utils/app.constant.ts +++ b/src/utils/app.constant.ts @@ -73,6 +73,7 @@ export enum cohortPrivileges { export enum FormContext { USERS = 'USERS', COHORTS = 'COHORTS', + COHORT_MEMBER = 'COHORTMEMBER' } export enum FormContextType { @@ -80,6 +81,7 @@ export enum FormContextType { TEACHER = 'TEACHER', TEAM_LEADER = 'TEAM LEADER', COHORT = 'COHORT', + COHORT_MEMBER = 'COHORTMEMBER' } export enum ObservationEntityType { LEARNER = 'learner',