From 60bb1cc263f5e152b2af4fa0e74a43737cc9d21b Mon Sep 17 00:00:00 2001 From: ngecu Date: Wed, 27 Mar 2024 13:11:59 +0300 Subject: [PATCH 1/7] student results page resolved --- backend/controllers/examResultController.js | 24 ++ backend/requests/examResults.http | 12 + backend/routes/examResultRoutes.js | 6 +- frontend/src/actions/examResultActions.jsx | 34 ++ .../src/constants/examResultConstants.jsx | 4 + frontend/src/reducers/examResultReducers.jsx | 16 + .../Parent/parentStudentExamResultsScreen.jsx | 317 ++++++++++++++++++ .../Student/studentExamResultScreen.jsx | 135 +++----- frontend/src/store.jsx | 2 + 9 files changed, 458 insertions(+), 92 deletions(-) create mode 100644 backend/requests/examResults.http create mode 100644 frontend/src/screens/Parent/parentStudentExamResultsScreen.jsx diff --git a/backend/controllers/examResultController.js b/backend/controllers/examResultController.js index dac77e5..5035619 100644 --- a/backend/controllers/examResultController.js +++ b/backend/controllers/examResultController.js @@ -38,6 +38,30 @@ export const getExamResultById = asyncHandler(async (req, res) => { } }); +// @desc Get a single exam result by ID +// @route GET /api/exam-results/:id +// @access Public +export const getExamResultByStudents = asyncHandler(async (req, res) => { + console.log(req.body); + const {students} = req.body; + + try { + const examResults = []; + for (const studentId of students) { + console.log("Student ID: ", studentId); + const examResult = await ExamResult.find({ student: studentId }).populate('student exam'); + examResults.push(...examResult); + } + + res.json(examResults); + } catch (error) { + + res.status(500).json({ message: `Server Error : ${error}` }); + } +}); + + + // @desc Update an existing exam result // @route PUT /api/exam-results/:id // @access Private diff --git a/backend/requests/examResults.http b/backend/requests/examResults.http new file mode 100644 index 0000000..7f832c6 --- /dev/null +++ b/backend/requests/examResults.http @@ -0,0 +1,12 @@ +GET http://localhost:5000/api/exam-results +Content-Type: application/json +Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MDE2Zjg2MWYxMzBmYzBhY2QxNDRiYSIsImlhdCI6MTcxMTUyNzYwMiwiZXhwIjoxNzE0MTE5NjAyfQ.v4BwGwcund1mLtOp3NOwceH0GN4OOEnEeQy3ZbkD1h8 + +### +POST http://localhost:5000/api/exam-results/student/ +Content-Type: application/json +Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MDE2Zjg2MWYxMzBmYzBhY2QxNDRiYSIsImlhdCI6MTcxMTUyNzYwMiwiZXhwIjoxNzE0MTE5NjAyfQ.v4BwGwcund1mLtOp3NOwceH0GN4OOEnEeQy3ZbkD1h8 + +{ + "students":["66016f851f130fc0acd144b6"] +} \ No newline at end of file diff --git a/backend/routes/examResultRoutes.js b/backend/routes/examResultRoutes.js index 90a2011..157bd69 100644 --- a/backend/routes/examResultRoutes.js +++ b/backend/routes/examResultRoutes.js @@ -1,14 +1,14 @@ import express from 'express' -import { createExam, deleteExam, getAllExams, getExamByCourse, getExamById, updateExam } from '../controllers/examController.js' -import { createExamResult, deleteExamResult, getAllExamResults, getExamResultById, updateExamResult } from '../controllers/examResultController.js' +import { createExamResult, deleteExamResult, getAllExamResults, getExamResultById, getExamResultByStudents, updateExamResult } from '../controllers/examResultController.js' const router = express.Router() import { protect, admin } from '../middleware/authMiddleware.js' -router.route('/').post(protect, createExamResult) router.route('/').get(protect, getAllExamResults) router.route('/:id').get(protect, getExamResultById) +router.route('/student').post(protect, getExamResultByStudents) router.route('/:id').put(protect, updateExamResult) router.route('/:id').delete(protect, deleteExamResult) +router.route('/').post(protect, createExamResult) export default router diff --git a/frontend/src/actions/examResultActions.jsx b/frontend/src/actions/examResultActions.jsx index 0118f6e..17b5c26 100644 --- a/frontend/src/actions/examResultActions.jsx +++ b/frontend/src/actions/examResultActions.jsx @@ -15,6 +15,9 @@ import { EXAM_RESULT_DELETE_REQUEST, EXAM_RESULT_DELETE_SUCCESS, EXAM_RESULT_DELETE_FAIL, + GET_EXAM_RESULTS_BY_STUDENT_REQUEST, + GET_EXAM_RESULTS_BY_STUDENT_SUCCESS, + GET_EXAM_RESULTS_BY_STUDENT_FAIL, } from '../constants/examResultConstants'; const base_url = `http://localhost:5000/api/exam-results`; @@ -172,4 +175,35 @@ export const deleteExamResult = (examResultId) => async (dispatch, getState) => : error.message, }); } +}; + +export const getExamResultsByStudents = (studentIds) => async (dispatch, getState) => { + try { + dispatch({ type: GET_EXAM_RESULTS_BY_STUDENT_REQUEST }); + + const { + userLogin: { userInfo }, + } = getState(); + + const config = { + headers: { + Authorization: `Bearer ${userInfo.token}`, + }, + }; + + const { data } = await axios.post(`${base_url}/student/`,{students:studentIds}, config); + + dispatch({ + type: GET_EXAM_RESULTS_BY_STUDENT_SUCCESS, + payload: data, + }); + } catch (error) { + dispatch({ + type: GET_EXAM_RESULTS_BY_STUDENT_FAIL, + payload: + error.response && error.response.data.message + ? error.response.data.message + : error.message, + }); + } }; \ No newline at end of file diff --git a/frontend/src/constants/examResultConstants.jsx b/frontend/src/constants/examResultConstants.jsx index 88fb7f1..9c1d5d4 100644 --- a/frontend/src/constants/examResultConstants.jsx +++ b/frontend/src/constants/examResultConstants.jsx @@ -19,3 +19,7 @@ export const EXAM_RESULT_UPDATE_FAIL = 'EXAM_RESULT_UPDATE_FAIL'; export const EXAM_RESULT_DELETE_REQUEST = 'EXAM_RESULT_DELETE_REQUEST'; export const EXAM_RESULT_DELETE_SUCCESS = 'EXAM_RESULT_DELETE_SUCCESS'; export const EXAM_RESULT_DELETE_FAIL = 'EXAM_RESULT_DELETE_FAIL'; + +export const GET_EXAM_RESULTS_BY_STUDENT_REQUEST = 'GET_EXAM_RESULTS_BY_STUDENT_REQUEST'; +export const GET_EXAM_RESULTS_BY_STUDENT_SUCCESS = 'GET_EXAM_RESULTS_BY_STUDENT_SUCCESS'; +export const GET_EXAM_RESULTS_BY_STUDENT_FAIL = 'GET_EXAM_RESULTS_BY_STUDENT_FAIL'; \ No newline at end of file diff --git a/frontend/src/reducers/examResultReducers.jsx b/frontend/src/reducers/examResultReducers.jsx index 4a2f346..2a90b1f 100644 --- a/frontend/src/reducers/examResultReducers.jsx +++ b/frontend/src/reducers/examResultReducers.jsx @@ -16,6 +16,9 @@ import { EXAM_RESULT_DELETE_REQUEST, EXAM_RESULT_DELETE_SUCCESS, EXAM_RESULT_DELETE_FAIL, + GET_EXAM_RESULTS_BY_STUDENT_REQUEST, + GET_EXAM_RESULTS_BY_STUDENT_SUCCESS, + GET_EXAM_RESULTS_BY_STUDENT_FAIL, } from '../constants/examResultConstants'; const initialState = { @@ -49,6 +52,19 @@ import { return state; } }; + + export const examResultsByStudentsReducer = (state = { examResults: [] }, action) => { + switch (action.type) { + case GET_EXAM_RESULTS_BY_STUDENT_REQUEST: + return { loading: true, success: false, error: null }; + case GET_EXAM_RESULTS_BY_STUDENT_SUCCESS: + return { loading: false, success: true, error: null, examResults: action.payload }; + case GET_EXAM_RESULTS_BY_STUDENT_FAIL: + return { loading: false, success: false, error: action.payload, examResults: [] }; + default: + return state; + } + }; export const getExamResultDetailsReducer = (state = { examResult: {} }, action) => { switch (action.type) { diff --git a/frontend/src/screens/Parent/parentStudentExamResultsScreen.jsx b/frontend/src/screens/Parent/parentStudentExamResultsScreen.jsx new file mode 100644 index 0000000..50a9270 --- /dev/null +++ b/frontend/src/screens/Parent/parentStudentExamResultsScreen.jsx @@ -0,0 +1,317 @@ +import React, { useState, useEffect } from 'react'; +import { Row, Col, ListGroup, Container, Button } from 'react-bootstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import Message from '../../components/Message'; +import Loader from '../../components/Loader'; +import { Link, useLocation } from 'react-router-dom'; +import { useRouteMatch } from 'react-router-dom'; +import { useHistory } from 'react-router-dom/cjs/react-router-dom.min'; +import Sidebar from './components/Sidebar' +import { Calendar, momentLocalizer } from 'react-big-calendar' +import moment from 'moment' +import 'react-big-calendar/lib/css/react-big-calendar.css'; +import { getFeesByStudent } from '../../actions/feeActions'; +import { Modal, Form, Input, DatePicker, Select,Table } from 'antd'; +import { createPaymentTransaction, initiateStkPush, listPaymentTransactionsByFee } from '../../actions/paymentActions'; +import {v4} from 'uuid' +import { NavLink } from 'react-router-dom'; +import Topbar from './components/Topbar'; +import { Badge } from 'react-bootstrap'; + + +const localizer = momentLocalizer(moment) +const { Option } = Select; + +const parentFeeScreen = () => { + const [isMpesa, setIsMpesa] = useState(true); + + + const [isModalOpen, setIsModalOpen] = useState(false); + const showModal = () => { + setIsModalOpen(true); + }; + const handleOk = () => { + setIsModalOpen(false); + }; + const handleCancel = () => { + setIsModalOpen(false); + }; + + + + const location = useLocation(); + const { pathname } = location; + + + + const dispatch = useDispatch(); + + + const userLogin = useSelector((state) => state.userLogin); + const { userInfo } = userLogin; + + const feesByStudent = useSelector((state) => state.getFeesByStudent); // Assuming you have a fees reducer + const { loading, error, fees } = feesByStudent; + + const paymentTransactionsByFee = useSelector((state) => state.paymentTransactionByFee); + const { loading: paymentTransactionsLoading, error: paymentTransactionsError, paymentTransactions } = paymentTransactionsByFee; + + + console.log("fees is ",fees) + const logoutHandler = () => { + dispatch(logout()); + }; + + + useEffect(() => { + if (userInfo && userInfo._id) { + dispatch(getFeesByStudent(userInfo.userData._id)); // Dispatch the action with the student's ID + } + }, [dispatch, userInfo]); + + useEffect(() => { + if (fees && fees._id) { + + dispatch(listPaymentTransactionsByFee(fees._id)); + } + }, [dispatch, fees,userInfo]); + + const [form] = Form.useForm(); + + const onFinish = (values) => { + // Handle the form submission, e.g., send the payment details to the backend + values.schoolFees = fees._id + values.transactionId = v4() + + // makePayment(values); + + if(values.paymentMethod == "mpesa"){ + dispatch(initiateStkPush(values)) + } + else{ + + dispatch(createPaymentTransaction(values)) + + } + }; + + + const columns = [ + { + title: 'Transaction ID', + dataIndex: 'transactionId', + key: 'transactionId', + }, + { + title: 'Amount Paid', + dataIndex: 'amountPaid', + key: 'amountPaid', + }, + { + title: 'approved', + dataIndex: 'approved', + key: 'approved', + render: (approved) => ( + + {approved ? 'Approved' : 'Not Approved'} + + ), + }, + { + title: 'Amount Remaining', + dataIndex: 'amountRemaining', + key: 'amountRemaining', + }, + { + title: 'Payment Method', + dataIndex: 'paymentMethod', + key: 'paymentMethod', + }, + { + title: 'Date', + dataIndex: 'date', + key: 'date', + }, + ]; +// Calculate the total amount paid from transactions where approved is true +const totalAmountPaid = paymentTransactions + .filter(transaction => transaction.approved) // Filter transactions with approved === true + .reduce((total, transaction) => total + transaction.amount, 0); // Sum the amounts + + + const data = paymentTransactions.map((transaction) => ({ + key: transaction._id, + transactionId: transaction.transactionId, + amountPaid: transaction.amount, + approved: transaction.approved, + amountRemaining:(fees.amount - transaction.amount), + // amountRemaining: transaction.amountRemaining, + paymentMethod: transaction.paymentMethod, + date: moment(transaction.createdAt).format('YYYY-MM-DD'), + })); + + return ( +