From fe58a5ae7fad9c534b2979c6eaf87d00bcedcb87 Mon Sep 17 00:00:00 2001 From: Abin Date: Thu, 20 Jul 2023 18:37:25 +0530 Subject: [PATCH 1/3] apis created for student management --- .../api/middlewares/protectedInterceptor.ts | 1 - .../pages/studentManagement/ViewStudents.tsx | 282 ++++++++++++++++++ client/src/routes.tsx | 5 + .../adapters/controllers/studentController.ts | 36 ++- .../app/repositories/studentDbRepository.ts | 11 +- .../usecases/management/studentManagement.ts | 41 +++ .../database/mongodb/models/student.ts | 10 + .../repositories/studentsRepoMongoDb.ts | 24 +- .../src/frameworks/webserver/routes/index.ts | 7 +- .../frameworks/webserver/routes/student.ts | 6 + server/src/types/studentInterface.ts | 1 + 11 files changed, 414 insertions(+), 10 deletions(-) create mode 100644 client/src/components/pages/studentManagement/ViewStudents.tsx create mode 100644 server/src/app/usecases/management/studentManagement.ts diff --git a/client/src/api/middlewares/protectedInterceptor.ts b/client/src/api/middlewares/protectedInterceptor.ts index 440e13e..ba3be8f 100644 --- a/client/src/api/middlewares/protectedInterceptor.ts +++ b/client/src/api/middlewares/protectedInterceptor.ts @@ -44,7 +44,6 @@ api.interceptors.response.use( ); return api(originalRequest); } catch (err) { - console.log(err); return Promise.reject(err); } } diff --git a/client/src/components/pages/studentManagement/ViewStudents.tsx b/client/src/components/pages/studentManagement/ViewStudents.tsx new file mode 100644 index 0000000..ec11d35 --- /dev/null +++ b/client/src/components/pages/studentManagement/ViewStudents.tsx @@ -0,0 +1,282 @@ +import React, { useEffect, useState } from "react"; +import { PencilIcon } from "@heroicons/react/24/solid"; +import { + MagnifyingGlassIcon, +} from "@heroicons/react/24/outline"; +import { + Card, + CardHeader, + Typography, + Button, + CardBody, + Chip, + CardFooter, + Avatar, + IconButton, + Tooltip, + Input, +} from "@material-tailwind/react"; +import { + getAllInstructors, + unblockInstructors, +} from "../../../api/endpoints/instructorManagement"; +import { toast } from "react-toastify"; +import { formatDate } from "../../../utils/helpers"; +import BlockReasonModal from "../InstructorManagement/BlockReasonModal"; +import usePagination from "../../../hooks/usePagination"; + +const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions", ""]; + +const ViewStudents: React.FC = () => { + const [instructors, setInstructors] = useState([]); + const [open, setOpen] = useState(false); + const [updated, setUpdated] = useState(false); + const [id, setId] = useState(""); + const ITEMS_PER_PAGE = 4; + const { + currentPage, + totalPages, + currentData, + goToPage, + goToPreviousPage, + goToNextPage, + } = usePagination(instructors, ITEMS_PER_PAGE); + const fetchInstructors = async () => { + try { + const response = await getAllInstructors(); + setInstructors(response?.data?.data); + } catch (error: any) { + toast.error(error.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + useEffect(() => { + fetchInstructors(); + }, [updated]); + + const handleUnblock = async (instructorId: string) => { + try { + const response = await unblockInstructors(instructorId); + toast.success(response.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + setUpdated(!updated); + } catch (error: any) { + toast.error(error.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + return ( + + {open && ( + + )} + +
+
+ + Manage Students + + + These are details about the students + +
+
+
+ } + /> +
+
+
+
+ + + + + {TABLE_HEAD.map((head) => ( + + ))} + + + + {currentData.map( + ( + { + _id, + img, + firstName, + lastName, + email, + dateJoined, + isBlocked, + isVerified, + }, + index + ) => { + const isLast = index === instructors.length - 1; + const classes = isLast + ? "p-4" + : "p-4 border-b border-blue-gray-50"; + + return ( + + + + + + + + + ); + } + )} + +
+ + {head} + +
+
+ + + {`${firstName} ${lastName}`} + +
+
+ + {email} + + + + {formatDate(dateJoined)} + + +
+ +
+
+
+ {isBlocked ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ + + + + +
+
+ + +
+ {Array.from({ length: totalPages }, (_, index) => index + 1).map( + (pageNumber) => ( + goToPage(pageNumber)} + > + {pageNumber} + + ) + )} +
+ +
+
+ ); +}; + +export default ViewStudents; diff --git a/client/src/routes.tsx b/client/src/routes.tsx index ea895c3..0053845 100644 --- a/client/src/routes.tsx +++ b/client/src/routes.tsx @@ -31,6 +31,7 @@ import UserDashboard from "./components/pages/dash/UserDashboard"; import MyCourses from "./components/pages/dash/MyCourses"; import MyProfile from "./components/pages/dash/MyProfile"; import DashHome from "./components/pages/dash/DashHome"; +import ViewStudents from "./components/pages/studentManagement/ViewStudents"; const LazyListCourse = lazy( () => import("./components/pages/Course/ListCourse") @@ -148,6 +149,10 @@ const AppRouter = createBrowserRouter([ }, ], }, + { + path:"students", + element: + }, { path: "categories", element: , diff --git a/server/src/adapters/controllers/studentController.ts b/server/src/adapters/controllers/studentController.ts index 40697db..3b43cea 100644 --- a/server/src/adapters/controllers/studentController.ts +++ b/server/src/adapters/controllers/studentController.ts @@ -17,6 +17,7 @@ import { } from '../../types/studentInterface'; import { CloudServiceInterface } from '../../app/services/cloudServiceInterface'; import { CloudServiceImpl } from '../../frameworks/services/s3CloudService'; +import { blockStudentU, getAllStudentsU, unblockStudentU } from '../../app/usecases/management/studentManagement'; const studentController = ( authServiceInterface: AuthServiceInterface, @@ -100,11 +101,44 @@ const studentController = ( } ); + const getAllStudents = asyncHandler(async (req:Request,res:Response)=>{ + const students = await getAllStudentsU(dbRepositoryStudent) + res.status(200).json({ + status: 'success', + message: 'Successfully retrieved all student details', + data: students + }); + }) + + const blockStudent = asyncHandler(async (req:Request,res:Response)=>{ + const studentId:string = req.params.studentId + const reason:string = req.body.reason + await blockStudentU(studentId,reason,dbRepositoryStudent) + res.status(200).json({ + status: 'success', + message: 'Successfully blocked student ', + data: null + }); + }) + + const unblockStudent = asyncHandler(async (req:Request,res:Response)=>{ + const studentId:string = req.params.studentId + await unblockStudentU(studentId,dbRepositoryStudent) + res.status(200).json({ + status: 'success', + message: 'Successfully unblocked student ', + data: null + }); + }) + return { changePassword, updateProfile, getStudentDetails, - getProfileUrl + getProfileUrl, + blockStudent, + unblockStudent, + getAllStudents }; }; diff --git a/server/src/app/repositories/studentDbRepository.ts b/server/src/app/repositories/studentDbRepository.ts index e9aa190..46cf6b2 100644 --- a/server/src/app/repositories/studentDbRepository.ts +++ b/server/src/app/repositories/studentDbRepository.ts @@ -19,12 +19,21 @@ export const studentDbRepository = ( const updateProfile = async (id: string, studentInfo: StudentUpdateInfo) => await repository.updateProfile(id, studentInfo); + const getAllStudents = async() => await repository.getAllStudents() + + const blockStudent = async(id:string,reason:string) => await repository.blockStudent(id,reason) + + const unblockStudent = async(id:string) => await repository.unblockStudent(id) + return { addStudent, getStudentByEmail, getStudent, changePassword, - updateProfile + updateProfile, + getAllStudents, + blockStudent, + unblockStudent, }; }; diff --git a/server/src/app/usecases/management/studentManagement.ts b/server/src/app/usecases/management/studentManagement.ts new file mode 100644 index 0000000..2a385d7 --- /dev/null +++ b/server/src/app/usecases/management/studentManagement.ts @@ -0,0 +1,41 @@ +import HttpStatusCodes from '../../../constants/HttpStatusCodes'; +import AppError from '../../../utils/appError'; +import { StudentsDbInterface } from '@src/app/repositories/studentDbRepository'; + +export const getAllStudentsU = async ( + studentRepository: ReturnType +) => { + const students = await studentRepository.getAllStudents(); + return students; +}; + +export const blockStudentU = async ( + studentId: string, + reason:string, + studentRepository: ReturnType +) => { + if (!studentId) { + throw new AppError('Invalid student details', HttpStatusCodes.BAD_REQUEST); + } + if (!reason) { + throw new AppError('Please give a reason to block a student', HttpStatusCodes.BAD_REQUEST); + } + const student = await studentRepository.getStudent(studentId); + if (student?.isBlocked) { + throw new AppError( + 'Already blocked this student', + HttpStatusCodes.CONFLICT + ); + } + await studentRepository.blockStudent(studentId,reason); +}; + +export const unblockStudentU = async ( + studentId: string, + studentRepository: ReturnType +) => { + if (!studentId) { + throw new AppError('Invalid student details', HttpStatusCodes.BAD_REQUEST); + } + await studentRepository.unblockStudent(studentId); +}; diff --git a/server/src/frameworks/database/mongodb/models/student.ts b/server/src/frameworks/database/mongodb/models/student.ts index 533ecc7..4b0cbc1 100644 --- a/server/src/frameworks/database/mongodb/models/student.ts +++ b/server/src/frameworks/database/mongodb/models/student.ts @@ -16,6 +16,8 @@ interface IStudent extends Document { coursesEnrolled: mongoose.Schema.Types.ObjectId[]; dateJoined: Date; isGoogleUser: boolean; + isBlocked: boolean; + blockedReason: string; } const ProfileSchema = new Schema({ @@ -85,6 +87,14 @@ const studentSchema = new Schema({ isGoogleUser: { type: Boolean, default: false + }, + isBlocked: { + type: Boolean, + default: false + }, + blockedReason: { + type: String, + default: '' } }); diff --git a/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts b/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts index 4f05e92..3e0f82b 100644 --- a/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts +++ b/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts @@ -35,12 +35,34 @@ export const studentRepositoryMongoDB = () => { ); }; + const getAllStudents = async () => { + const students = await Student.find({}); + return students; + }; + + const blockStudent = async (id: string, reason: string) => { + await Student.updateOne( + { _id: new mongoose.Types.ObjectId(id) }, + { isBlocked: true, blockedReason: reason } + ); + }; + + const unblockStudent = async (id: string) => { + await Student.updateOne( + { _id: new mongoose.Types.ObjectId(id) }, + { isBlocked: false, blockedReason: '' } + ); + }; + return { addStudent, getStudentByEmail, getStudent, changePassword, - updateProfile + updateProfile, + getAllStudents, + blockStudent, + unblockStudent }; }; diff --git a/server/src/frameworks/webserver/routes/index.ts b/server/src/frameworks/webserver/routes/index.ts index 195574d..c00a592 100644 --- a/server/src/frameworks/webserver/routes/index.ts +++ b/server/src/frameworks/webserver/routes/index.ts @@ -29,12 +29,7 @@ const routes = (app: Application, redisClient: RedisClient) => { app.use('/api/video-streaming', videoStreamRouter()); app.use('/api/instructors', instructorRouter()); app.use('/api/payments', jwtAuthMiddleware, paymentRouter()); - app.use( - '/api/students', - jwtAuthMiddleware, - studentRoleCheckMiddleware, - studentRouter() - ); + app.use('/api/students', jwtAuthMiddleware, studentRouter()); }; export default routes; diff --git a/server/src/frameworks/webserver/routes/student.ts b/server/src/frameworks/webserver/routes/student.ts index 656acfc..2e0c10c 100644 --- a/server/src/frameworks/webserver/routes/student.ts +++ b/server/src/frameworks/webserver/routes/student.ts @@ -26,6 +26,12 @@ const studentRouter = () => { router.get('/get-profile-url',controller.getProfileUrl) + router.get('/get-all-students',controller.getAllStudents) + + router.patch('/block-student/:studentId',controller.blockStudent) + + router.patch('/unblock-student/:studentId',controller.unblockStudent) + return router; }; export default studentRouter; diff --git a/server/src/types/studentInterface.ts b/server/src/types/studentInterface.ts index 371ba9b..6deb5ba 100644 --- a/server/src/types/studentInterface.ts +++ b/server/src/types/studentInterface.ts @@ -11,6 +11,7 @@ export interface StudentInterface { mobile: number; password: string; isGoogleUser: boolean; + isBlocked:boolean } export interface StudentUpdateInfo { From 6cf804647966a6cba14908feeac8f2c7ee537f2a Mon Sep 17 00:00:00 2001 From: Abin Date: Thu, 20 Jul 2023 21:48:39 +0530 Subject: [PATCH 2/3] listed students,block,unblock added --- client/src/api/endpoints/studentManagement.ts | 17 ++ .../src/api/services/studentManageService.ts | 31 +++ .../studentManagement/BlockStudentModal.tsx | 138 ++++++++++ .../studentManagement/BlockedStudents.tsx | 254 ++++++++++++++++++ .../pages/studentManagement/StudentsTab.tsx | 70 +++++ .../pages/studentManagement/ViewStudents.tsx | 93 +++---- client/src/constants/endpoints.ts | 5 +- client/src/routes.tsx | 5 +- .../adapters/controllers/studentController.ts | 1 + 9 files changed, 552 insertions(+), 62 deletions(-) create mode 100644 client/src/api/endpoints/studentManagement.ts create mode 100644 client/src/api/services/studentManageService.ts create mode 100644 client/src/components/pages/studentManagement/BlockStudentModal.tsx create mode 100644 client/src/components/pages/studentManagement/BlockedStudents.tsx create mode 100644 client/src/components/pages/studentManagement/StudentsTab.tsx diff --git a/client/src/api/endpoints/studentManagement.ts b/client/src/api/endpoints/studentManagement.ts new file mode 100644 index 0000000..7aa5872 --- /dev/null +++ b/client/src/api/endpoints/studentManagement.ts @@ -0,0 +1,17 @@ +import { + getAllStudentsService, + blockStudentService, + unblockStudentService, +} from "../services/studentManageService"; +import END_POINTS from "../../constants/endpoints"; + +export const getAllStudents = () => { + return getAllStudentsService(END_POINTS.GET_ALL_STUDENTS); +}; + +export const blockStudents = (studentId:string,reason: string) => { + return blockStudentService(END_POINTS.BLOCK_STUDENT,studentId, reason); +}; +export const unblockStudent = (studentId:string) => { + return unblockStudentService(END_POINTS.UNBLOCK_STUDENT,studentId); +}; diff --git a/client/src/api/services/studentManageService.ts b/client/src/api/services/studentManageService.ts new file mode 100644 index 0000000..9356caf --- /dev/null +++ b/client/src/api/services/studentManageService.ts @@ -0,0 +1,31 @@ +import CONSTANTS_COMMON from "../../constants/common"; +import api from "../middlewares/protectedInterceptor"; + +export const blockStudentService = async ( + endpoint: string, + studentId: string, + reason: string +) => { + console.log(reason) + const response = await api.patch( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${studentId}`,{reason} + ); + return response.data; +}; + +export const unblockStudentService = async ( + endpoint: string, + studentId: string +) => { + const response = await api.patch( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${studentId}` + ); + return response.data; +}; + +export const getAllStudentsService = async (endpoint: string) => { + const response = await api.get( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}` + ); + return response.data; +}; diff --git a/client/src/components/pages/studentManagement/BlockStudentModal.tsx b/client/src/components/pages/studentManagement/BlockStudentModal.tsx new file mode 100644 index 0000000..165e63b --- /dev/null +++ b/client/src/components/pages/studentManagement/BlockStudentModal.tsx @@ -0,0 +1,138 @@ +import { Fragment, useRef, useState } from "react"; +import { Dialog, Transition } from "@headlessui/react"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { SetStateAction, Dispatch } from "react"; +import {toast} from 'react-toastify' +import { blockStudents } from "../../../api/endpoints/studentManagement"; +interface ModalProps { + open:boolean; + setOpen: Dispatch>; + updated:boolean; + setUpdated:Dispatch>; + id:string +}; +export default function BlockStudentModal({open,setOpen,updated,setUpdated,id}:ModalProps) { + const cancelButtonRef = useRef(null); + const [selectedReason, setSelectedReason] = useState(""); + + const reasons = [ + "posted wrong comments", + "Violated the rules", + "others", + ]; + + const handleBlock = async (studentId: string, reason: string) => { + try { + const response = await blockStudents(studentId, reason); + toast.success(response?.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + setUpdated(!updated) + + } catch (error: any) { + toast.error(error.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + + return ( + + + +
+ + +
+
+ + +
+
+
+
+
+ + Block Student + +
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+
+ ); +} diff --git a/client/src/components/pages/studentManagement/BlockedStudents.tsx b/client/src/components/pages/studentManagement/BlockedStudents.tsx new file mode 100644 index 0000000..b7c492f --- /dev/null +++ b/client/src/components/pages/studentManagement/BlockedStudents.tsx @@ -0,0 +1,254 @@ +import React, { useEffect, useState } from "react"; +import { PencilIcon } from "@heroicons/react/24/solid"; +import { + MagnifyingGlassIcon, +} from "@heroicons/react/24/outline"; +import { + Card, + CardHeader, + Typography, + Button, + CardBody, + Chip, + CardFooter, + Avatar, + IconButton, + Tooltip, + Input, +} from "@material-tailwind/react"; +import { + unblockInstructors, +} from "../../../api/endpoints/instructorManagement"; +import { toast } from "react-toastify"; +import { formatDate } from "../../../utils/helpers"; +import usePagination from "../../../hooks/usePagination"; +import { getBlockedInstructors } from "../../../api/endpoints/instructorManagement"; + +const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions", ""]; + +const BlockedStudents: React.FC = () => { + const [instructors, setInstructors] = useState([]); + const [updated, setUpdated] = useState(false); + const ITEMS_PER_PAGE = 6; + const { + currentPage, + totalPages, + currentData, + goToPage, + goToPreviousPage, + goToNextPage, + } = usePagination(instructors, ITEMS_PER_PAGE); + const fetchBlockedInstructors = async () => { + try { + const response = await getBlockedInstructors(); + setInstructors(response?.data?.data); + } catch (error: any) { + toast.error(error.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + useEffect(() => { + fetchBlockedInstructors(); + }, [updated]); + + const handleUnblock = async (instructorId: string) => { + try { + const response = await unblockInstructors(instructorId); + toast.success(response.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + setUpdated(!updated); + } catch (error: any) { + toast.error(error.data.message, { + position: toast.POSITION.BOTTOM_RIGHT, + }); + } + }; + return ( + + +
+
+ + Unblock students + + + These are details about blocked students + +
+
+
+ } + /> +
+
+
+
+ + + + + {TABLE_HEAD.map((head) => ( + + ))} + + + + {currentData.map( + ( + { + _id, + img, + firstName, + lastName, + email, + dateJoined, + isBlocked, + }, + index + ) => { + const isLast = index === instructors.length - 1; + const classes = isLast + ? "p-4" + : "p-4 border-b border-blue-gray-50"; + + return ( + + + + + + + + ); + } + )} + +
+ + {head} + +
+
+ + + {`${firstName} ${lastName}`} + +
+
+ + {email} + + + + {formatDate(dateJoined)} + + +
+ +
+
+
+ {isBlocked ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+
+ + +
+ {Array.from({ length: totalPages }, (_, index) => index + 1).map( + (pageNumber) => ( + goToPage(pageNumber)} + > + {pageNumber} + + ) + )} +
+ +
+
+ ); +}; + +export default BlockedStudents; diff --git a/client/src/components/pages/studentManagement/StudentsTab.tsx b/client/src/components/pages/studentManagement/StudentsTab.tsx new file mode 100644 index 0000000..bb5542b --- /dev/null +++ b/client/src/components/pages/studentManagement/StudentsTab.tsx @@ -0,0 +1,70 @@ +import React, { useState } from "react"; +import { + Tabs, + TabsHeader, + TabsBody, + Tab, + TabPanel, +} from "@material-tailwind/react"; +import { + Square3Stack3DIcon, + UserCircleIcon, + Cog6ToothIcon, +} from "@heroicons/react/24/solid"; +import BlockedStudents from "./BlockedStudents"; +import ViewStudents from "./ViewStudents"; + +interface TabData { + label: string; + value: string; + icon: React.ElementType; +} + +export default function StudentsTab() { + const [activeTab, setActiveTab] = useState("all"); + + const handleTabChange = (value: string) => { + setActiveTab(value); + }; + + const data: TabData[] = [ + { + label: "All students", + value: "all", + icon: Square3Stack3DIcon, + }, + { + label: "Blocked", + value: "blocked", + icon: UserCircleIcon, + }, + ]; + + const tabComponents: { [key: string]: JSX.Element } = { + all: , // Replace with the component you want to show for "All students" + blocked: , // Replace with the component you want to show for "Blocked" + // Add more components for other tabs if needed + }; + + return ( + + + {data.map(({ label, value, icon: Icon }) => ( + +
+ + {label} +
+
+ ))} +
+ + {data.map(({ value }) => ( + + {tabComponents[value]} + + ))} + +
+ ); +} diff --git a/client/src/components/pages/studentManagement/ViewStudents.tsx b/client/src/components/pages/studentManagement/ViewStudents.tsx index ec11d35..a84f475 100644 --- a/client/src/components/pages/studentManagement/ViewStudents.tsx +++ b/client/src/components/pages/studentManagement/ViewStudents.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import { PencilIcon } from "@heroicons/react/24/solid"; -import { - MagnifyingGlassIcon, -} from "@heroicons/react/24/outline"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { Card, CardHeader, @@ -17,61 +15,66 @@ import { Input, } from "@material-tailwind/react"; import { - getAllInstructors, - unblockInstructors, -} from "../../../api/endpoints/instructorManagement"; + getAllStudents, + unblockStudent, +} from "../../../api/endpoints/studentManagement"; + import { toast } from "react-toastify"; import { formatDate } from "../../../utils/helpers"; import BlockReasonModal from "../InstructorManagement/BlockReasonModal"; import usePagination from "../../../hooks/usePagination"; +import StudentsTab from "./StudentsTab"; +import BlockStudentModal from "./BlockStudentModal"; -const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions", ""]; +const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions"]; const ViewStudents: React.FC = () => { - const [instructors, setInstructors] = useState([]); + const [students, setStudents] = useState([]); const [open, setOpen] = useState(false); const [updated, setUpdated] = useState(false); const [id, setId] = useState(""); - const ITEMS_PER_PAGE = 4; - const { - currentPage, - totalPages, + const ITEMS_PER_PAGE = 4; + const { + currentPage, + totalPages, currentData, goToPage, - goToPreviousPage, + goToPreviousPage, goToNextPage, - } = usePagination(instructors, ITEMS_PER_PAGE); - const fetchInstructors = async () => { + } = usePagination(students, ITEMS_PER_PAGE); + + const fetchStudents = async () => { try { - const response = await getAllInstructors(); - setInstructors(response?.data?.data); + const response = await getAllStudents(); + console.log(response); + setStudents(response?.data); } catch (error: any) { - toast.error(error.data.message, { + toast.error(error.data.message, { position: toast.POSITION.BOTTOM_RIGHT, }); } }; useEffect(() => { - fetchInstructors(); + fetchStudents(); }, [updated]); - const handleUnblock = async (instructorId: string) => { + const handleUnblock = async (studentId: string) => { try { - const response = await unblockInstructors(instructorId); - toast.success(response.data.message, { + const response = await unblockStudent(studentId); + toast.success(response?.data?.message, { position: toast.POSITION.BOTTOM_RIGHT, }); setUpdated(!updated); } catch (error: any) { - toast.error(error.data.message, { + toast.error(error?.data?.message, { position: toast.POSITION.BOTTOM_RIGHT, }); } - }; + }; return ( - + {open && ( - { - {currentData.map( + {currentData?.map( ( - { - _id, - img, - firstName, - lastName, - email, - dateJoined, - isBlocked, - isVerified, - }, + { _id, img, firstName, lastName, email, dateJoined, isBlocked }, index ) => { - const isLast = index === instructors.length - 1; + const isLast = index === students?.length - 1; const classes = isLast ? "p-4" : "p-4 border-b border-blue-gray-50"; @@ -181,20 +175,8 @@ const ViewStudents: React.FC = () => { @@ -226,13 +208,6 @@ const ViewStudents: React.FC = () => { )} - - - - - - - ); } diff --git a/client/src/constants/endpoints.ts b/client/src/constants/endpoints.ts index 7592cd1..968aa8f 100644 --- a/client/src/constants/endpoints.ts +++ b/client/src/constants/endpoints.ts @@ -42,6 +42,9 @@ const END_POINTS = { UPDATE_PROFILE:"api/students/update-profile", GET_STUDENT_DETAILS:"api/students/get-student-details", GET_COURSE_BY_STUDENT:"api/courses/get-course-by-student", - GET_PROFILE_URL:"api/students/get-profile-url" + GET_PROFILE_URL:"api/students/get-profile-url", + GET_ALL_STUDENTS:"api/students/get-all-students", + BLOCK_STUDENT:"api/students/block-student", + UNBLOCK_STUDENT:"api/students/unblock-student" } export default END_POINTS \ No newline at end of file diff --git a/client/src/routes.tsx b/client/src/routes.tsx index 0053845..92241c7 100644 --- a/client/src/routes.tsx +++ b/client/src/routes.tsx @@ -32,6 +32,7 @@ import MyCourses from "./components/pages/dash/MyCourses"; import MyProfile from "./components/pages/dash/MyProfile"; import DashHome from "./components/pages/dash/DashHome"; import ViewStudents from "./components/pages/studentManagement/ViewStudents"; +import StudentsTab from "./components/pages/studentManagement/StudentsTab"; const LazyListCourse = lazy( () => import("./components/pages/Course/ListCourse") @@ -151,11 +152,11 @@ const AppRouter = createBrowserRouter([ }, { path:"students", - element: + element: }, { path: "categories", - element: , + element: , children: [ { path: "", diff --git a/server/src/adapters/controllers/studentController.ts b/server/src/adapters/controllers/studentController.ts index 3b43cea..776ac2c 100644 --- a/server/src/adapters/controllers/studentController.ts +++ b/server/src/adapters/controllers/studentController.ts @@ -113,6 +113,7 @@ const studentController = ( const blockStudent = asyncHandler(async (req:Request,res:Response)=>{ const studentId:string = req.params.studentId const reason:string = req.body.reason + console.log(reason) await blockStudentU(studentId,reason,dbRepositoryStudent) res.status(200).json({ status: 'success', From 55978c74969a30eb1e9adf0cac07b37278798311 Mon Sep 17 00:00:00 2001 From: Abin Date: Fri, 21 Jul 2023 01:30:39 +0530 Subject: [PATCH 3/3] student management completed --- client/src/api/endpoints/studentManagement.ts | 6 + .../src/api/services/studentManageService.ts | 11 +- .../ViewMoreInstructorRequest.tsx | 2 +- .../studentManagement/BlockedStudents.tsx | 229 +++++++++--------- .../pages/studentManagement/StudentsTab.tsx | 11 +- .../pages/studentManagement/ViewStudents.tsx | 12 +- client/src/constants/endpoints.ts | 3 +- client/src/routes.tsx | 2 +- .../adapters/controllers/studentController.ts | 17 +- .../app/repositories/studentDbRepository.ts | 3 + .../usecases/management/studentManagement.ts | 17 +- .../repositories/studentsRepoMongoDb.ts | 8 +- .../frameworks/webserver/routes/student.ts | 2 + 13 files changed, 187 insertions(+), 136 deletions(-) diff --git a/client/src/api/endpoints/studentManagement.ts b/client/src/api/endpoints/studentManagement.ts index 7aa5872..df4648d 100644 --- a/client/src/api/endpoints/studentManagement.ts +++ b/client/src/api/endpoints/studentManagement.ts @@ -2,6 +2,7 @@ import { getAllStudentsService, blockStudentService, unblockStudentService, + getAllBlockedStudentsService, } from "../services/studentManageService"; import END_POINTS from "../../constants/endpoints"; @@ -12,6 +13,11 @@ export const getAllStudents = () => { export const blockStudents = (studentId:string,reason: string) => { return blockStudentService(END_POINTS.BLOCK_STUDENT,studentId, reason); }; + export const unblockStudent = (studentId:string) => { return unblockStudentService(END_POINTS.UNBLOCK_STUDENT,studentId); }; + +export const getAllBlockedStudents = ()=>{ + return getAllBlockedStudentsService(END_POINTS.GET_BLOCKED_STUDENTS) +} diff --git a/client/src/api/services/studentManageService.ts b/client/src/api/services/studentManageService.ts index 9356caf..5b9dc82 100644 --- a/client/src/api/services/studentManageService.ts +++ b/client/src/api/services/studentManageService.ts @@ -6,9 +6,9 @@ export const blockStudentService = async ( studentId: string, reason: string ) => { - console.log(reason) const response = await api.patch( - `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${studentId}`,{reason} + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${studentId}`, + { reason } ); return response.data; }; @@ -29,3 +29,10 @@ export const getAllStudentsService = async (endpoint: string) => { ); return response.data; }; + +export const getAllBlockedStudentsService = async (endpoint: string) => { + const response = await api.get( + `${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}` + ); + return response.data; +}; diff --git a/client/src/components/pages/InstructorManagement/ViewMoreInstructorRequest.tsx b/client/src/components/pages/InstructorManagement/ViewMoreInstructorRequest.tsx index 72cf446..c7428f7 100644 --- a/client/src/components/pages/InstructorManagement/ViewMoreInstructorRequest.tsx +++ b/client/src/components/pages/InstructorManagement/ViewMoreInstructorRequest.tsx @@ -10,7 +10,7 @@ import { InstructorApiResponse } from "../../../api/types/apiResponses/apiRespon -const ViewMoreInstructorRequest: React.FC = () => { + const ViewMoreInstructorRequest: React.FC = () => { const { id } = useParams(); const [instructor, setInstructor] = useState(); const [open, setOpen] = useState(false) diff --git a/client/src/components/pages/studentManagement/BlockedStudents.tsx b/client/src/components/pages/studentManagement/BlockedStudents.tsx index b7c492f..e4fabd8 100644 --- a/client/src/components/pages/studentManagement/BlockedStudents.tsx +++ b/client/src/components/pages/studentManagement/BlockedStudents.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import { PencilIcon } from "@heroicons/react/24/solid"; -import { - MagnifyingGlassIcon, -} from "@heroicons/react/24/outline"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { Card, CardHeader, @@ -16,20 +14,25 @@ import { Tooltip, Input, } from "@material-tailwind/react"; -import { - unblockInstructors, -} from "../../../api/endpoints/instructorManagement"; +import { unblockInstructors } from "../../../api/endpoints/instructorManagement"; import { toast } from "react-toastify"; import { formatDate } from "../../../utils/helpers"; import usePagination from "../../../hooks/usePagination"; -import { getBlockedInstructors } from "../../../api/endpoints/instructorManagement"; +import { + getAllBlockedStudents, + unblockStudent, +} from "../../../api/endpoints/studentManagement"; const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions", ""]; -const BlockedStudents: React.FC = () => { - const [instructors, setInstructors] = useState([]); - const [updated, setUpdated] = useState(false); - const ITEMS_PER_PAGE = 6; +interface Props { + updated:boolean; + setUpdated:(val:boolean)=>void +} +const BlockedStudents: React.FC = ({updated,setUpdated}) => { + const [students, setStudents] = useState([]); + // const [updated, setUpdated] = useState(false); + const ITEMS_PER_PAGE = 4; const { currentPage, totalPages, @@ -37,13 +40,13 @@ const BlockedStudents: React.FC = () => { goToPage, goToPreviousPage, goToNextPage, - } = usePagination(instructors, ITEMS_PER_PAGE); + } = usePagination(students, ITEMS_PER_PAGE); const fetchBlockedInstructors = async () => { try { - const response = await getBlockedInstructors(); - setInstructors(response?.data?.data); + const response = await getAllBlockedStudents(); + setStudents(response?.data); } catch (error: any) { - toast.error(error.data.message, { + toast.error(error?.data?.message, { position: toast.POSITION.BOTTOM_RIGHT, }); } @@ -52,15 +55,15 @@ const BlockedStudents: React.FC = () => { fetchBlockedInstructors(); }, [updated]); - const handleUnblock = async (instructorId: string) => { + const handleUnblock = async (studentId: string) => { try { - const response = await unblockInstructors(instructorId); - toast.success(response.data.message, { + const response = await unblockStudent(studentId); + toast.success(response?.message, { position: toast.POSITION.BOTTOM_RIGHT, }); setUpdated(!updated); } catch (error: any) { - toast.error(error.data.message, { + toast.error(error?.message, { position: toast.POSITION.BOTTOM_RIGHT, }); } @@ -107,107 +110,105 @@ const BlockedStudents: React.FC = () => { ))} - - {currentData.map( - ( - { - _id, - img, - firstName, - lastName, - email, - dateJoined, - isBlocked, - }, - index - ) => { - const isLast = index === instructors.length - 1; - const classes = isLast - ? "p-4" - : "p-4 border-b border-blue-gray-50"; + + {currentData.length === 0 ? ( + + + No blocked students found + + + ) : ( + currentData.map( + ( + { + _id, + img, + firstName, + lastName, + email, + dateJoined, + isBlocked, + }, + index + ) => { + const isLast = index === students?.length - 1; + const classes = isLast + ? "p-4" + : "p-4 border-b border-blue-gray-50"; - return ( - - -
- + return ( + + +
+ + + {`${firstName} ${lastName}`} + +
+ + + + {email} + + + - {`${firstName} ${lastName}`} + {formatDate(dateJoined)} -
- - - - {email} - - - - - {formatDate(dateJoined)} - - - -
- -
- - -
- {isBlocked ? ( -
- -
- ) : ( -
- -
- )} -
- - - ); - } + + +
+ +
+ + +
+ {isBlocked ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ + + ); + } + ) )} diff --git a/client/src/components/pages/studentManagement/StudentsTab.tsx b/client/src/components/pages/studentManagement/StudentsTab.tsx index bb5542b..48f043b 100644 --- a/client/src/components/pages/studentManagement/StudentsTab.tsx +++ b/client/src/components/pages/studentManagement/StudentsTab.tsx @@ -22,6 +22,7 @@ interface TabData { export default function StudentsTab() { const [activeTab, setActiveTab] = useState("all"); + const [updated,setUpdated] = useState(false) const handleTabChange = (value: string) => { setActiveTab(value); @@ -41,20 +42,20 @@ export default function StudentsTab() { ]; const tabComponents: { [key: string]: JSX.Element } = { - all: , // Replace with the component you want to show for "All students" - blocked: , // Replace with the component you want to show for "Blocked" + all: , // Replace with the component you want to show for "All students" + blocked: , // Replace with the component you want to show for "Blocked" // Add more components for other tabs if needed }; return ( - + {data.map(({ label, value, icon: Icon }) => ( - +
{label} -
+
))}
diff --git a/client/src/components/pages/studentManagement/ViewStudents.tsx b/client/src/components/pages/studentManagement/ViewStudents.tsx index a84f475..c13e157 100644 --- a/client/src/components/pages/studentManagement/ViewStudents.tsx +++ b/client/src/components/pages/studentManagement/ViewStudents.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from "react"; import { PencilIcon } from "@heroicons/react/24/solid"; import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { Dispatch,SetStateAction } from "react"; import { Card, CardHeader, @@ -28,10 +29,14 @@ import BlockStudentModal from "./BlockStudentModal"; const TABLE_HEAD = ["Name", "Email", "Date Joined", "Status", "Actions"]; -const ViewStudents: React.FC = () => { +interface Props { + updated:boolean; + setUpdated:Dispatch> +} +const ViewStudents: React.FC = ({updated,setUpdated}) => { const [students, setStudents] = useState([]); const [open, setOpen] = useState(false); - const [updated, setUpdated] = useState(false); + // const [updated, setUpdated] = useState(false); const [id, setId] = useState(""); const ITEMS_PER_PAGE = 4; const { @@ -46,7 +51,6 @@ const ViewStudents: React.FC = () => { const fetchStudents = async () => { try { const response = await getAllStudents(); - console.log(response); setStudents(response?.data); } catch (error: any) { toast.error(error.data.message, { @@ -72,7 +76,7 @@ const ViewStudents: React.FC = () => { } }; return ( - + {open && ( import("./components/pages/Course/ListCourse") ); diff --git a/server/src/adapters/controllers/studentController.ts b/server/src/adapters/controllers/studentController.ts index 776ac2c..e3842dc 100644 --- a/server/src/adapters/controllers/studentController.ts +++ b/server/src/adapters/controllers/studentController.ts @@ -17,7 +17,7 @@ import { } from '../../types/studentInterface'; import { CloudServiceInterface } from '../../app/services/cloudServiceInterface'; import { CloudServiceImpl } from '../../frameworks/services/s3CloudService'; -import { blockStudentU, getAllStudentsU, unblockStudentU } from '../../app/usecases/management/studentManagement'; +import { blockStudentU, getAllBlockedStudentsU, getAllStudentsU, unblockStudentU } from '../../app/usecases/management/studentManagement'; const studentController = ( authServiceInterface: AuthServiceInterface, @@ -38,7 +38,7 @@ const studentController = ( const studentId: string | undefined = req.user?.Id; await changePasswordU( studentId, - passwordInfo, + passwordInfo, authService, dbRepositoryStudent ); @@ -113,7 +113,6 @@ const studentController = ( const blockStudent = asyncHandler(async (req:Request,res:Response)=>{ const studentId:string = req.params.studentId const reason:string = req.body.reason - console.log(reason) await blockStudentU(studentId,reason,dbRepositoryStudent) res.status(200).json({ status: 'success', @@ -132,6 +131,15 @@ const studentController = ( }); }) + const getAllBlockedStudents = asyncHandler(async(req:Request,res:Response)=>{ + const students = await getAllBlockedStudentsU(dbRepositoryStudent) + res.status(200).json({ + status: 'success', + message: 'Successfully unblocked student ', + data: students + }); + }) + return { changePassword, updateProfile, @@ -139,7 +147,8 @@ const studentController = ( getProfileUrl, blockStudent, unblockStudent, - getAllStudents + getAllStudents, + getAllBlockedStudents }; }; diff --git a/server/src/app/repositories/studentDbRepository.ts b/server/src/app/repositories/studentDbRepository.ts index 46cf6b2..4df1cab 100644 --- a/server/src/app/repositories/studentDbRepository.ts +++ b/server/src/app/repositories/studentDbRepository.ts @@ -25,6 +25,8 @@ export const studentDbRepository = ( const unblockStudent = async(id:string) => await repository.unblockStudent(id) + const getAllBlockedStudents =async () => await repository.getAllBlockedStudents() + return { addStudent, getStudentByEmail, @@ -34,6 +36,7 @@ export const studentDbRepository = ( getAllStudents, blockStudent, unblockStudent, + getAllBlockedStudents }; }; diff --git a/server/src/app/usecases/management/studentManagement.ts b/server/src/app/usecases/management/studentManagement.ts index 2a385d7..675a221 100644 --- a/server/src/app/usecases/management/studentManagement.ts +++ b/server/src/app/usecases/management/studentManagement.ts @@ -11,14 +11,17 @@ export const getAllStudentsU = async ( export const blockStudentU = async ( studentId: string, - reason:string, + reason: string, studentRepository: ReturnType ) => { if (!studentId) { throw new AppError('Invalid student details', HttpStatusCodes.BAD_REQUEST); } if (!reason) { - throw new AppError('Please give a reason to block a student', HttpStatusCodes.BAD_REQUEST); + throw new AppError( + 'Please give a reason to block a student', + HttpStatusCodes.BAD_REQUEST + ); } const student = await studentRepository.getStudent(studentId); if (student?.isBlocked) { @@ -27,7 +30,7 @@ export const blockStudentU = async ( HttpStatusCodes.CONFLICT ); } - await studentRepository.blockStudent(studentId,reason); + await studentRepository.blockStudent(studentId, reason); }; export const unblockStudentU = async ( @@ -39,3 +42,11 @@ export const unblockStudentU = async ( } await studentRepository.unblockStudent(studentId); }; + + +export const getAllBlockedStudentsU = async ( + studentRepository: ReturnType +) => { + const blockedStudents = await studentRepository.getAllBlockedStudents(); + return blockedStudents; +}; diff --git a/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts b/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts index 3e0f82b..3078a21 100644 --- a/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts +++ b/server/src/frameworks/database/mongodb/repositories/studentsRepoMongoDb.ts @@ -54,6 +54,11 @@ export const studentRepositoryMongoDB = () => { ); }; + const getAllBlockedStudents = async ()=>{ + const blockedStudents = await Student.find({isBlocked:true}) + return blockedStudents + } + return { addStudent, getStudentByEmail, @@ -62,7 +67,8 @@ export const studentRepositoryMongoDB = () => { updateProfile, getAllStudents, blockStudent, - unblockStudent + unblockStudent, + getAllBlockedStudents }; }; diff --git a/server/src/frameworks/webserver/routes/student.ts b/server/src/frameworks/webserver/routes/student.ts index 2e0c10c..c2d9c1c 100644 --- a/server/src/frameworks/webserver/routes/student.ts +++ b/server/src/frameworks/webserver/routes/student.ts @@ -32,6 +32,8 @@ const studentRouter = () => { router.patch('/unblock-student/:studentId',controller.unblockStudent) + router.get('/get-all-blocked-students',controller.getAllBlockedStudents) + return router; }; export default studentRouter;