├── .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 |
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 |
26 | {error && {error.message}}
27 |
28 | )}
29 | />
30 | );
31 | };
32 |
33 | export default PHSelect;
34 |
--------------------------------------------------------------------------------
/src/components/form/PHSelectWithWatch.tsx:
--------------------------------------------------------------------------------
1 | import { Form, Select } from 'antd';
2 | import { useEffect } from 'react';
3 | import { Controller, useFormContext, useWatch } from 'react-hook-form';
4 |
5 | type TPHSelectProps = {
6 | label: string;
7 | name: string;
8 | options: { value: string; label: string; disabled?: boolean }[] | undefined;
9 | disabled?: boolean;
10 | mode?: 'multiple' | undefined;
11 | onValueChange: React.Dispatch>;
12 | };
13 |
14 | const PHSelectWithWatch = ({
15 | label,
16 | name,
17 | options,
18 | disabled,
19 | mode,
20 | onValueChange,
21 | }: TPHSelectProps) => {
22 | const method = useFormContext();
23 | const inputValue = useWatch({
24 | control: method.control,
25 | name,
26 | });
27 |
28 | useEffect(() => {
29 | onValueChange(inputValue);
30 | }, [inputValue]);
31 |
32 | return (
33 | (
36 |
37 |
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 |
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 |
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 |
--------------------------------------------------------------------------------