├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── public └── vite.svg ├── src ├── App.tsx ├── assets │ └── react.svg ├── components │ ├── form │ │ ├── PHDatePicker.tsx │ │ ├── PHForm.tsx │ │ ├── PHInput.tsx │ │ ├── PHSelect.tsx │ │ ├── PHSelectWithWatch.tsx │ │ └── PHTimePicker.tsx │ └── layout │ │ ├── MainLayout.tsx │ │ ├── ProtectedRoute.tsx │ │ └── Sidebar.tsx ├── constants │ ├── global.ts │ └── semester.ts ├── index.css ├── main.tsx ├── pages │ ├── About.tsx │ ├── ChangePassword.tsx │ ├── Contact.tsx │ ├── Login.tsx │ ├── Register.tsx │ ├── admin │ │ ├── AdminDashboard.tsx │ │ ├── academicManagement │ │ │ ├── AcademicDepartment.tsx │ │ │ ├── AcademicFaculty.tsx │ │ │ ├── AcademicSemester.tsx │ │ │ ├── CreateAcademicDepartment.tsx │ │ │ ├── CreateAcademicFaculty.tsx │ │ │ └── CreateAcademicSemester.tsx │ │ ├── courseManagement │ │ │ ├── Courses.tsx │ │ │ ├── CreateCourse.tsx │ │ │ ├── OfferCourse.tsx │ │ │ ├── OfferedCourses.tsx │ │ │ ├── RegisteredSemesters.tsx │ │ │ └── SemesterRegistration.tsx │ │ └── userManagement │ │ │ ├── CreateAdmin.tsx │ │ │ ├── CreateFaculty.tsx │ │ │ ├── CreateStudent.tsx │ │ │ ├── StudentData.tsx │ │ │ ├── StudentDetails.tsx │ │ │ └── StudentUpdate.tsx │ ├── faculty │ │ ├── FacultyDashboard.tsx │ │ ├── MyCourses.tsx │ │ └── MyStudents.tsx │ └── student │ │ ├── MySchedule.tsx │ │ ├── OfferedCourse.tsx │ │ └── StudentDashboard.tsx ├── redux │ ├── api │ │ └── baseApi.ts │ ├── features │ │ ├── admin │ │ │ ├── academicManagement.api.ts │ │ │ ├── courseManagement.ts │ │ │ └── userManagement.api.ts │ │ ├── auth │ │ │ ├── authApi.ts │ │ │ └── authSlice.ts │ │ ├── counter │ │ │ ├── counterApi.ts │ │ │ └── counterSlice.ts │ │ ├── faculty │ │ │ └── facultyCourses.api.ts │ │ └── student │ │ │ └── studentCourseManagement.api.ts │ ├── hooks.ts │ └── store.ts ├── routes │ ├── admin.routes.tsx │ ├── faculty.routes.tsx │ ├── routes.tsx │ └── student.routes.tsx ├── schemas │ └── academicManagement.schema.ts ├── types │ ├── academicManagement.type.ts │ ├── courseManagement.type.ts │ ├── global.ts │ ├── index.ts │ ├── sidebar.type.ts │ ├── studentCourse.type.ts │ └── userManagement.type.ts ├── utils │ ├── routesGenerator.ts │ ├── sidebarItemsGenerator.tsx │ └── verifyToken.ts └── vite-env.d.ts ├── test.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/L2B2-PH-university-client/f84bb0541b56ff98c7506c1ac2832284cf4f7c68/.env.example -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | '@typescript-eslint/no-explicit-any': 'warn', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phum-client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@ant-design/icons": "^5.2.6", 14 | "@hookform/resolvers": "^3.3.4", 15 | "@reduxjs/toolkit": "^2.0.1", 16 | "antd": "^5.13.1", 17 | "jwt-decode": "^4.0.0", 18 | "moment": "^2.30.1", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-hook-form": "^7.49.3", 22 | "react-redux": "^9.1.0", 23 | "react-router-dom": "^6.21.2", 24 | "redux-persist": "^6.0.0", 25 | "sonner": "^1.3.1", 26 | "zod": "^3.22.4" 27 | }, 28 | "devDependencies": { 29 | "@types/react": "^18.2.43", 30 | "@types/react-dom": "^18.2.17", 31 | "@typescript-eslint/eslint-plugin": "^6.14.0", 32 | "@typescript-eslint/parser": "^6.14.0", 33 | "@vitejs/plugin-react": "^4.2.1", 34 | "eslint": "^8.55.0", 35 | "eslint-plugin-react-hooks": "^4.6.0", 36 | "eslint-plugin-react-refresh": "^0.4.5", 37 | "typescript": "^5.2.2", 38 | "vite": "^5.0.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import MainLayout from './components/layout/MainLayout'; 2 | import ProtectedRoute from './components/layout/ProtectedRoute'; 3 | 4 | function App() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/PHDatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { DatePicker, Form } from 'antd'; 2 | import { Controller } from 'react-hook-form'; 3 | 4 | type TDatePickerProps = { 5 | name: string; 6 | label?: string; 7 | }; 8 | 9 | const PHDatePicker = ({ name, label }: TDatePickerProps) => { 10 | return ( 11 |
12 | ( 15 | 16 | 17 | 18 | )} 19 | /> 20 |
21 | ); 22 | }; 23 | 24 | export default PHDatePicker; 25 | -------------------------------------------------------------------------------- /src/components/form/PHForm.tsx: -------------------------------------------------------------------------------- 1 | import { Form } from 'antd'; 2 | import { ReactNode } from 'react'; 3 | import { 4 | FieldValues, 5 | FormProvider, 6 | SubmitHandler, 7 | useForm, 8 | } from 'react-hook-form'; 9 | 10 | type TFormConfig = { 11 | defaultValues?: Record; 12 | resolver?: any; 13 | }; 14 | 15 | type TFormProps = { 16 | onSubmit: SubmitHandler; 17 | children: ReactNode; 18 | } & TFormConfig; 19 | 20 | const PHForm = ({ 21 | onSubmit, 22 | children, 23 | defaultValues, 24 | resolver, 25 | }: TFormProps) => { 26 | const formConfig: TFormConfig = {}; 27 | 28 | if (defaultValues) { 29 | formConfig['defaultValues'] = defaultValues; 30 | } 31 | 32 | if (resolver) { 33 | formConfig['resolver'] = resolver; 34 | } 35 | 36 | const methods = useForm(formConfig); 37 | 38 | const submit: SubmitHandler = (data) => { 39 | onSubmit(data); 40 | methods.reset(); 41 | }; 42 | 43 | return ( 44 | 45 |
46 | {children} 47 |
48 |
49 | ); 50 | }; 51 | 52 | export default PHForm; 53 | -------------------------------------------------------------------------------- /src/components/form/PHInput.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input } from 'antd'; 2 | import { Controller } from 'react-hook-form'; 3 | 4 | type TInputProps = { 5 | type: string; 6 | name: string; 7 | label?: string; 8 | disabled?: boolean; 9 | }; 10 | 11 | const PHInput = ({ type, name, label, disabled }: TInputProps) => { 12 | return ( 13 |
14 | ( 17 | 18 | 25 | 26 | )} 27 | /> 28 |
29 | ); 30 | }; 31 | 32 | export default PHInput; 33 | -------------------------------------------------------------------------------- /src/components/form/PHSelect.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Select } from 'antd'; 2 | import { Controller } from 'react-hook-form'; 3 | 4 | type TPHSelectProps = { 5 | label: string; 6 | name: string; 7 | options: { value: string; label: string; disabled?: boolean }[] | undefined; 8 | disabled?: boolean; 9 | mode?: 'multiple' | undefined; 10 | }; 11 | 12 | const PHSelect = ({ label, name, options, disabled, mode }: TPHSelectProps) => { 13 | return ( 14 | ( 17 | 18 | 45 | {error && {error.message}} 46 | 47 | )} 48 | /> 49 | ); 50 | }; 51 | 52 | export default PHSelectWithWatch; 53 | -------------------------------------------------------------------------------- /src/components/form/PHTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { Controller, useFormContext } from 'react-hook-form'; 2 | import { Form, TimePicker } from 'antd'; 3 | 4 | type TPHDatePicker = { 5 | name: string; 6 | label: string; 7 | }; 8 | 9 | const PHTimePicker = ({ name, label }: TPHDatePicker) => { 10 | const { control } = useFormContext(); 11 | 12 | return ( 13 |
14 | ( 18 | <> 19 | 20 | 26 | {error && {error.message}} 27 | 28 | 29 | )} 30 | > 31 |
32 | ); 33 | }; 34 | 35 | export default PHTimePicker; 36 | -------------------------------------------------------------------------------- /src/components/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Layout } from 'antd'; 2 | import Sidebar from './Sidebar'; 3 | import { useAppDispatch } from '../../redux/hooks'; 4 | import { logout } from '../../redux/features/auth/authSlice'; 5 | import { Outlet } from 'react-router-dom'; 6 | const { Header, Content } = Layout; 7 | 8 | const MainLayout = () => { 9 | const dispatch = useAppDispatch(); 10 | 11 | const handleLogout = () => { 12 | dispatch(logout()); 13 | }; 14 | 15 | return ( 16 | 17 | 18 | 19 |
20 | {' '} 21 |
22 | 23 |
29 | 30 |
31 |
32 |
33 |
34 | ); 35 | }; 36 | 37 | export default MainLayout; 38 | -------------------------------------------------------------------------------- /src/components/layout/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { useAppDispatch, useAppSelector } from '../../redux/hooks'; 3 | import { 4 | logout, 5 | selectCurrentUser, 6 | useCurrentToken, 7 | } from '../../redux/features/auth/authSlice'; 8 | import { Navigate } from 'react-router-dom'; 9 | import { verifyToken } from '../../utils/verifyToken'; 10 | 11 | type TProtectedRoute = { 12 | children: ReactNode; 13 | role: string | undefined; 14 | }; 15 | 16 | const ProtectedRoute = ({ children, role }: TProtectedRoute) => { 17 | const token = useAppSelector(useCurrentToken); 18 | 19 | let user; 20 | 21 | if (token) { 22 | user = verifyToken(token); 23 | } 24 | 25 | const dispatch = useAppDispatch(); 26 | 27 | if (role !== undefined && role !== user?.role) { 28 | dispatch(logout()); 29 | return ; 30 | } 31 | if (!token) { 32 | return ; 33 | } 34 | 35 | return children; 36 | }; 37 | 38 | export default ProtectedRoute; 39 | -------------------------------------------------------------------------------- /src/components/layout/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Layout, Menu } from 'antd'; 2 | import { sidebarItemsGenerator } from '../../utils/sidebarItemsGenerator'; 3 | import { adminPaths } from '../../routes/admin.routes'; 4 | import { facultyPaths } from '../../routes/faculty.routes'; 5 | import { studentPaths } from '../../routes/student.routes'; 6 | import { useAppSelector } from '../../redux/hooks'; 7 | import { 8 | TUser, 9 | selectCurrentUser, 10 | useCurrentToken, 11 | } from '../../redux/features/auth/authSlice'; 12 | import { verifyToken } from '../../utils/verifyToken'; 13 | 14 | const { Sider } = Layout; 15 | 16 | const userRole = { 17 | ADMIN: 'admin', 18 | FACULTY: 'faculty', 19 | STUDENT: 'student', 20 | }; 21 | 22 | const Sidebar = () => { 23 | const token = useAppSelector(useCurrentToken); 24 | 25 | let user; 26 | 27 | if (token) { 28 | user = verifyToken(token); 29 | } 30 | 31 | let sidebarItems; 32 | 33 | switch ((user as TUser)!.role) { 34 | case userRole.ADMIN: 35 | sidebarItems = sidebarItemsGenerator(adminPaths, userRole.ADMIN); 36 | break; 37 | case userRole.FACULTY: 38 | sidebarItems = sidebarItemsGenerator(facultyPaths, userRole.FACULTY); 39 | break; 40 | case userRole.STUDENT: 41 | sidebarItems = sidebarItemsGenerator(studentPaths, userRole.STUDENT); 42 | break; 43 | 44 | default: 45 | break; 46 | } 47 | 48 | return ( 49 | 54 |
63 |

PH Uni

64 |
65 | 71 | 72 | ); 73 | }; 74 | 75 | export default Sidebar; 76 | -------------------------------------------------------------------------------- /src/constants/global.ts: -------------------------------------------------------------------------------- 1 | export const monthNames = [ 2 | 'January', 3 | 'February', 4 | 'March', 5 | 'April', 6 | 'May', 7 | 'June', 8 | 'July', 9 | 'August', 10 | 'September', 11 | 'October', 12 | 'November', 13 | 'December', 14 | ]; 15 | 16 | const weekdays = ['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; 17 | 18 | export const genders = ['Male', 'Female', 'Other']; 19 | 20 | export const bloodGroups = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-']; 21 | 22 | export const monthOptions = monthNames.map((item) => ({ 23 | value: item, 24 | label: item, 25 | })); 26 | 27 | export const genderOptions = genders.map((item) => ({ 28 | value: item.toLowerCase(), 29 | label: item, 30 | })); 31 | 32 | export const bloodGroupOptions = bloodGroups.map((item) => ({ 33 | value: item, 34 | label: item, 35 | })); 36 | 37 | export const weekDaysOptions = weekdays.map((item) => ({ 38 | value: item, 39 | label: item, 40 | })); 41 | -------------------------------------------------------------------------------- /src/constants/semester.ts: -------------------------------------------------------------------------------- 1 | export const semesterOptions = [ 2 | { value: '01', label: 'Autumn' }, 3 | { value: '02', label: 'Summer' }, 4 | { value: '03', label: 'Fall' }, 5 | ]; 6 | 7 | export const semesterStatusOptions = [ 8 | { value: 'UPCOMING', label: 'Upcoming' }, 9 | { value: 'ONGOING', label: 'Ongoing' }, 10 | { value: 'ENDED', label: 'Ended' }, 11 | ]; 12 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import { RouterProvider } from 'react-router-dom'; 5 | import router from './routes/routes.tsx'; 6 | import { Provider } from 'react-redux'; 7 | import { persistor, store } from './redux/store.ts'; 8 | import { PersistGate } from 'redux-persist/integration/react'; 9 | import { Toaster } from 'sonner'; 10 | 11 | ReactDOM.createRoot(document.getElementById('root')!).render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/pages/About.tsx: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return ( 3 |
4 |

This is About component

5 |
6 | ); 7 | }; 8 | 9 | export default About; 10 | -------------------------------------------------------------------------------- /src/pages/ChangePassword.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Row } from 'antd'; 2 | import PHForm from '../components/form/PHForm'; 3 | import PHInput from '../components/form/PHInput'; 4 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 5 | import { useChangePasswordMutation } from '../redux/features/admin/userManagement.api'; 6 | import { TResponse } from '../types'; 7 | import { useAppDispatch } from '../redux/hooks'; 8 | import { logout } from '../redux/features/auth/authSlice'; 9 | import { Navigate, useNavigate } from 'react-router-dom'; 10 | 11 | const ChangePassword = () => { 12 | const [changePassword] = useChangePasswordMutation(); 13 | const dispatch = useAppDispatch(); 14 | const navigate = useNavigate(); 15 | 16 | const onSubmit: SubmitHandler = async (data) => { 17 | console.log(data); 18 | 19 | const res = (await changePassword(data)) as TResponse; 20 | console.log(res?.data?.success); 21 | if (res?.data?.success) { 22 | dispatch(logout()); 23 | navigate('/login'); 24 | } 25 | }; 26 | 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default ChangePassword; 39 | -------------------------------------------------------------------------------- /src/pages/Contact.tsx: -------------------------------------------------------------------------------- 1 | const Contact = () => { 2 | return ( 3 |
4 |

This is Contact component

5 |
6 | ); 7 | }; 8 | 9 | export default Contact; 10 | -------------------------------------------------------------------------------- /src/pages/Login.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Row } from 'antd'; 2 | import { FieldValues } from 'react-hook-form'; 3 | import { useLoginMutation } from '../redux/features/auth/authApi'; 4 | import { useAppDispatch } from '../redux/hooks'; 5 | import { TUser, setUser } from '../redux/features/auth/authSlice'; 6 | import { verifyToken } from '../utils/verifyToken'; 7 | import { useNavigate } from 'react-router-dom'; 8 | import { toast } from 'sonner'; 9 | import PHForm from '../components/form/PHForm'; 10 | import PHInput from '../components/form/PHInput'; 11 | 12 | const Login = () => { 13 | const navigate = useNavigate(); 14 | const dispatch = useAppDispatch(); 15 | 16 | const defaultValues = { 17 | userId: '2026010016', 18 | password: 'student123', 19 | }; 20 | 21 | const [login] = useLoginMutation(); 22 | 23 | const onSubmit = async (data: FieldValues) => { 24 | console.log(data); 25 | const toastId = toast.loading('Logging in'); 26 | 27 | try { 28 | const userInfo = { 29 | id: data.userId, 30 | password: data.password, 31 | }; 32 | const res = await login(userInfo).unwrap(); 33 | 34 | const user = verifyToken(res.data.accessToken) as TUser; 35 | dispatch(setUser({ user: user, token: res.data.accessToken })); 36 | toast.success('Logged in', { id: toastId, duration: 2000 }); 37 | 38 | if (res.data.needsPasswordChange) { 39 | navigate(`/change-password`); 40 | } else { 41 | navigate(`/${user.role}/dashboard`); 42 | } 43 | } catch (err) { 44 | toast.error('Something went wrong', { id: toastId, duration: 2000 }); 45 | } 46 | }; 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default Login; 60 | -------------------------------------------------------------------------------- /src/pages/Register.tsx: -------------------------------------------------------------------------------- 1 | const Register = () => { 2 | return ( 3 |
4 |

This is Register component

5 |
6 | ); 7 | }; 8 | 9 | export default Register; 10 | -------------------------------------------------------------------------------- /src/pages/admin/AdminDashboard.tsx: -------------------------------------------------------------------------------- 1 | const AdminDashboard = () => { 2 | return ( 3 |
4 |

This is AdminDashboard component

5 |
6 | ); 7 | }; 8 | 9 | export default AdminDashboard; 10 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/AcademicDepartment.tsx: -------------------------------------------------------------------------------- 1 | const AcademicDepartment = () => { 2 | return ( 3 |
4 |

This is AcademicDepartment component

5 |
6 | ); 7 | }; 8 | 9 | export default AcademicDepartment; 10 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/AcademicFaculty.tsx: -------------------------------------------------------------------------------- 1 | const AcademicFaculty = () => { 2 | return ( 3 |
4 |

This is AcademicFaculty component

5 |
6 | ); 7 | }; 8 | 9 | export default AcademicFaculty; 10 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/AcademicSemester.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Table, TableColumnsType, TableProps } from 'antd'; 2 | import { useGetAllSemestersQuery } from '../../../redux/features/admin/academicManagement.api'; 3 | import { TAcademicSemester } from '../../../types/academicManagement.type'; 4 | import { useState } from 'react'; 5 | import { TQueryParam } from '../../../types'; 6 | 7 | export type TTableData = Pick< 8 | TAcademicSemester, 9 | 'name' | 'year' | 'startMonth' | 'endMonth' 10 | >; 11 | 12 | const AcademicSemester = () => { 13 | const [params, setParams] = useState(undefined); 14 | const { 15 | data: semesterData, 16 | isLoading, 17 | isFetching, 18 | } = useGetAllSemestersQuery(params); 19 | 20 | console.log({ isLoading, isFetching }); 21 | 22 | const tableData = semesterData?.data?.map( 23 | ({ _id, name, startMonth, endMonth, year }) => ({ 24 | key: _id, 25 | name, 26 | startMonth, 27 | endMonth, 28 | year, 29 | }) 30 | ); 31 | 32 | const columns: TableColumnsType = [ 33 | { 34 | title: 'Name', 35 | key: 'name', 36 | dataIndex: 'name', 37 | filters: [ 38 | { 39 | text: 'Autumn', 40 | value: 'Autumn', 41 | }, 42 | { 43 | text: 'Fall', 44 | value: 'Fall', 45 | }, 46 | { 47 | text: 'Summer', 48 | value: 'Summer', 49 | }, 50 | ], 51 | }, 52 | { 53 | title: 'Year', 54 | key: 'year', 55 | dataIndex: 'year', 56 | filters: [ 57 | { 58 | text: '2024', 59 | value: '2024', 60 | }, 61 | { 62 | text: '2025', 63 | value: '2025', 64 | }, 65 | { 66 | text: '2026', 67 | value: '2026', 68 | }, 69 | ], 70 | }, 71 | { 72 | title: 'Start Month', 73 | key: 'startMonth', 74 | dataIndex: 'startMonth', 75 | }, 76 | { 77 | title: 'End Month', 78 | key: 'endMonth', 79 | dataIndex: 'endMonth', 80 | }, 81 | { 82 | title: 'Action', 83 | key: 'x', 84 | render: () => { 85 | return ( 86 |
87 | 88 |
89 | ); 90 | }, 91 | }, 92 | ]; 93 | 94 | const onChange: TableProps['onChange'] = ( 95 | _pagination, 96 | filters, 97 | _sorter, 98 | extra 99 | ) => { 100 | if (extra.action === 'filter') { 101 | const queryParams: TQueryParam[] = []; 102 | 103 | filters.name?.forEach((item) => 104 | queryParams.push({ name: 'name', value: item }) 105 | ); 106 | 107 | filters.year?.forEach((item) => 108 | queryParams.push({ name: 'year', value: item }) 109 | ); 110 | 111 | setParams(queryParams); 112 | } 113 | }; 114 | 115 | return ( 116 | 122 | ); 123 | }; 124 | 125 | export default AcademicSemester; 126 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/CreateAcademicDepartment.tsx: -------------------------------------------------------------------------------- 1 | const CreateAcademicDepartment = () => { 2 | return ( 3 |
4 |

This is CreateAcademicDepartment component

5 |
6 | ); 7 | }; 8 | 9 | export default CreateAcademicDepartment; 10 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/CreateAcademicFaculty.tsx: -------------------------------------------------------------------------------- 1 | const CreateAcademicFaculty = () => { 2 | return ( 3 |
4 |

This is CreateAcademicFaculty component

5 |
6 | ); 7 | }; 8 | 9 | export default CreateAcademicFaculty; 10 | -------------------------------------------------------------------------------- /src/pages/admin/academicManagement/CreateAcademicSemester.tsx: -------------------------------------------------------------------------------- 1 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 2 | import PHForm from '../../../components/form/PHForm'; 3 | import { Button, Col, Flex } from 'antd'; 4 | import PHSelect from '../../../components/form/PHSelect'; 5 | import { semesterOptions } from '../../../constants/semester'; 6 | import { monthOptions } from '../../../constants/global'; 7 | import { zodResolver } from '@hookform/resolvers/zod'; 8 | 9 | import { academicSemesterSchema } from '../../../schemas/academicManagement.schema'; 10 | import { useAddAcademicSemesterMutation } from '../../../redux/features/admin/academicManagement.api'; 11 | import { toast } from 'sonner'; 12 | import { TResponse } from '../../../types/global'; 13 | 14 | const currentYear = new Date().getFullYear(); 15 | const yearOptions = [0, 1, 2, 3, 4].map((number) => ({ 16 | value: String(currentYear + number), 17 | label: String(currentYear + number), 18 | })); 19 | 20 | const CreateAcademicSemester = () => { 21 | const [addAcademicSemester] = useAddAcademicSemesterMutation(); 22 | 23 | const onSubmit: SubmitHandler = async (data) => { 24 | const toastId = toast.loading('Creating...'); 25 | 26 | const name = semesterOptions[Number(data?.name) - 1]?.label; 27 | 28 | const semesterData = { 29 | name, 30 | code: data.name, 31 | year: data.year, 32 | startMonth: data.startMonth, 33 | endMonth: data.endMonth, 34 | }; 35 | 36 | try { 37 | const res = (await addAcademicSemester(semesterData)) as TResponse; 38 | console.log(res); 39 | if (res.error) { 40 | toast.error(res.error.data.message, { id: toastId }); 41 | } else { 42 | toast.success('Semester created', { id: toastId }); 43 | } 44 | } catch (err) { 45 | toast.error('Something went wrong', { id: toastId }); 46 | } 47 | }; 48 | 49 | return ( 50 | 51 | 52 | 56 | 57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ); 70 | }; 71 | 72 | export default CreateAcademicSemester; 73 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/Courses.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal, Table } from 'antd'; 2 | import { 3 | useAddFacultiesMutation, 4 | useGetAllCoursesQuery, 5 | } from '../../../redux/features/admin/courseManagement'; 6 | import { useState } from 'react'; 7 | import PHForm from '../../../components/form/PHForm'; 8 | import PHSelect from '../../../components/form/PHSelect'; 9 | import { useGetAcademicFacultiesQuery } from '../../../redux/features/admin/academicManagement.api'; 10 | import { useGetAllFacultiesQuery } from '../../../redux/features/admin/userManagement.api'; 11 | 12 | const Courses = () => { 13 | // const [params, setParams] = useState(undefined); 14 | 15 | const { data: courses, isFetching } = useGetAllCoursesQuery(undefined); 16 | 17 | const tableData = courses?.data?.map(({ _id, title, prefix, code }) => ({ 18 | key: _id, 19 | title, 20 | code: `${prefix}${code}`, 21 | })); 22 | 23 | const columns = [ 24 | { 25 | title: 'Title', 26 | key: 'title', 27 | dataIndex: 'title', 28 | }, 29 | { 30 | title: 'Code', 31 | key: 'code', 32 | dataIndex: 'code', 33 | }, 34 | { 35 | title: 'Action', 36 | key: 'x', 37 | render: (item) => { 38 | return ; 39 | }, 40 | }, 41 | ]; 42 | 43 | // const onChange: TableProps['onChange'] = ( 44 | // _pagination, 45 | // filters, 46 | // _sorter, 47 | // extra 48 | // ) => { 49 | // if (extra.action === 'filter') { 50 | // const queryParams: TQueryParam[] = []; 51 | // setParams(queryParams); 52 | // } 53 | // }; 54 | 55 | return ( 56 |
62 | ); 63 | }; 64 | 65 | const AddFacultyModal = ({ facultyInfo }) => { 66 | const [isModalOpen, setIsModalOpen] = useState(false); 67 | const { data: facultiesData } = useGetAllFacultiesQuery(undefined); 68 | const [addFaculties] = useAddFacultiesMutation(); 69 | 70 | const facultiesOption = facultiesData?.data?.map((item) => ({ 71 | value: item._id, 72 | label: item.fullName, 73 | })); 74 | 75 | const handleSubmit = (data) => { 76 | const facultyData = { 77 | courseId: facultyInfo.key, 78 | data, 79 | }; 80 | 81 | console.log(facultyData); 82 | 83 | addFaculties(facultyData); 84 | }; 85 | 86 | const showModal = () => { 87 | setIsModalOpen(true); 88 | }; 89 | 90 | const handleCancel = () => { 91 | setIsModalOpen(false); 92 | }; 93 | 94 | return ( 95 | <> 96 | 97 | 103 | 104 | 110 | 111 | 112 | 113 | 114 | ); 115 | }; 116 | 117 | export default Courses; 118 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/CreateCourse.tsx: -------------------------------------------------------------------------------- 1 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 2 | import PHForm from '../../../components/form/PHForm'; 3 | import { Button, Col, Flex } from 'antd'; 4 | import PHSelect from '../../../components/form/PHSelect'; 5 | import { toast } from 'sonner'; 6 | import PHInput from '../../../components/form/PHInput'; 7 | import { 8 | useAddCourseMutation, 9 | useGetAllCoursesQuery, 10 | } from '../../../redux/features/admin/courseManagement'; 11 | import { TResponse } from '../../../types'; 12 | 13 | const CreateCourse = () => { 14 | const [createCourse] = useAddCourseMutation(); 15 | const { data: courses } = useGetAllCoursesQuery(undefined); 16 | 17 | const preRequisiteCoursesOptions = courses?.data?.map((item) => ({ 18 | value: item._id, 19 | label: item.title, 20 | })); 21 | 22 | const onSubmit: SubmitHandler = async (data) => { 23 | const toastId = toast.loading('Creating...'); 24 | 25 | const courseData = { 26 | ...data, 27 | code: Number(data.code), 28 | credits: Number(data.credits), 29 | isDeleted: false, 30 | preRequisiteCourses: data.preRequisiteCourses 31 | ? data.preRequisiteCourses?.map((item) => ({ 32 | course: item, 33 | isDeleted: false, 34 | })) 35 | : [], 36 | }; 37 | 38 | console.log(courseData); 39 | 40 | try { 41 | const res = (await createCourse(courseData)) as TResponse; 42 | console.log(res); 43 | if (res.error) { 44 | toast.error(res.error.data.message, { id: toastId }); 45 | } else { 46 | toast.success('Semester created', { id: toastId }); 47 | } 48 | } catch (err) { 49 | toast.error('Something went wrong', { id: toastId }); 50 | } 51 | }; 52 | 53 | return ( 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default CreateCourse; 75 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/OfferCourse.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Flex } from 'antd'; 2 | import PHForm from '../../../components/form/PHForm'; 3 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 4 | import PHSelect from '../../../components/form/PHSelect'; 5 | import PHSelectWithWatch from '../../../components/form/PHSelectWithWatch'; 6 | 7 | import { useState } from 'react'; 8 | import PHInput from '../../../components/form/PHInput'; 9 | 10 | import moment from 'moment'; 11 | import { 12 | useCreateOfferedCourseMutation, 13 | useGetAllCoursesQuery, 14 | useGetAllRegisteredSemestersQuery, 15 | useGetCourseFacultiesQuery, 16 | } from '../../../redux/features/admin/courseManagement'; 17 | import { 18 | useGetAcademicDepartmentsQuery, 19 | useGetAcademicFacultiesQuery, 20 | } from '../../../redux/features/admin/academicManagement.api'; 21 | import { weekDaysOptions } from '../../../constants/global'; 22 | import PHTimePicker from '../../../components/form/PHTimePicker'; 23 | 24 | const OfferCourse = () => { 25 | const [courseId, setCourseId] = useState(''); 26 | 27 | const [addOfferedCourse] = useCreateOfferedCourseMutation(); 28 | 29 | const { data: semesterRegistrationData } = useGetAllRegisteredSemestersQuery([ 30 | { name: 'sort', value: 'year' }, 31 | { name: 'status', value: 'UPCOMING' }, 32 | ]); 33 | 34 | const { data: academicFacultyData } = useGetAcademicFacultiesQuery(undefined); 35 | 36 | const { data: academicDepartmentData } = 37 | useGetAcademicDepartmentsQuery(undefined); 38 | 39 | const { data: coursesData } = useGetAllCoursesQuery(undefined); 40 | 41 | const { data: facultiesData, isFetching: fetchingFaculties } = 42 | useGetCourseFacultiesQuery(courseId, { skip: !courseId }); 43 | 44 | const semesterRegistrationOptions = semesterRegistrationData?.data?.map( 45 | (item) => ({ 46 | value: item._id, 47 | label: `${item.academicSemester.name} ${item.academicSemester.year}`, 48 | }) 49 | ); 50 | 51 | const academicFacultyOptions = academicFacultyData?.data?.map((item) => ({ 52 | value: item._id, 53 | label: item.name, 54 | })); 55 | 56 | const academicDepartmentOptions = academicDepartmentData?.data?.map( 57 | (item) => ({ 58 | value: item._id, 59 | label: item.name, 60 | }) 61 | ); 62 | 63 | const courseOptions = coursesData?.data?.map((item) => ({ 64 | value: item._id, 65 | label: item.title, 66 | })); 67 | 68 | const facultiesOptions = facultiesData?.data?.faculties?.map((item) => ({ 69 | value: item._id, 70 | label: item.fullName, 71 | })); 72 | 73 | const onSubmit: SubmitHandler = async (data) => { 74 | const offeredCourseData = { 75 | ...data, 76 | maxCapacity: Number(data.maxCapacity), 77 | section: Number(data.section), 78 | startTime: moment(new Date(data.startTime)).format('HH:mm'), 79 | endTime: moment(new Date(data.endTime)).format('HH:mm'), 80 | }; 81 | 82 | const res = await addOfferedCourse(offeredCourseData); 83 | console.log(res); 84 | }; 85 | 86 | return ( 87 | 88 | 89 | 90 | 95 | 100 | 105 | 111 | 117 | 118 | 119 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | ); 133 | }; 134 | 135 | export default OfferCourse; 136 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/OfferedCourses.tsx: -------------------------------------------------------------------------------- 1 | const OfferedCourses = () => { 2 | return ( 3 |
4 |

This is OfferedCourses component

5 |
6 | ); 7 | }; 8 | 9 | export default OfferedCourses; 10 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/RegisteredSemesters.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Dropdown, Table, TableColumnsType, Tag } from 'antd'; 2 | import { 3 | useGetAllRegisteredSemestersQuery, 4 | useUpdateRegisteredSemesterMutation, 5 | } from '../../../redux/features/admin/courseManagement'; 6 | import moment from 'moment'; 7 | import { TSemester } from '../../../types'; 8 | import { useState } from 'react'; 9 | export type TTableData = Pick; 10 | 11 | const items = [ 12 | { 13 | label: 'Upcoming', 14 | key: 'UPCOMING', 15 | }, 16 | { 17 | label: 'Ongoing', 18 | key: 'ONGOING', 19 | }, 20 | { 21 | label: 'Ended', 22 | key: 'ENDED', 23 | }, 24 | ]; 25 | 26 | const RegisteredSemesters = () => { 27 | // const [params, setParams] = useState(undefined); 28 | const [semesterId, setSemesterId] = useState(''); 29 | const { data: semesterData, isFetching } = 30 | useGetAllRegisteredSemestersQuery(undefined); 31 | 32 | const [updateSemesterStatus] = useUpdateRegisteredSemesterMutation(); 33 | 34 | console.log(semesterId); 35 | 36 | const tableData = semesterData?.data?.map( 37 | ({ _id, academicSemester, startDate, endDate, status }) => ({ 38 | key: _id, 39 | name: `${academicSemester.name} ${academicSemester.year}`, 40 | startDate: moment(new Date(startDate)).format('MMMM'), 41 | endDate: moment(new Date(endDate)).format('MMMM'), 42 | status, 43 | }) 44 | ); 45 | 46 | const handleStatusUpdate = (data) => { 47 | const updateData = { 48 | id: semesterId, 49 | data: { 50 | status: data.key, 51 | }, 52 | }; 53 | 54 | updateSemesterStatus(updateData); 55 | }; 56 | 57 | const menuProps = { 58 | items, 59 | onClick: handleStatusUpdate, 60 | }; 61 | 62 | const columns: TableColumnsType = [ 63 | { 64 | title: 'Name', 65 | key: 'name', 66 | dataIndex: 'name', 67 | }, 68 | { 69 | title: 'Status', 70 | key: 'status', 71 | dataIndex: 'status', 72 | render: (item) => { 73 | let color; 74 | if (item === 'UPCOMING') { 75 | color = 'blue'; 76 | } 77 | if (item === 'ONGOING') { 78 | color = 'green'; 79 | } 80 | if (item === 'ENDED') { 81 | color = 'red'; 82 | } 83 | 84 | return {item}; 85 | }, 86 | }, 87 | { 88 | title: 'Start Date', 89 | key: 'startDate', 90 | dataIndex: 'startDate', 91 | }, 92 | { 93 | title: 'End Date', 94 | key: 'endDate', 95 | dataIndex: 'endDate', 96 | }, 97 | { 98 | title: 'Action', 99 | key: 'x', 100 | render: (item) => { 101 | return ( 102 | 103 | 104 | 105 | ); 106 | }, 107 | }, 108 | ]; 109 | 110 | // const onChange: TableProps['onChange'] = ( 111 | // _pagination, 112 | // filters, 113 | // _sorter, 114 | // extra 115 | // ) => { 116 | // if (extra.action === 'filter') { 117 | // const queryParams: TQueryParam[] = []; 118 | // setParams(queryParams); 119 | // } 120 | // }; 121 | 122 | return ( 123 |
129 | ); 130 | }; 131 | 132 | export default RegisteredSemesters; 133 | -------------------------------------------------------------------------------- /src/pages/admin/courseManagement/SemesterRegistration.tsx: -------------------------------------------------------------------------------- 1 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 2 | import PHForm from '../../../components/form/PHForm'; 3 | import { Button, Col, Flex } from 'antd'; 4 | import PHSelect from '../../../components/form/PHSelect'; 5 | import { semesterStatusOptions } from '../../../constants/semester'; 6 | 7 | import { toast } from 'sonner'; 8 | import { useGetAllSemestersQuery } from '../../../redux/features/admin/academicManagement.api'; 9 | import PHDatePicker from '../../../components/form/PHDatePicker'; 10 | import PHInput from '../../../components/form/PHInput'; 11 | import { useAddRegisteredSemesterMutation } from '../../../redux/features/admin/courseManagement'; 12 | import { TResponse } from '../../../types'; 13 | 14 | const SemesterRegistration = () => { 15 | const [addSemester] = useAddRegisteredSemesterMutation(); 16 | const { data: academicSemester } = useGetAllSemestersQuery([ 17 | { name: 'sort', value: 'year' }, 18 | ]); 19 | 20 | const academicSemesterOptions = academicSemester?.data?.map((item) => ({ 21 | value: item._id, 22 | label: `${item.name} ${item.year}`, 23 | })); 24 | 25 | const onSubmit: SubmitHandler = async (data) => { 26 | const toastId = toast.loading('Creating...'); 27 | 28 | const semesterData = { 29 | ...data, 30 | minCredit: Number(data.minCredit), 31 | maxCredit: Number(data.maxCredit), 32 | }; 33 | 34 | console.log(semesterData); 35 | 36 | try { 37 | const res = (await addSemester(semesterData)) as TResponse; 38 | console.log(res); 39 | if (res.error) { 40 | toast.error(res.error.data.message, { id: toastId }); 41 | } else { 42 | toast.success('Semester created', { id: toastId }); 43 | } 44 | } catch (err) { 45 | toast.error('Something went wrong', { id: toastId }); 46 | } 47 | }; 48 | 49 | return ( 50 | 51 | 52 | 53 | 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ); 74 | }; 75 | 76 | export default SemesterRegistration; 77 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/CreateAdmin.tsx: -------------------------------------------------------------------------------- 1 | const CreateAdmin = () => { 2 | return ( 3 |
4 |

This is CreateAdmin component

5 |
6 | ); 7 | }; 8 | 9 | export default CreateAdmin; 10 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/CreateFaculty.tsx: -------------------------------------------------------------------------------- 1 | const CreateFaculty = () => { 2 | return ( 3 |
4 |

This is CreateFaculty component

5 |
6 | ); 7 | }; 8 | 9 | export default CreateFaculty; 10 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/CreateStudent.tsx: -------------------------------------------------------------------------------- 1 | import { Controller, FieldValues, SubmitHandler } from 'react-hook-form'; 2 | import PHForm from '../../../components/form/PHForm'; 3 | import PHInput from '../../../components/form/PHInput'; 4 | import { Button, Col, Divider, Form, Input, Row } from 'antd'; 5 | import PHSelect from '../../../components/form/PHSelect'; 6 | import { bloodGroupOptions, genderOptions } from '../../../constants/global'; 7 | import PHDatePicker from '../../../components/form/PHDatePicker'; 8 | import { 9 | useGetAcademicDepartmentsQuery, 10 | useGetAllSemestersQuery, 11 | } from '../../../redux/features/admin/academicManagement.api'; 12 | import { useAddStudentMutation } from '../../../redux/features/admin/userManagement.api'; 13 | 14 | const studentDummyData = { 15 | password: 'student123', 16 | student: { 17 | name: { 18 | firstName: 'I am ', 19 | middleName: 'Student', 20 | lastName: 'Number 1', 21 | }, 22 | gender: 'male', 23 | dateOfBirth: '1990-01-01', 24 | bloogGroup: 'A+', 25 | 26 | email: 'student3@gmail.com', 27 | contactNo: '1235678', 28 | emergencyContactNo: '987-654-3210', 29 | presentAddress: '123 Main St, Cityville', 30 | permanentAddress: '456 Oak St, Townsville', 31 | 32 | guardian: { 33 | fatherName: 'James Doe', 34 | fatherOccupation: 'Engineer', 35 | fatherContactNo: '111-222-3333', 36 | motherName: 'Mary Doe', 37 | motherOccupation: 'Teacher', 38 | motherContactNo: '444-555-6666', 39 | }, 40 | 41 | localGuardian: { 42 | name: 'Alice Johnson', 43 | occupation: 'Doctor', 44 | contactNo: '777-888-9999', 45 | address: '789 Pine St, Villageton', 46 | }, 47 | 48 | admissionSemester: '65bb60ebf71fdd1add63b1c0', 49 | academicDepartment: '65b4acae3dc8d4f3ad83e416', 50 | }, 51 | }; 52 | 53 | //! This is only for development 54 | //! Should be removed 55 | const studentDefaultValues = { 56 | name: { 57 | firstName: 'I am ', 58 | middleName: 'Student', 59 | lastName: 'Number 1', 60 | }, 61 | gender: 'male', 62 | 63 | bloogGroup: 'A+', 64 | 65 | contactNo: '1235678', 66 | emergencyContactNo: '987-654-3210', 67 | presentAddress: '123 Main St, Cityville', 68 | permanentAddress: '456 Oak St, Townsville', 69 | 70 | guardian: { 71 | fatherName: 'James Doe', 72 | fatherOccupation: 'Engineer', 73 | fatherContactNo: '111-222-3333', 74 | motherName: 'Mary Doe', 75 | motherOccupation: 'Teacher', 76 | motherContactNo: '444-555-6666', 77 | }, 78 | 79 | localGuardian: { 80 | name: 'Alice Johnson', 81 | occupation: 'Doctor', 82 | contactNo: '777-888-9999', 83 | address: '789 Pine St, Villageton', 84 | }, 85 | 86 | admissionSemester: '65bb60ebf71fdd1add63b1c0', 87 | academicDepartment: '65b4acae3dc8d4f3ad83e416', 88 | }; 89 | 90 | const CreateStudent = () => { 91 | const [addStudent, { data, error }] = useAddStudentMutation(); 92 | 93 | console.log({ data, error }); 94 | 95 | const { data: sData, isLoading: sIsLoading } = 96 | useGetAllSemestersQuery(undefined); 97 | 98 | const { data: dData, isLoading: dIsLoading } = 99 | useGetAcademicDepartmentsQuery(undefined); 100 | 101 | const semesterOptions = sData?.data?.map((item) => ({ 102 | value: item._id, 103 | label: `${item.name} ${item.year}`, 104 | })); 105 | 106 | const departmentOptions = dData?.data?.map((item) => ({ 107 | value: item._id, 108 | label: item.name, 109 | })); 110 | 111 | const onSubmit: SubmitHandler = (data) => { 112 | const studentData = { 113 | password: 'student123', 114 | student: data, 115 | }; 116 | 117 | const formData = new FormData(); 118 | 119 | formData.append('data', JSON.stringify(studentData)); 120 | formData.append('file', data.image); 121 | 122 | addStudent(formData); 123 | 124 | //! This is for development 125 | //! Just for checking 126 | console.log(Object.fromEntries(formData)); 127 | }; 128 | 129 | return ( 130 | 131 | 132 | 133 | Personal Info. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 156 | 157 | 158 | ( 161 | 162 | onChange(e.target.files?.[0])} 167 | /> 168 | 169 | )} 170 | /> 171 | 172 | 173 | Contact Info. 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 187 | 188 | 189 | 194 | 195 | 196 | 201 | 202 | 203 | Guardian 204 | 205 | 206 | 211 | 212 | 213 | 218 | 219 | 220 | 225 | 226 | 227 | 232 | 233 | 234 | 239 | 240 | 241 | 246 | 247 | 248 | Local Guardian 249 | 250 | 251 | 252 | 253 | 254 | 259 | 260 | 261 | 266 | 267 | 268 | 273 | 274 | 275 | Academic Info. 276 | 277 | 278 | 284 | 285 | 286 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | ); 300 | }; 301 | 302 | export default CreateStudent; 303 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/StudentData.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Pagination, 4 | Space, 5 | Table, 6 | TableColumnsType, 7 | TableProps, 8 | } from 'antd'; 9 | import { useState } from 'react'; 10 | import { TQueryParam, TStudent } from '../../../types'; 11 | import { useGetAllStudentsQuery } from '../../../redux/features/admin/userManagement.api'; 12 | import { Link } from 'react-router-dom'; 13 | 14 | export type TTableData = Pick< 15 | TStudent, 16 | 'fullName' | 'id' | 'email' | 'contactNo' 17 | >; 18 | 19 | const StudentData = () => { 20 | const [params, setParams] = useState([]); 21 | const [page, setPage] = useState(1); 22 | const { 23 | data: studentData, 24 | isLoading, 25 | isFetching, 26 | } = useGetAllStudentsQuery([ 27 | { name: 'page', value: page }, 28 | { name: 'sort', value: 'id' }, 29 | ...params, 30 | ]); 31 | 32 | console.log({ isLoading, isFetching }); 33 | 34 | const metaData = studentData?.meta; 35 | 36 | const tableData = studentData?.data?.map( 37 | ({ _id, fullName, id, email, contactNo }) => ({ 38 | key: _id, 39 | fullName, 40 | id, 41 | email, 42 | contactNo, 43 | }) 44 | ); 45 | 46 | const columns: TableColumnsType = [ 47 | { 48 | title: 'Name', 49 | key: 'name', 50 | dataIndex: 'fullName', 51 | }, 52 | 53 | { 54 | title: 'Roll No.', 55 | key: 'id', 56 | dataIndex: 'id', 57 | }, 58 | { 59 | title: 'Email', 60 | key: 'email', 61 | dataIndex: 'email', 62 | }, 63 | { 64 | title: 'Contact No.', 65 | key: 'contactNo', 66 | dataIndex: 'contactNo', 67 | }, 68 | { 69 | title: 'Action', 70 | key: 'x', 71 | render: (item) => { 72 | console.log(item); 73 | return ( 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | ); 82 | }, 83 | width: '1%', 84 | }, 85 | ]; 86 | 87 | const onChange: TableProps['onChange'] = ( 88 | _pagination, 89 | filters, 90 | _sorter, 91 | extra 92 | ) => { 93 | if (extra.action === 'filter') { 94 | const queryParams: TQueryParam[] = []; 95 | 96 | filters.name?.forEach((item) => 97 | queryParams.push({ name: 'name', value: item }) 98 | ); 99 | 100 | filters.year?.forEach((item) => 101 | queryParams.push({ name: 'year', value: item }) 102 | ); 103 | 104 | setParams(queryParams); 105 | } 106 | }; 107 | 108 | return ( 109 | <> 110 |
117 | setPage(value)} 120 | pageSize={metaData?.limit} 121 | total={metaData?.total} 122 | /> 123 | 124 | ); 125 | }; 126 | 127 | export default StudentData; 128 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/StudentDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | 3 | const StudentDetails = () => { 4 | const { studentId } = useParams(); 5 | 6 | return ( 7 |
8 |

This is Student Details of {studentId}

9 |
10 | ); 11 | }; 12 | 13 | export default StudentDetails; 14 | -------------------------------------------------------------------------------- /src/pages/admin/userManagement/StudentUpdate.tsx: -------------------------------------------------------------------------------- 1 | const StudentUpdate = () => { 2 | return ( 3 |
4 |

This is StudentUpdate component

5 |
6 | ); 7 | }; 8 | 9 | export default StudentUpdate; 10 | -------------------------------------------------------------------------------- /src/pages/faculty/FacultyDashboard.tsx: -------------------------------------------------------------------------------- 1 | const FacultyDashboard = () => { 2 | return ( 3 |
4 |

This is FacultyDashboard component

5 |
6 | ); 7 | }; 8 | 9 | export default FacultyDashboard; 10 | -------------------------------------------------------------------------------- /src/pages/faculty/MyCourses.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Flex } from 'antd'; 2 | import PHForm from '../../components/form/PHForm'; 3 | import PHSelect from '../../components/form/PHSelect'; 4 | import { useGetAllFacultyCoursesQuery } from '../../redux/features/faculty/facultyCourses.api'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { FieldValues, SubmitHandler } from 'react-hook-form'; 7 | 8 | const MyCourses = () => { 9 | const { data: facultyCoursesData } = useGetAllFacultyCoursesQuery(undefined); 10 | const navigate = useNavigate(); 11 | 12 | console.log(facultyCoursesData); 13 | 14 | const semesterOptions = facultyCoursesData?.data?.map((item) => ({ 15 | label: `${item.academicSemester.name} ${item.academicSemester.year}`, 16 | value: item.semesterRegistration._id, 17 | })); 18 | 19 | const courseOptions = facultyCoursesData?.data?.map((item) => ({ 20 | label: item.course.title, 21 | value: item.course._id, 22 | })); 23 | 24 | const onSubmit: SubmitHandler = (data) => { 25 | navigate(`/faculty/courses/${data.semesterRegistration}/${data.course}`); 26 | }; 27 | 28 | return ( 29 | 30 |
31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default MyCourses; 46 | -------------------------------------------------------------------------------- /src/pages/faculty/MyStudents.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | import { 3 | useAddMarkMutation, 4 | useGetAllFacultyCoursesQuery, 5 | } from '../../redux/features/faculty/facultyCourses.api'; 6 | import { Button, Modal, Table, TableColumnsType } from 'antd'; 7 | import { useState } from 'react'; 8 | import PHForm from '../../components/form/PHForm'; 9 | import PHSelect from '../../components/form/PHSelect'; 10 | import PHInput from '../../components/form/PHInput'; 11 | 12 | const MyStudents = () => { 13 | const { registerSemesterId, courseId } = useParams(); 14 | const { data: facultyCoursesData } = useGetAllFacultyCoursesQuery([ 15 | { name: 'semesterRegistration', value: registerSemesterId }, 16 | { name: 'course', value: courseId }, 17 | ]); 18 | 19 | console.log(facultyCoursesData); 20 | 21 | const tableData = facultyCoursesData?.data?.map( 22 | ({ _id, student, semesterRegistration, offeredCourse }) => ({ 23 | key: _id, 24 | name: student.fullName, 25 | roll: student.id, 26 | semesterRegistration: semesterRegistration._id, 27 | student: student._id, 28 | offeredCourse: offeredCourse._id, 29 | }) 30 | ); 31 | 32 | const columns = [ 33 | { 34 | title: 'Name', 35 | key: 'name', 36 | dataIndex: 'name', 37 | }, 38 | { 39 | title: 'Roll', 40 | key: 'roll', 41 | dataIndex: 'roll', 42 | }, 43 | { 44 | title: 'Action', 45 | key: 'x', 46 | render: (item) => { 47 | return ( 48 |
49 | 50 |
51 | ); 52 | }, 53 | }, 54 | ]; 55 | 56 | return
; 57 | }; 58 | 59 | const AddMarksModal = ({ studentInfo }) => { 60 | console.log(studentInfo); 61 | const [isModalOpen, setIsModalOpen] = useState(false); 62 | const [addMark] = useAddMarkMutation(); 63 | 64 | const handleSubmit = async (data) => { 65 | const studentMark = { 66 | semesterRegistration: studentInfo.semesterRegistration, 67 | offeredCourse: studentInfo.offeredCourse, 68 | student: studentInfo.student, 69 | courseMarks: { 70 | classTest1: Number(data.classTest1), 71 | midTerm: Number(data.midTerm), 72 | classTest2: Number(data.classTest2), 73 | finalTerm: Number(data.finalTerm), 74 | }, 75 | }; 76 | 77 | console.log(studentMark); 78 | const res = await addMark(studentMark); 79 | 80 | console.log(res); 81 | }; 82 | 83 | const showModal = () => { 84 | setIsModalOpen(true); 85 | }; 86 | 87 | const handleCancel = () => { 88 | setIsModalOpen(false); 89 | }; 90 | 91 | return ( 92 | <> 93 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ); 110 | }; 111 | 112 | export default MyStudents; 113 | -------------------------------------------------------------------------------- /src/pages/student/MySchedule.tsx: -------------------------------------------------------------------------------- 1 | import { useGetAllEnrolledCoursesQuery } from '../../redux/features/student/studentCourseManagement.api'; 2 | 3 | const MySchedule = () => { 4 | const { data } = useGetAllEnrolledCoursesQuery(undefined); 5 | console.log(data); 6 | 7 | return ( 8 |
9 | {data?.data?.map((item) => { 10 | return ( 11 |
12 |
{item.course.title}
13 |
{item.offeredCourse.section}
14 |
15 | {item.offeredCourse.days.map((item) => ( 16 | {item} 17 | ))} 18 |
19 |
20 | ); 21 | })} 22 |
23 | ); 24 | }; 25 | 26 | export default MySchedule; 27 | -------------------------------------------------------------------------------- /src/pages/student/OfferedCourse.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Col, Row } from 'antd'; 2 | import { 3 | useEnrolCourseMutation, 4 | useGetAllOfferedCoursesQuery, 5 | } from '../../redux/features/student/studentCourseManagement.api'; 6 | 7 | type TCourse = { 8 | [index: string]: any; 9 | }; 10 | 11 | const OfferedCourse = () => { 12 | const { data: offeredCourseData } = useGetAllOfferedCoursesQuery(undefined); 13 | const [enroll] = useEnrolCourseMutation(); 14 | 15 | const singleObject = offeredCourseData?.data?.reduce((acc: TCourse, item) => { 16 | const key = item.course.title; 17 | acc[key] = acc[key] || { courseTitle: key, sections: [] }; 18 | acc[key].sections.push({ 19 | section: item.section, 20 | _id: item._id, 21 | days: item.days, 22 | startTime: item.startTime, 23 | endTime: item.endTime, 24 | }); 25 | return acc; 26 | }, {}); 27 | 28 | const modifiedData = Object.values(singleObject ? singleObject : {}); 29 | 30 | const handleEnroll = async (id) => { 31 | const enrollData = { 32 | offeredCourse: id, 33 | }; 34 | 35 | const res = await enroll(enrollData); 36 | console.log(res); 37 | }; 38 | 39 | if (!modifiedData.length) { 40 | return

No available courses

; 41 | } 42 | 43 | return ( 44 | 45 | {modifiedData.map((item) => { 46 | return ( 47 | 48 |
49 |

{item.courseTitle}

50 |
51 |
52 | {item.sections.map((section) => { 53 | return ( 54 | 59 |
Section: {section.section} 60 | 61 | days:{' '} 62 | {section.days.map((day) => ( 63 | {day} 64 | ))} 65 | 66 | Start Time: {section.startTime} 67 | End Time: {section.endTime} 68 | 71 | 72 | ); 73 | })} 74 | 75 | 76 | ); 77 | })} 78 | 79 | ); 80 | }; 81 | 82 | export default OfferedCourse; 83 | 84 | // [ 85 | // { course: { title: 'React' }, section: 1, _id: 'sdfasdfasdfas45345' }, 86 | // { course: { title: 'React' }, section: 2, _id: 'sdfasdfasdfas45345' }, 87 | // { course: { title: 'Redux' }, section: 1, _id: 'sdfasdfasdfas45345' }, 88 | // ]; 89 | 90 | // [ 91 | // { 92 | // courseTitle: 'React', 93 | // sections: [ 94 | // { section: 1, _id: 'ADFa4345basdfa' }, 95 | // { section: 2, _id: 'ADFa4345basdf3' }, 96 | // ], 97 | // }, 98 | // { 99 | // courseTitle: 'Redux', 100 | // sections: [{ section: 1, _id: 'ADFa4345basdfa' }], 101 | // }, 102 | // ]; 103 | -------------------------------------------------------------------------------- /src/pages/student/StudentDashboard.tsx: -------------------------------------------------------------------------------- 1 | const StudentDashboard = () => { 2 | return ( 3 |
4 |

This is StudentDashboard component

5 |
6 | ); 7 | }; 8 | 9 | export default StudentDashboard; 10 | -------------------------------------------------------------------------------- /src/redux/api/baseApi.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseQueryApi, 3 | BaseQueryFn, 4 | DefinitionType, 5 | FetchArgs, 6 | createApi, 7 | fetchBaseQuery, 8 | } from '@reduxjs/toolkit/query/react'; 9 | import { RootState } from '../store'; 10 | import { logout, setUser } from '../features/auth/authSlice'; 11 | import { toast } from 'sonner'; 12 | 13 | const baseQuery = fetchBaseQuery({ 14 | baseUrl: 'http://localhost:5000/api/v1', 15 | credentials: 'include', 16 | prepareHeaders: (headers, { getState }) => { 17 | const token = (getState() as RootState).auth.token; 18 | 19 | if (token) { 20 | headers.set('authorization', `${token}`); 21 | } 22 | 23 | return headers; 24 | }, 25 | }); 26 | 27 | const baseQueryWithRefreshToken: BaseQueryFn< 28 | FetchArgs, 29 | BaseQueryApi, 30 | DefinitionType 31 | > = async (args, api, extraOptions): Promise => { 32 | let result = await baseQuery(args, api, extraOptions); 33 | 34 | if (result?.error?.status === 404) { 35 | toast.error(result.error.data.message); 36 | } 37 | if (result?.error?.status === 403) { 38 | toast.error(result.error.data.message); 39 | } 40 | if (result?.error?.status === 401) { 41 | //* Send Refresh 42 | console.log('Sending refresh token'); 43 | 44 | const res = await fetch('http://localhost:5000/api/v1/auth/refresh-token', { 45 | method: 'POST', 46 | credentials: 'include', 47 | }); 48 | 49 | const data = await res.json(); 50 | 51 | if (data?.data?.accessToken) { 52 | const user = (api.getState() as RootState).auth.user; 53 | 54 | api.dispatch( 55 | setUser({ 56 | user, 57 | token: data.data.accessToken, 58 | }) 59 | ); 60 | 61 | result = await baseQuery(args, api, extraOptions); 62 | } else { 63 | api.dispatch(logout()); 64 | } 65 | } 66 | 67 | return result; 68 | }; 69 | 70 | export const baseApi = createApi({ 71 | reducerPath: 'baseApi', 72 | baseQuery: baseQueryWithRefreshToken, 73 | tagTypes: ['semester', 'courses', 'offeredCourse'], 74 | endpoints: () => ({}), 75 | }); 76 | -------------------------------------------------------------------------------- /src/redux/features/admin/academicManagement.api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TAcademicDepartment, 3 | TAcademicFaculty, 4 | TAcademicSemester, 5 | TQueryParam, 6 | TResponseRedux, 7 | } from '../../../types'; 8 | 9 | import { baseApi } from '../../api/baseApi'; 10 | 11 | const academicManagementApi = baseApi.injectEndpoints({ 12 | endpoints: (builder) => ({ 13 | getAllSemesters: builder.query({ 14 | query: (args) => { 15 | const params = new URLSearchParams(); 16 | 17 | if (args) { 18 | args.forEach((item: TQueryParam) => { 19 | params.append(item.name, item.value as string); 20 | }); 21 | } 22 | 23 | return { 24 | url: '/academic-semesters', 25 | method: 'GET', 26 | params: params, 27 | }; 28 | }, 29 | transformResponse: (response: TResponseRedux) => { 30 | return { 31 | data: response.data, 32 | meta: response.meta, 33 | }; 34 | }, 35 | }), 36 | addAcademicSemester: builder.mutation({ 37 | query: (data) => ({ 38 | url: '/academic-semesters/create-academic-semester', 39 | method: 'POST', 40 | body: data, 41 | }), 42 | }), 43 | getAcademicFaculties: builder.query({ 44 | query: () => { 45 | return { url: '/academic-faculties', method: 'GET' }; 46 | }, 47 | transformResponse: (response: TResponseRedux) => { 48 | return { 49 | data: response.data, 50 | meta: response.meta, 51 | }; 52 | }, 53 | }), 54 | addAcademicFaculty: builder.mutation({ 55 | query: (data) => ({ 56 | url: '/academic-faculties/create-academic-faculty', 57 | method: 'POST', 58 | body: data, 59 | }), 60 | }), 61 | getAcademicDepartments: builder.query({ 62 | query: () => { 63 | return { url: '/academic-departments', method: 'GET' }; 64 | }, 65 | transformResponse: (response: TResponseRedux) => { 66 | return { 67 | data: response.data, 68 | meta: response.meta, 69 | }; 70 | }, 71 | }), 72 | addAcademicDepartment: builder.mutation({ 73 | query: (data) => ({ 74 | url: '/academic-departments/create-academic-department', 75 | method: 'POST', 76 | body: data, 77 | }), 78 | }), 79 | }), 80 | }); 81 | 82 | export const { 83 | useGetAllSemestersQuery, 84 | useAddAcademicSemesterMutation, 85 | useGetAcademicDepartmentsQuery, 86 | useGetAcademicFacultiesQuery, 87 | } = academicManagementApi; 88 | -------------------------------------------------------------------------------- /src/redux/features/admin/courseManagement.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TCourse, 3 | TQueryParam, 4 | TResponseRedux, 5 | TSemester, 6 | } from '../../../types'; 7 | import { baseApi } from '../../api/baseApi'; 8 | 9 | const courseManagementApi = baseApi.injectEndpoints({ 10 | endpoints: (builder) => ({ 11 | getAllRegisteredSemesters: builder.query({ 12 | query: (args) => { 13 | const params = new URLSearchParams(); 14 | 15 | if (args) { 16 | args.forEach((item: TQueryParam) => { 17 | params.append(item.name, item.value as string); 18 | }); 19 | } 20 | 21 | return { 22 | url: '/semester-registrations', 23 | method: 'GET', 24 | params: params, 25 | }; 26 | }, 27 | providesTags: ['semester'], 28 | transformResponse: (response: TResponseRedux) => { 29 | return { 30 | data: response.data, 31 | meta: response.meta, 32 | }; 33 | }, 34 | }), 35 | addRegisteredSemester: builder.mutation({ 36 | query: (data) => ({ 37 | url: '/semester-registrations/create-semester-registration', 38 | method: 'POST', 39 | body: data, 40 | }), 41 | invalidatesTags: ['semester'], 42 | }), 43 | updateRegisteredSemester: builder.mutation({ 44 | query: (args) => ({ 45 | url: `/semester-registrations/${args.id}`, 46 | method: 'PATCH', 47 | body: args.data, 48 | }), 49 | invalidatesTags: ['semester'], 50 | }), 51 | getAllCourses: builder.query({ 52 | query: (args) => { 53 | const params = new URLSearchParams(); 54 | 55 | if (args) { 56 | args.forEach((item: TQueryParam) => { 57 | params.append(item.name, item.value as string); 58 | }); 59 | } 60 | 61 | return { 62 | url: '/courses', 63 | method: 'GET', 64 | params: params, 65 | }; 66 | }, 67 | providesTags: ['courses'], 68 | transformResponse: (response: TResponseRedux) => { 69 | return { 70 | data: response.data, 71 | meta: response.meta, 72 | }; 73 | }, 74 | }), 75 | addCourse: builder.mutation({ 76 | query: (data) => ({ 77 | url: `/courses/create-course`, 78 | method: 'POST', 79 | body: data, 80 | }), 81 | invalidatesTags: ['courses'], 82 | }), 83 | addFaculties: builder.mutation({ 84 | query: (args) => ({ 85 | url: `/courses/${args.courseId}/assign-faculties`, 86 | method: 'PUT', 87 | body: args.data, 88 | }), 89 | invalidatesTags: ['courses'], 90 | }), 91 | getCourseFaculties: builder.query({ 92 | query: (id) => { 93 | return { 94 | url: `/courses/${id}/get-faculties`, 95 | method: 'GET', 96 | }; 97 | }, 98 | transformResponse: (response: TResponseRedux) => { 99 | return { 100 | data: response.data, 101 | meta: response.meta, 102 | }; 103 | }, 104 | }), 105 | createOfferedCourse: builder.mutation({ 106 | query: (data) => ({ 107 | url: `offered-courses/create-offered-course`, 108 | method: 'POST', 109 | body: data, 110 | }), 111 | invalidatesTags: ['courses'], 112 | }), 113 | }), 114 | }); 115 | 116 | export const { 117 | useAddRegisteredSemesterMutation, 118 | useGetAllRegisteredSemestersQuery, 119 | useUpdateRegisteredSemesterMutation, 120 | useGetAllCoursesQuery, 121 | useAddCourseMutation, 122 | useAddFacultiesMutation, 123 | useGetCourseFacultiesQuery, 124 | useCreateOfferedCourseMutation, 125 | } = courseManagementApi; 126 | -------------------------------------------------------------------------------- /src/redux/features/admin/userManagement.api.ts: -------------------------------------------------------------------------------- 1 | import { TQueryParam, TResponseRedux, TStudent } from '../../../types'; 2 | 3 | import { baseApi } from '../../api/baseApi'; 4 | 5 | const userManagementApi = baseApi.injectEndpoints({ 6 | endpoints: (builder) => ({ 7 | getAllStudents: builder.query({ 8 | query: (args) => { 9 | console.log(args); 10 | const params = new URLSearchParams(); 11 | 12 | if (args) { 13 | args.forEach((item: TQueryParam) => { 14 | params.append(item.name, item.value as string); 15 | }); 16 | } 17 | 18 | return { 19 | url: '/students', 20 | method: 'GET', 21 | params: params, 22 | }; 23 | }, 24 | transformResponse: (response: TResponseRedux) => { 25 | return { 26 | data: response.data, 27 | meta: response.meta, 28 | }; 29 | }, 30 | }), 31 | getAllFaculties: builder.query({ 32 | query: (args) => { 33 | console.log(args); 34 | const params = new URLSearchParams(); 35 | 36 | if (args) { 37 | args.forEach((item: TQueryParam) => { 38 | params.append(item.name, item.value as string); 39 | }); 40 | } 41 | 42 | return { 43 | url: '/faculties', 44 | method: 'GET', 45 | params: params, 46 | }; 47 | }, 48 | transformResponse: (response: TResponseRedux) => { 49 | return { 50 | data: response.data, 51 | meta: response.meta, 52 | }; 53 | }, 54 | }), 55 | addStudent: builder.mutation({ 56 | query: (data) => ({ 57 | url: '/users/create-student', 58 | method: 'POST', 59 | body: data, 60 | }), 61 | }), 62 | changePassword: builder.mutation({ 63 | query: (data) => ({ 64 | url: '/auth/change-password', 65 | method: 'POST', 66 | body: data, 67 | }), 68 | }), 69 | }), 70 | }); 71 | 72 | export const { 73 | useAddStudentMutation, 74 | useGetAllStudentsQuery, 75 | useGetAllFacultiesQuery, 76 | useChangePasswordMutation, 77 | } = userManagementApi; 78 | -------------------------------------------------------------------------------- /src/redux/features/auth/authApi.ts: -------------------------------------------------------------------------------- 1 | import { baseApi } from '../../api/baseApi'; 2 | 3 | const authApi = baseApi.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | login: builder.mutation({ 6 | query: (userInfo) => ({ 7 | url: '/auth/login', 8 | method: 'POST', 9 | body: userInfo, 10 | }), 11 | }), 12 | }), 13 | }); 14 | 15 | export const { useLoginMutation } = authApi; 16 | -------------------------------------------------------------------------------- /src/redux/features/auth/authSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { RootState } from '../../store'; 3 | 4 | export type TUser = { 5 | userId: string; 6 | role: string; 7 | iat: number; 8 | exp: number; 9 | }; 10 | 11 | type TAuthState = { 12 | user: null | TUser; 13 | token: null | string; 14 | }; 15 | 16 | const initialState: TAuthState = { 17 | user: null, 18 | token: null, 19 | }; 20 | 21 | const authSlice = createSlice({ 22 | name: 'auth', 23 | initialState, 24 | reducers: { 25 | setUser: (state, action) => { 26 | const { user, token } = action.payload; 27 | state.user = user; 28 | state.token = token; 29 | }, 30 | logout: (state) => { 31 | state.user = null; 32 | state.token = null; 33 | }, 34 | }, 35 | }); 36 | 37 | export const { setUser, logout } = authSlice.actions; 38 | 39 | export default authSlice.reducer; 40 | 41 | export const useCurrentToken = (state: RootState) => state.auth.token; 42 | export const selectCurrentUser = (state: RootState) => state.auth.user; 43 | -------------------------------------------------------------------------------- /src/redux/features/counter/counterApi.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/L2B2-PH-university-client/f84bb0541b56ff98c7506c1ac2832284cf4f7c68/src/redux/features/counter/counterApi.ts -------------------------------------------------------------------------------- /src/redux/features/counter/counterSlice.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apollo-Level2-Web-Dev/L2B2-PH-university-client/f84bb0541b56ff98c7506c1ac2832284cf4f7c68/src/redux/features/counter/counterSlice.ts -------------------------------------------------------------------------------- /src/redux/features/faculty/facultyCourses.api.ts: -------------------------------------------------------------------------------- 1 | import { TQueryParam, TResponseRedux } from '../../../types'; 2 | import { baseApi } from '../../api/baseApi'; 3 | 4 | const facultyCourseApi = baseApi.injectEndpoints({ 5 | endpoints: (builder) => ({ 6 | getAllFacultyCourses: builder.query({ 7 | query: (args) => { 8 | console.log(args); 9 | const params = new URLSearchParams(); 10 | 11 | if (args) { 12 | args.forEach((item: TQueryParam) => { 13 | params.append(item.name, item.value as string); 14 | }); 15 | } 16 | return { 17 | url: '/enrolled-courses', 18 | method: 'GET', 19 | params: params, 20 | }; 21 | }, 22 | providesTags: ['offeredCourse'], 23 | transformResponse: (response: TResponseRedux) => { 24 | return { 25 | data: response.data, 26 | meta: response.meta, 27 | }; 28 | }, 29 | }), 30 | 31 | addMark: builder.mutation({ 32 | query: (data) => ({ 33 | url: '/enrolled-courses/update-enrolled-course-marks', 34 | method: 'PATCH', 35 | body: data, 36 | }), 37 | }), 38 | }), 39 | }); 40 | 41 | export const { useGetAllFacultyCoursesQuery, useAddMarkMutation } = 42 | facultyCourseApi; 43 | -------------------------------------------------------------------------------- /src/redux/features/student/studentCourseManagement.api.ts: -------------------------------------------------------------------------------- 1 | import { TQueryParam, TResponseRedux } from '../../../types'; 2 | import { TOfferedCourse } from '../../../types/studentCourse.type'; 3 | import { baseApi } from '../../api/baseApi'; 4 | 5 | const studentCourseApi = baseApi.injectEndpoints({ 6 | endpoints: (builder) => ({ 7 | getAllOfferedCourses: builder.query({ 8 | query: (args) => { 9 | console.log(args); 10 | const params = new URLSearchParams(); 11 | 12 | if (args) { 13 | args.forEach((item: TQueryParam) => { 14 | params.append(item.name, item.value as string); 15 | }); 16 | } 17 | return { 18 | url: '/offered-courses/my-offered-courses', 19 | method: 'GET', 20 | params: params, 21 | }; 22 | }, 23 | providesTags: ['offeredCourse'], 24 | transformResponse: (response: TResponseRedux) => { 25 | return { 26 | data: response.data, 27 | meta: response.meta, 28 | }; 29 | }, 30 | }), 31 | getAllEnrolledCourses: builder.query({ 32 | query: (args) => { 33 | console.log(args); 34 | const params = new URLSearchParams(); 35 | 36 | if (args) { 37 | args.forEach((item: TQueryParam) => { 38 | params.append(item.name, item.value as string); 39 | }); 40 | } 41 | return { 42 | url: '/enrolled-courses/my-enrolled-courses', 43 | method: 'GET', 44 | params: params, 45 | }; 46 | }, 47 | providesTags: ['offeredCourse'], 48 | transformResponse: (response: TResponseRedux) => { 49 | return { 50 | data: response.data, 51 | meta: response.meta, 52 | }; 53 | }, 54 | }), 55 | enrolCourse: builder.mutation({ 56 | query: (data) => ({ 57 | url: '/enrolled-courses/create-enrolled-course', 58 | method: 'POST', 59 | body: data, 60 | }), 61 | invalidatesTags: ['offeredCourse'], 62 | }), 63 | }), 64 | }); 65 | 66 | export const { 67 | useGetAllOfferedCoursesQuery, 68 | useEnrolCourseMutation, 69 | useGetAllEnrolledCoursesQuery, 70 | } = studentCourseApi; 71 | -------------------------------------------------------------------------------- /src/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux'; 2 | import type { TypedUseSelectorHook } from 'react-redux'; 3 | import type { RootState, AppDispatch } from './store'; 4 | 5 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 6 | export const useAppDispatch: () => AppDispatch = useDispatch; 7 | export const useAppSelector: TypedUseSelectorHook = useSelector; 8 | -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import authReducer from './features/auth/authSlice'; 3 | import { baseApi } from './api/baseApi'; 4 | import { 5 | persistReducer, 6 | persistStore, 7 | FLUSH, 8 | REHYDRATE, 9 | PAUSE, 10 | PERSIST, 11 | PURGE, 12 | REGISTER, 13 | } from 'redux-persist'; 14 | import storage from 'redux-persist/lib/storage'; 15 | 16 | const persistConfig = { 17 | key: 'auth', 18 | storage, 19 | }; 20 | 21 | const persistedAuthReducer = persistReducer(persistConfig, authReducer); 22 | 23 | export const store = configureStore({ 24 | reducer: { 25 | [baseApi.reducerPath]: baseApi.reducer, 26 | auth: persistedAuthReducer, 27 | }, 28 | middleware: (getDefaultMiddlewares) => 29 | getDefaultMiddlewares({ 30 | serializableCheck: { 31 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 32 | }, 33 | }).concat(baseApi.middleware), 34 | }); 35 | 36 | // Infer the `RootState` and `AppDispatch` types from the store itself 37 | export type RootState = ReturnType; 38 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 39 | export type AppDispatch = typeof store.dispatch; 40 | 41 | export const persistor = persistStore(store); 42 | -------------------------------------------------------------------------------- /src/routes/admin.routes.tsx: -------------------------------------------------------------------------------- 1 | import AdminDashboard from '../pages/admin/AdminDashboard'; 2 | import CreateAdmin from '../pages/admin/userManagement/CreateAdmin'; 3 | import CreateFaculty from '../pages/admin/userManagement/CreateFaculty'; 4 | import CreateStudent from '../pages/admin/userManagement/CreateStudent'; 5 | import AcademicDepartment from '../pages/admin/academicManagement/AcademicDepartment'; 6 | import AcademicFaculty from '../pages/admin/academicManagement/AcademicFaculty'; 7 | import AcademicSemester from '../pages/admin/academicManagement/AcademicSemester'; 8 | import CreateAcademicDepartment from '../pages/admin/academicManagement/CreateAcademicDepartment'; 9 | import CreateAcademicFaculty from '../pages/admin/academicManagement/CreateAcademicFaculty'; 10 | import CreateAcademicSemester from '../pages/admin/academicManagement/CreateAcademicSemester'; 11 | import StudentData from '../pages/admin/userManagement/StudentData'; 12 | import StudentDetails from '../pages/admin/userManagement/StudentDetails'; 13 | import SemesterRegistration from '../pages/admin/courseManagement/SemesterRegistration'; 14 | import RegisteredSemesters from '../pages/admin/courseManagement/RegisteredSemesters'; 15 | import CreateCourse from '../pages/admin/courseManagement/CreateCourse'; 16 | import Courses from '../pages/admin/courseManagement/Courses'; 17 | import OfferCourse from '../pages/admin/courseManagement/OfferCourse'; 18 | import OfferedCourses from '../pages/admin/courseManagement/OfferedCourses'; 19 | 20 | export const adminPaths = [ 21 | { 22 | name: 'Dashboard', 23 | path: 'dashboard', 24 | element: , 25 | }, 26 | { 27 | name: 'Academic Management', 28 | children: [ 29 | { 30 | name: 'Create A. Semester', 31 | path: 'create-academic-semester', 32 | element: , 33 | }, 34 | { 35 | name: 'Academic Semester', 36 | path: 'academic-semester', 37 | element: , 38 | }, 39 | { 40 | name: 'Create A. Faculty', 41 | path: 'create-academic-faculty', 42 | element: , 43 | }, 44 | { 45 | name: 'Academic Faculty', 46 | path: 'academic-faculty', 47 | element: , 48 | }, 49 | { 50 | name: 'Create A. Department', 51 | path: 'create-academic-department', 52 | element: , 53 | }, 54 | { 55 | name: 'Academic Department', 56 | path: 'academic-department', 57 | element: , 58 | }, 59 | ], 60 | }, 61 | { 62 | name: 'User Management', 63 | children: [ 64 | { 65 | name: 'Create Student', 66 | path: 'create-student', 67 | element: , 68 | }, 69 | { 70 | name: 'Students', 71 | path: 'students-data', 72 | element: , 73 | }, 74 | { 75 | path: 'student-data/:studentId', 76 | element: , 77 | }, 78 | { 79 | name: 'Create Admin', 80 | path: 'create-admin', 81 | element: , 82 | }, 83 | { 84 | name: 'Create Faculty', 85 | path: 'create-faculty', 86 | element: , 87 | }, 88 | 89 | { 90 | name: 'Create Member', 91 | path: 'create-member', 92 | element: , 93 | }, 94 | ], 95 | }, 96 | { 97 | name: 'Course Management', 98 | children: [ 99 | { 100 | name: 'Semester Registration', 101 | path: 'semester-registration', 102 | element: , 103 | }, 104 | { 105 | name: 'Registered Semesters', 106 | path: 'registered-semesters', 107 | element: , 108 | }, 109 | { 110 | name: 'Create Course', 111 | path: 'create-course', 112 | element: , 113 | }, 114 | { 115 | name: 'Courses', 116 | path: 'courses', 117 | element: , 118 | }, 119 | { 120 | name: 'Offer Course', 121 | path: 'offer-course', 122 | element: , 123 | }, 124 | { 125 | name: 'Offered Courses', 126 | path: 'offered-courses', 127 | element: , 128 | }, 129 | ], 130 | }, 131 | ]; 132 | 133 | // export const adminSidebarItems = adminPaths.reduce( 134 | // (acc: TSidebarItem[], item) => { 135 | // if (item.path && item.name) { 136 | // acc.push({ 137 | // key: item.name, 138 | // label: {item.name}, 139 | // }); 140 | // } 141 | 142 | // if (item.children) { 143 | // acc.push({ 144 | // key: item.name, 145 | // label: item.name, 146 | // children: item.children.map((child) => ({ 147 | // key: child.name, 148 | // label: {child.name}, 149 | // })), 150 | // }); 151 | // } 152 | 153 | // return acc; 154 | // }, 155 | // [] 156 | // ); 157 | 158 | //* Programatical way 159 | 160 | // export const adminRoutes = adminPaths.reduce((acc: TRoute[], item) => { 161 | // if (item.path && item.element) { 162 | // acc.push({ 163 | // path: item.path, 164 | // element: item.element, 165 | // }); 166 | // } 167 | 168 | // if (item.children) { 169 | // item.children.forEach((child) => { 170 | // acc.push({ 171 | // path: child.path, 172 | // element: child.element, 173 | // }); 174 | // }); 175 | // } 176 | 177 | // return acc; 178 | // }, []); 179 | 180 | //! Hard coded way 181 | 182 | // export const adminPaths = [ 183 | // { 184 | // path: 'dashboard', 185 | // element: , 186 | // }, 187 | // { 188 | // path: 'create-student', 189 | // element: , 190 | // }, 191 | // { 192 | // path: 'create-admin', 193 | // element: , 194 | // }, 195 | // { 196 | // path: 'create-faculty', 197 | // element: , 198 | // }, 199 | // ]; 200 | -------------------------------------------------------------------------------- /src/routes/faculty.routes.tsx: -------------------------------------------------------------------------------- 1 | import FacultyDashboard from '../pages/faculty/FacultyDashboard'; 2 | import MyCourses from '../pages/faculty/MyCourses'; 3 | import MyStudents from '../pages/faculty/MyStudents'; 4 | export const facultyPaths = [ 5 | { 6 | name: 'Dashboard', 7 | path: 'dashboard', 8 | element: , 9 | }, 10 | { 11 | name: 'My Courses', 12 | path: 'courses', 13 | element: , 14 | }, 15 | { 16 | path: 'courses/:registerSemesterId/:courseId', 17 | element: , 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /src/routes/routes.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from 'react-router-dom'; 2 | import App from '../App'; 3 | import Login from '../pages/Login'; 4 | import Register from '../pages/Register'; 5 | import { adminPaths } from './admin.routes'; 6 | import { routeGenerator } from '../utils/routesGenerator'; 7 | import { facultyPaths } from './faculty.routes'; 8 | import { studentPaths } from './student.routes'; 9 | import ProtectedRoute from '../components/layout/ProtectedRoute'; 10 | import ChangePassword from '../pages/ChangePassword'; 11 | 12 | const router = createBrowserRouter([ 13 | { 14 | path: '/', 15 | element: , 16 | }, 17 | { 18 | path: '/admin', 19 | element: ( 20 | 21 | 22 | 23 | ), 24 | children: routeGenerator(adminPaths), 25 | }, 26 | { 27 | path: '/faculty', 28 | element: ( 29 | 30 | 31 | 32 | ), 33 | children: routeGenerator(facultyPaths), 34 | }, 35 | { 36 | path: '/student', 37 | element: ( 38 | 39 | 40 | 41 | ), 42 | children: routeGenerator(studentPaths), 43 | }, 44 | { 45 | path: '/login', 46 | element: , 47 | }, 48 | { 49 | path: '/change-password', 50 | element: , 51 | }, 52 | { 53 | path: '/register', 54 | element: , 55 | }, 56 | ]); 57 | 58 | export default router; 59 | -------------------------------------------------------------------------------- /src/routes/student.routes.tsx: -------------------------------------------------------------------------------- 1 | import MySchedule from '../pages/student/MySchedule'; 2 | import OfferedCourse from '../pages/student/OfferedCourse'; 3 | import StudentDashboard from '../pages/student/StudentDashboard'; 4 | 5 | export const studentPaths = [ 6 | { 7 | name: 'Dashboard', 8 | path: 'dashboard', 9 | element: , 10 | }, 11 | { 12 | name: 'Offered Course', 13 | path: 'offered-course', 14 | element: , 15 | }, 16 | { 17 | name: 'My Schedule', 18 | path: 'schedule', 19 | element: , 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/schemas/academicManagement.schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const academicSemesterSchema = z.object({ 4 | name: z.string({ required_error: 'Please select a Name' }), 5 | year: z.string({ required_error: 'Please select a Year' }), 6 | startMonth: z.string({ required_error: 'Please select a Start Month' }), 7 | endMonth: z.string({ required_error: 'Please select a End Month' }), 8 | }); 9 | -------------------------------------------------------------------------------- /src/types/academicManagement.type.ts: -------------------------------------------------------------------------------- 1 | export type TAcademicSemester = { 2 | _id: string; 3 | name: string; 4 | year: string; 5 | code: string; 6 | startMonth: string; 7 | endMonth: string; 8 | createdAt: string; 9 | updatedAt: string; 10 | __v: number; 11 | }; 12 | 13 | export type TAcademicFaculty = { 14 | _id: string; 15 | name: string; 16 | createdAt: string; 17 | updatedAt: string; 18 | __v: number; 19 | }; 20 | 21 | export type TAcademicDepartment = { 22 | _id: string; 23 | name: string; 24 | academicFaculty: TAcademicFaculty; 25 | createdAt: string; 26 | updatedAt: string; 27 | }; 28 | -------------------------------------------------------------------------------- /src/types/courseManagement.type.ts: -------------------------------------------------------------------------------- 1 | import { TAcademicSemester } from '.'; 2 | 3 | export type TSemester = { 4 | _id: string; 5 | academicSemester: TAcademicSemester; 6 | status: string; 7 | startDate: string; 8 | endDate: string; 9 | minCredit: number; 10 | maxCredit: number; 11 | createdAt: string; 12 | updatedAt: string; 13 | }; 14 | 15 | export type TCourse = { 16 | _id: string; 17 | title: string; 18 | prefix: string; 19 | code: number; 20 | credits: number; 21 | preRequisiteCourses: { course: string | null; isDeleted: boolean }[]; 22 | isDeleted: boolean; 23 | }; 24 | -------------------------------------------------------------------------------- /src/types/global.ts: -------------------------------------------------------------------------------- 1 | import { BaseQueryApi } from '@reduxjs/toolkit/query'; 2 | 3 | export type TError = { 4 | data: { 5 | message: string; 6 | stack: string; 7 | success: boolean; 8 | }; 9 | status: number; 10 | }; 11 | 12 | export type TMeta = { 13 | limit: number; 14 | page: number; 15 | total: number; 16 | totalPage: number; 17 | }; 18 | 19 | export type TResponse = { 20 | data?: T; 21 | error?: TError; 22 | meta?: TMeta; 23 | success: boolean; 24 | message: string; 25 | }; 26 | 27 | export type TResponseRedux = TResponse & BaseQueryApi; 28 | 29 | export type TQueryParam = { 30 | name: string; 31 | value: boolean | React.Key; 32 | }; 33 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './global'; 2 | export * from './sidebar.type'; 3 | export * from './academicManagement.type'; 4 | export * from './userManagement.type'; 5 | export * from './courseManagement.type'; 6 | -------------------------------------------------------------------------------- /src/types/sidebar.type.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export type TRoute = { 4 | path: string; 5 | element: ReactNode; 6 | }; 7 | export type TSidebarItem = 8 | | { 9 | key: string; 10 | label: ReactNode; 11 | children?: TSidebarItem[]; 12 | } 13 | | undefined; 14 | 15 | export type TUserPath = { 16 | name?: string; 17 | path?: string; 18 | element?: ReactNode; 19 | children?: TUserPath[]; 20 | }; 21 | -------------------------------------------------------------------------------- /src/types/studentCourse.type.ts: -------------------------------------------------------------------------------- 1 | export type TOfferedCourse = { 2 | _id: string; 3 | semesterRegistration: string; 4 | academicSemester: string; 5 | academicFaculty: string; 6 | academicDepartment: string; 7 | course: TCourse; 8 | faculty: string; 9 | maxCapacity: number; 10 | section: number; 11 | days: string[]; 12 | startTime: string; 13 | endTime: string; 14 | createdAt: string; 15 | updatedAt: string; 16 | __v: number; 17 | enrolledCourses: any[]; 18 | completedCourses: any[]; 19 | completedCourseIds: any[]; 20 | isPreRequisitesFulFilled: boolean; 21 | isAlreadyEnrolled: boolean; 22 | }; 23 | 24 | export type TCourse = { 25 | _id: string; 26 | title: string; 27 | prefix: string; 28 | code: number; 29 | credits: number; 30 | preRequisiteCourses: any[]; 31 | isDeleted: boolean; 32 | __v: number; 33 | }; 34 | -------------------------------------------------------------------------------- /src/types/userManagement.type.ts: -------------------------------------------------------------------------------- 1 | import { TAcademicDepartment, TAcademicFaculty, TAcademicSemester } from '.'; 2 | 3 | export interface TStudent { 4 | _id: string; 5 | id: string; 6 | user: TUser; 7 | name: TName; 8 | gender: string; 9 | dateOfBirth: string; 10 | email: string; 11 | contactNo: string; 12 | emergencyContactNo: string; 13 | bloogGroup: string; 14 | presentAddress: string; 15 | permanentAddress: string; 16 | guardian: TGuardian; 17 | localGuardian: TLocalGuardian; 18 | profileImg: string; 19 | admissionSemester: TAcademicSemester; 20 | isDeleted: boolean; 21 | academicDepartment: TAcademicDepartment; 22 | academicFaculty: TAcademicFaculty; 23 | fullName: string; 24 | } 25 | 26 | export type TUser = { 27 | _id: string; 28 | id: string; 29 | email: string; 30 | needsPasswordChange: boolean; 31 | role: string; 32 | status: string; 33 | isDeleted: boolean; 34 | createdAt: string; 35 | updatedAt: string; 36 | __v: number; 37 | }; 38 | 39 | export type TName = { 40 | firstName: string; 41 | middleName: string; 42 | lastName: string; 43 | _id: string; 44 | }; 45 | 46 | export type TGuardian = { 47 | fatherName: string; 48 | fatherOccupation: string; 49 | fatherContactNo: string; 50 | motherName: string; 51 | motherOccupation: string; 52 | motherContactNo: string; 53 | _id: string; 54 | }; 55 | 56 | export type TLocalGuardian = { 57 | name: string; 58 | occupation: string; 59 | contactNo: string; 60 | address: string; 61 | _id: string; 62 | }; 63 | -------------------------------------------------------------------------------- /src/utils/routesGenerator.ts: -------------------------------------------------------------------------------- 1 | import { TRoute, TUserPath } from '../types'; 2 | 3 | export const routeGenerator = (items: TUserPath[]) => { 4 | const routes = items.reduce((acc: TRoute[], item) => { 5 | if (item.path && item.element) { 6 | acc.push({ 7 | path: item.path, 8 | element: item.element, 9 | }); 10 | } 11 | 12 | if (item.children) { 13 | item.children.forEach((child) => { 14 | acc.push({ 15 | path: child.path!, 16 | element: child.element, 17 | }); 18 | }); 19 | } 20 | 21 | return acc; 22 | }, []); 23 | 24 | return routes; 25 | }; 26 | -------------------------------------------------------------------------------- /src/utils/sidebarItemsGenerator.tsx: -------------------------------------------------------------------------------- 1 | import { TSidebarItem, TUserPath } from '../types'; 2 | import { NavLink } from 'react-router-dom'; 3 | 4 | export const sidebarItemsGenerator = (items: TUserPath[], role: string) => { 5 | const sidebarItems = items.reduce((acc: TSidebarItem[], item) => { 6 | if (item.path && item.name) { 7 | acc.push({ 8 | key: item.name, 9 | label: {item.name}, 10 | }); 11 | } 12 | 13 | if (item.children) { 14 | acc.push({ 15 | key: item.name, 16 | label: item.name, 17 | children: item.children.map((child) => { 18 | if (child.name) { 19 | return { 20 | key: child.name, 21 | label: ( 22 | {child.name} 23 | ), 24 | }; 25 | } 26 | }), 27 | }); 28 | } 29 | 30 | return acc; 31 | }, []); 32 | 33 | return sidebarItems; 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/verifyToken.ts: -------------------------------------------------------------------------------- 1 | import { jwtDecode } from 'jwt-decode'; 2 | 3 | export const verifyToken = (token: string) => { 4 | return jwtDecode(token); 5 | }; 6 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // const adminPaths2 = [ 2 | // { 3 | // name: 'Dashboard', 4 | // path: 'dashboard', 5 | // element: 'ADMIN_DASHBOARD', 6 | // }, 7 | // { 8 | // name: 'User Management', 9 | // children: [ 10 | // { 11 | // name: 'Create Admin', 12 | // path: 'create-admin', 13 | // element: 'CREATE_ADMIN', 14 | // }, 15 | // { 16 | // name: 'Create Faculty', 17 | // path: 'create-faculty', 18 | // element: 'CREATE_FACULTY', 19 | // }, 20 | // { 21 | // name: 'Create Student', 22 | // path: 'create-student', 23 | // element: 'CREATE_STUDENT', 24 | // }, 25 | // ], 26 | // }, 27 | // ]; 28 | 29 | // const newArray = adminPaths2.reduce((acc, item) => { 30 | // if (item.path && item.name) { 31 | // acc.push({ 32 | // key: item.name, 33 | // label: 'NAVLINK', 34 | // }); 35 | // } 36 | 37 | // if (item.children) { 38 | // acc.push({ 39 | // key: item.name, 40 | // label: item.name, 41 | // children: item.children.map((child) => ({ 42 | // key: child.name, 43 | // label: 'NAVLINK', 44 | // })), 45 | // }); 46 | // } 47 | 48 | // return acc; 49 | // }, []); 50 | 51 | // const newArray = adminPaths2.reduce((acc, item) => { 52 | // if (item.path && item.element) { 53 | // acc.push({ 54 | // path: item.path, 55 | // element: item.element, 56 | // }); 57 | // } 58 | 59 | // if (item.children) { 60 | // item.children.forEach((child) => { 61 | // acc.push({ 62 | // path: child.path, 63 | // element: child.element, 64 | // }); 65 | // }); 66 | // } 67 | 68 | // return acc; 69 | // }, []); 70 | 71 | const obj = { 72 | name: 'Mezba', 73 | role: 'mentor', 74 | age: 18, 75 | greet: () => { 76 | return 'Hello World'; 77 | }, 78 | }; 79 | 80 | console.log(JSON.stringify(obj)); 81 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------