├── .gitignore ├── .nvmrc ├── INSTALLATION.md ├── LICENSE ├── README.md ├── SECURITY.md ├── client ├── .env.example ├── _redirects ├── eslint.config.js ├── index.html ├── netlify.toml ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── cursor.png │ ├── hrms.png │ ├── images.png │ ├── menu.svg │ ├── metro.png │ ├── robots.txt │ ├── sitemap.xml │ ├── unknown.jpeg │ └── verify.avif ├── src │ ├── App.css │ ├── App.jsx │ ├── admin │ │ ├── attendance │ │ │ ├── Attendance.jsx │ │ │ └── CheckAttendance.jsx │ │ ├── complaint │ │ │ └── Complaint.jsx │ │ ├── dashboard │ │ │ └── Dashboard.jsx │ │ ├── department │ │ │ └── Department.jsx │ │ ├── employee │ │ │ ├── CreateEmployee.jsx │ │ │ ├── Employee.jsx │ │ │ ├── UpdateEmployee.jsx │ │ │ └── ViewEmployee.jsx │ │ ├── feedback │ │ │ └── Feedback.jsx │ │ ├── leave │ │ │ ├── EmployeeOnLeave.jsx │ │ │ └── LeaveRequest.jsx │ │ ├── mail │ │ │ └── Communication.jsx │ │ ├── payroll │ │ │ └── Payroll.jsx │ │ ├── performance │ │ │ └── Performance.jsx │ │ ├── recruitment │ │ │ ├── JobApplications.jsx │ │ │ ├── JobOpenings.jsx │ │ │ └── PostJob.jsx │ │ └── report │ │ │ └── Report.jsx │ ├── app │ │ ├── admin.jsx │ │ └── employee.jsx │ ├── auth │ │ ├── EmailConfirmation.jsx │ │ ├── ForgetPassword.jsx │ │ ├── InvalidResetLink.jsx │ │ ├── Login.jsx │ │ └── ResetPassword.jsx │ ├── axios │ │ └── axiosInstance.js │ ├── careers │ │ └── Career.jsx │ ├── components │ │ ├── shared │ │ │ ├── buttons │ │ │ │ └── FilterButton.jsx │ │ │ ├── cards │ │ │ │ ├── InfoCard.jsx │ │ │ │ └── JobCard.jsx │ │ │ ├── charts │ │ │ │ ├── BarGraph.jsx │ │ │ │ ├── LineChart.jsx │ │ │ │ └── Pie.jsx │ │ │ ├── chat │ │ │ │ ├── ChatPanel.jsx │ │ │ │ ├── ContactItem.jsx │ │ │ │ └── Message.jsx │ │ │ ├── error │ │ │ │ ├── Error.jsx │ │ │ │ ├── FetchError.jsx │ │ │ │ ├── NoDataMessage.jsx │ │ │ │ └── NotFound.jsx │ │ │ ├── loaders │ │ │ │ ├── ButtonLoader.jsx │ │ │ │ ├── ChatbotLoader.jsx │ │ │ │ ├── ComponentLoader.jsx │ │ │ │ └── Loader.jsx │ │ │ ├── modals │ │ │ │ ├── ApplicantionModal.jsx │ │ │ │ ├── DepartmentModal.jsx │ │ │ │ ├── ImportExcelModal.jsx │ │ │ │ ├── InviteModal.jsx │ │ │ │ ├── JobApplicationModal.jsx │ │ │ │ ├── JobOpeningModal.jsx │ │ │ │ ├── Modal.jsx │ │ │ │ ├── MonthSheetModal.jsx │ │ │ │ ├── PayrollModal.jsx │ │ │ │ ├── PerformanceModal.jsx │ │ │ │ ├── ProfileModal.jsx │ │ │ │ ├── RemarksModal.jsx │ │ │ │ ├── RoleModal.jsx │ │ │ │ ├── SettingModal.jsx │ │ │ │ ├── SheetModal.jsx │ │ │ │ └── SubstituteModal.jsx │ │ │ └── others │ │ │ │ ├── ChatPanel.jsx │ │ │ │ ├── FilterBar.jsx │ │ │ │ └── Pagination.jsx │ │ └── ui │ │ │ ├── EmployeeSidebar.jsx │ │ │ ├── Footer.jsx │ │ │ └── Sidebar.jsx │ ├── constants │ │ └── index.js │ ├── context │ │ └── index.jsx │ ├── hooks │ │ └── useDebounce.jsx │ ├── main.jsx │ ├── pages │ │ ├── attendance │ │ │ ├── Attendance.jsx │ │ │ └── MarkAttendance.jsx │ │ ├── complaint │ │ │ └── Complaint.jsx │ │ ├── feedback │ │ │ └── Feedback.jsx │ │ ├── home │ │ │ └── Home.jsx │ │ ├── leave │ │ │ └── Leave.jsx │ │ └── updates │ │ │ └── Update.jsx │ ├── reducers │ │ ├── attendance.reducer.js │ │ ├── authentication.reducer.js │ │ ├── complaint.reducer.js │ │ ├── department.reducer.js │ │ ├── employee.reducer.js │ │ ├── feedback.reducer.js │ │ ├── inshights.reducer.js │ │ ├── leave.reducer.js │ │ ├── payroll.reducer.js │ │ ├── performance.reducer.js │ │ ├── recruitment.reducer.js │ │ ├── role.reducer.js │ │ └── update.reducer.js │ ├── services │ │ ├── attendance.service.js │ │ ├── authentication.service.js │ │ ├── chat.service.js │ │ ├── complaint.service.js │ │ ├── department.service.js │ │ ├── employee.service.js │ │ ├── feedback.service.js │ │ ├── insights.service.js │ │ ├── leave.service.js │ │ ├── payroll.service.js │ │ ├── performance.service.js │ │ ├── recruitment.service.js │ │ └── role.service.js │ ├── store │ │ └── index.js │ ├── utils │ │ └── index.js │ └── validations │ │ └── index.js ├── tailwind.config.js └── vite.config.js ├── public └── hrms.png └── server ├── .env.example ├── package-lock.json ├── package.json ├── src ├── config │ └── index.js ├── controllers │ ├── attendance.controller.js │ ├── authentication.controller.js │ ├── chatbot.controller.js │ ├── complaint.controller.js │ ├── department.controller.js │ ├── employee.controller.js │ ├── feedback.controller.js │ ├── insights.controller.js │ ├── leave.controller.js │ ├── payroll.controller.js │ ├── performance.controller.js │ ├── recruitment.controller.js │ ├── role.controller.js │ └── update.controller.js ├── doc │ └── index.js ├── gemini │ └── index.js ├── index.js ├── middlewares │ └── index.js ├── models │ ├── attendance.model.js │ ├── complaint.model.js │ ├── department.model.js │ ├── employee.model.js │ ├── feedback.model.js │ ├── leave.model.js │ ├── payroll.model.js │ ├── performance.model.js │ ├── recruitment.model.js │ ├── role.model.js │ ├── session.model.js │ └── update.model.js ├── predictions │ ├── chatbot.js │ ├── index.js │ ├── sentiment.js │ └── substitute.js ├── public │ └── welcome.html ├── routes │ ├── attendance.routes.js │ ├── authentication.routes.js │ ├── complaint.routes.js │ ├── department.routes.js │ ├── employee.routes.js │ ├── feedback.routes.js │ ├── index.routes.js │ ├── insights.routes.js │ ├── leave.routes.js │ ├── payroll.routes.js │ ├── performance.routes.js │ ├── recruitment.routes.js │ └── role.routes.js ├── seeders │ └── index.js ├── setup │ └── index.js ├── templates │ └── index.js └── utils │ └── index.js ├── test.yaml └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js dependencies 2 | client/node_modules 3 | server/node_modules 4 | 5 | 6 | # Ignore all .env files 7 | server/.env 8 | server/host.env 9 | server/.vercel 10 | client/.env 11 | 12 | # Ignore client-specific files 13 | client/format.json 14 | client/employees.xlsx 15 | 16 | # Ignore server-specific files 17 | server/src/uploads 18 | server/src/qrcodes 19 | 20 | # Logs 21 | client/*.log 22 | server/*.log 23 | client/npm-debug.log* 24 | server/npm-debug.log* 25 | client/yarn-debug.log* 26 | server/yarn-debug.log* 27 | client/yarn-error.log* 28 | server/yarn-error.log* 29 | 30 | # Build outputs 31 | client/build 32 | client/dist 33 | 34 | # Cache and system files 35 | .DS_Store 36 | Thumbs.db 37 | 38 | # IDE and editor settings 39 | .vscode/ 40 | .idea/ 41 | *.swp 42 | *.swo 43 | 44 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.18.0 -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | ## _Prerequisites_ : 8 | 9 | - _Node.js and npm must be installed on your machine_ 10 | - _You can install them from the [Node.js Official Website](https://nodejs.org/)_ 11 | 12 | ## _Installation Steps_ : 13 | 14 | - _Clone the repository to your local machine:_ 15 | ```bash 16 | git clone https://github.com/WhatsWrongOB/AI-HRMS.git 17 | ``` 18 | - _Navigate to the project’s client folder and install dependencies:_ 19 | ```bash 20 | cd client 21 | npm install 22 | ``` 23 | 24 | - _Navigate to the project’s server folder and install dependencies:_ 25 | ```bash 26 | cd server 27 | npm install 28 | ``` 29 | 30 | ## _Environment Variables_ : 31 | 32 | - _In the server and client folder, rename the .env.example file to .env:_ 33 | - _Fill in all the required environment variables for proper configuration._ 34 | ```bash 35 | # In client .env 36 | VITE_URL= 37 | VITE_ENV= 38 | 39 | # In server .env 40 | PORT= 41 | JWTSECRET= 42 | CLIENT_URL= 43 | SERVER_URL= 44 | MONGO_URI= 45 | SMTP_HOST= 46 | SMTP_PORT= 47 | SMTP_USER= 48 | SMTP_PASS= 49 | GEMINI= 50 | LATITUDE= 51 | LONGITUDE= 52 | CLOUDINARY_CLOUD_NAME= 53 | CLOUDINARY_API_KEY= 54 | CLOUDINARY_API_SECRET= 55 | ``` 56 | 57 | ## _Initial Project Setup_ : 58 | 59 | - _In the server folder, run the setup command to seed the database with an initial data_ 60 | ```bash 61 | npm run setup 62 | ``` 63 | - _This will create an employee with ID: 000 & Password: password_ 64 | - _You can use these credentials to log in to the hrms application._ 65 | 66 | ## _Running the Project_ : 67 | 68 | - _Start the client and server of the application as needed._ 69 | ```bash 70 | # In client/ 71 | npm run dev 72 | 73 | # In server/ 74 | npm run dev 75 | ``` 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Obaid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |

AI-Driven Human Resource Management ⚡

6 |

7 |

Streamlining HR Operations | AI-Powered Insights

8 |

9 | 10 | 11 | ``` 12 | Give a Star ⭐️ & Fork to this project ... Happy coding! 🤩` 13 | ``` 14 | 15 | *The AI-Driven HRMS is a robust HR management system, built on the MERN stack. It integrates AI for sentiment analysis, automated substitute assignment, and a Gemini-powered chatbot* 16 | 17 |
18 | 19 | ***Self-hosted Version*** : [_click here_](https://metrohrms.site) 20 | 21 | ***Installation Guide*** : [_click here_](https://github.com/WhatsWrongOB/AI-HRMS/blob/main/INSTALLATION.md) 22 | 23 | ## _Key Features_ : 24 | 25 | - _Robust Authentication_ 26 | - _Employee Management_ 27 | - _Attendance Management_ 28 | - _Leave Management_ 29 | - _Payroll Management_ 30 | - _Recruitment Management_ 31 | - _Performance Management_ 32 | - _Complaint Management_ 33 | - _Feedback Management_ 34 | - _Reports & Analytics_ 35 | 36 | ## _Standout Features_ : 37 | 38 | - _AI Sentiment Analysis_ 39 | - _AI Substitute Assignment_ 40 | - _QR Code-Based Attendance_ 41 | - _GEMINI-Powered Chatbot_ 42 | 43 | ## _Impact or Outcomes_ : 44 | 45 | - _Faster Decision-Making_ 46 | - _Reduced Manual Workload_ 47 | - _Improved Attendance Accuracy_ 48 | - _Enhanced Employee Satisfaction_ 49 | 50 | 51 | ## _Tech Stack_ : 52 | 53 | - _MERN Stack for Development_ 54 | - _Redux for State Management_ 55 | - _AI Integration with Gemini_ 56 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | 2 | VITE_URL=http://localhost:3000/api 3 | VITE_ENV=development -------------------------------------------------------------------------------- /client/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /client/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Metro HR 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /client/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "https://metrohrms.netlify.app/*" 3 | to = "https://metrohrms.site/:splat" 4 | status = 301 5 | force = true 6 | 7 | [[redirects]] 8 | from = "/*" 9 | to = "/index.html" 10 | status = 200 11 | 12 | [build] 13 | image = "ubuntu-22.04" 14 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hrms", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "AI Driven Human Resource Managemen System - Final Year Project", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite --host", 9 | "build": "vite build", 10 | "lint": "eslint .", 11 | "preview": "vite preview" 12 | }, 13 | "author": "Obaid Ali Siddiqui", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/WhatsWrongOB/AI-HRMS" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/WhatsWrongOB/AI-HRMS/issues" 20 | }, 21 | "homepage": "https://metrohrms.netlify.app", 22 | "keywords": [ 23 | "HRMS", 24 | "AI", 25 | "MERN", 26 | "Human Resources", 27 | "Payroll", 28 | "Attendance" 29 | ], 30 | "dependencies": { 31 | "@fortawesome/fontawesome-free": "^6.7.2", 32 | "@hookform/resolvers": "^3.9.1", 33 | "@reduxjs/toolkit": "^2.3.0", 34 | "axios": "^1.7.7", 35 | "chart.js": "^4.4.6", 36 | "filepond": "^4.32.7", 37 | "react": "^18.3.1", 38 | "react-chartjs-2": "^5.2.0", 39 | "react-dom": "^18.3.1", 40 | "react-filepond": "^7.1.3", 41 | "react-helmet": "^6.1.0", 42 | "react-hook-form": "^7.54.1", 43 | "react-hot-toast": "^2.4.1", 44 | "react-icons": "^5.4.0", 45 | "react-markdown": "^9.0.3", 46 | "react-redux": "^9.1.2", 47 | "react-router-dom": "^6.27.0", 48 | "socket.io-client": "^4.8.1", 49 | "typeface-poppins": "^1.1.13", 50 | "xlsx": "^0.18.5", 51 | "zod": "^3.24.2" 52 | }, 53 | "devDependencies": { 54 | "@eslint/js": "^9.13.0", 55 | "@types/react": "^18.3.11", 56 | "@types/react-dom": "^18.3.1", 57 | "@vitejs/plugin-react": "^4.3.3", 58 | "autoprefixer": "^10.4.20", 59 | "eslint": "^9.13.0", 60 | "eslint-plugin-react": "^7.37.1", 61 | "eslint-plugin-react-hooks": "^5.0.0", 62 | "eslint-plugin-react-refresh": "^0.4.13", 63 | "globals": "^15.11.0", 64 | "postcss": "^8.4.47", 65 | "tailwindcss": "^3.4.14", 66 | "vite": "^5.4.9" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/cursor.png -------------------------------------------------------------------------------- /client/public/hrms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/hrms.png -------------------------------------------------------------------------------- /client/public/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/images.png -------------------------------------------------------------------------------- /client/public/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /client/public/metro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/metro.png -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /private/ 3 | Sitemap: https://metrohrms.site/sitemap.xml -------------------------------------------------------------------------------- /client/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | https://metrohrms.site/ 6 | 2025-08-04 7 | 1.00 8 | 9 | 10 | 11 | https://metrohrms.site/careers 12 | 2025-08-04 13 | 0.80 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/public/unknown.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/unknown.jpeg -------------------------------------------------------------------------------- /client/public/verify.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ObaidBuilds/AI-HRMS/8202fd65f10700c34731aff5d889845297a6a1a2/client/public/verify.avif -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import "typeface-poppins"; 2 | import Login from "./auth/Login"; 3 | import AdminApp from "./app/admin"; 4 | import EmployeeApp from "./app/employee"; 5 | import { Toaster } from "react-hot-toast"; 6 | import { ThemeProvider } from "./context"; 7 | import { useSelector } from "react-redux"; 8 | import "@fortawesome/fontawesome-free/css/all.min.css"; 9 | import Loader from "./components/shared/loaders/Loader"; 10 | import { Navigate, Route, Routes } from "react-router-dom"; 11 | import { lazy, Suspense, useEffect, useState } from "react"; 12 | 13 | const Career = lazy(() => import("./careers/Career")); 14 | const ResetPassword = lazy(() => import("./auth/ResetPassword")); 15 | const ForgetPassword = lazy(() => import("./auth/ForgetPassword")); 16 | const InvalidResetLink = lazy(() => import("./auth/InvalidResetLink")); 17 | const EmailConfirmation = lazy(() => import("./auth/EmailConfirmation")); 18 | 19 | function HrmsForMetroCashAndCarry() { 20 | const { user } = useSelector((state) => state.authentication); 21 | 22 | useEffect(() => { 23 | const syncLogout = (event) => { 24 | if (event.key === "logout") { 25 | sessionStorage.clear(); 26 | localStorage.clear(); 27 | 28 | window.location.href = "/login"; 29 | } 30 | }; 31 | 32 | window.addEventListener("storage", syncLogout); 33 | return () => { 34 | window.removeEventListener("storage", syncLogout); 35 | }; 36 | }, []); 37 | 38 | if (user?.authority === "admin") return ; 39 | if (user?.authority === "employee") return ; 40 | 41 | return ; 42 | } 43 | 44 | function EmployeeRouter() { 45 | return ( 46 | 47 | 48 | } /> 49 | 50 | 51 | ); 52 | } 53 | 54 | function AdminRouter() { 55 | return ( 56 | 57 | 58 | } /> 59 | 60 | 61 | ); 62 | } 63 | 64 | function AuthRouter() { 65 | return ( 66 | 67 | } /> 68 | } /> 69 | } /> 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | 75 | ); 76 | } 77 | 78 | const RootApp = () => { 79 | return ( 80 | }> 81 | 82 | 83 | 84 | ); 85 | }; 86 | 87 | export default RootApp; 88 | -------------------------------------------------------------------------------- /client/src/admin/mail/Communication.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { FaSearch } from "react-icons/fa"; 3 | import ChatPanel from "../../components/shared/chat/ChatPanel"; 4 | import ContactItem from "../../components/shared/chat/ContactItem"; 5 | 6 | const employees = [ 7 | { 8 | id: 1, 9 | name: "Obaid Ali", 10 | designation: "Manager", 11 | profilePic: "https://randomuser.me/api/portraits/men/1.jpg", 12 | status: "online", 13 | lastSeen: "2 min ago", 14 | }, 15 | { 16 | id: 2, 17 | name: "Noman Rehan", 18 | designation: "Supervisor", 19 | profilePic: "https://randomuser.me/api/portraits/women/1.jpg", 20 | status: "online", 21 | lastSeen: "5 min ago", 22 | }, 23 | { 24 | id: 3, 25 | name: "Mujtaba Abid", 26 | designation: "Developer", 27 | profilePic: "https://randomuser.me/api/portraits/men/2.jpg", 28 | status: "offline", 29 | lastSeen: "1 hour ago", 30 | }, 31 | { 32 | id: 4, 33 | name: "Mohsin Khan", 34 | designation: "Designer", 35 | profilePic: "https://randomuser.me/api/portraits/women/2.jpg", 36 | status: "online", 37 | lastSeen: "Just now", 38 | }, 39 | { 40 | id: 5, 41 | name: "Qaim Ali", 42 | designation: "QA Engineer", 43 | profilePic: "https://randomuser.me/api/portraits/men/3.jpg", 44 | status: "offline", 45 | lastSeen: "3 hours ago", 46 | }, 47 | ]; 48 | 49 | const Communication = () => { 50 | 51 | const [message, setMessage] = useState(""); 52 | const [searchTerm, setSearchTerm] = useState(""); 53 | const [showContacts, setShowContacts] = useState(true); 54 | const [selectedEmployee, setSelectedEmployee] = useState(null); 55 | 56 | const filteredEmployees = employees.filter( 57 | (employee) => 58 | employee.name.toLowerCase().includes(searchTerm.toLowerCase()) || 59 | employee.designation.toLowerCase().includes(searchTerm.toLowerCase()) 60 | ); 61 | 62 | const handleBack = () => { 63 | setShowContacts(true); 64 | setSelectedEmployee(null) 65 | }; 66 | 67 | const handleSelectEmployee = (employee) => { 68 | setSelectedEmployee(employee); 69 | setShowContacts(false); 70 | }; 71 | 72 | return ( 73 |
75 |
80 |
81 |

82 | Contacts 83 |

84 |
85 | 86 | setSearchTerm(e.target.value)} 92 | /> 93 |
94 |
95 | 96 |
99 | {filteredEmployees.length > 0 ? ( 100 | filteredEmployees.map((employee) => ( 101 | 107 | )) 108 | ) : ( 109 |
110 | No contacts found 111 |
112 | )} 113 |
114 |
115 | 116 | 122 |
123 | ); 124 | }; 125 | 126 | export default Communication; 127 | -------------------------------------------------------------------------------- /client/src/app/admin.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux"; 2 | import Sidebar from "../components/ui/Sidebar"; 3 | import { lazy, Suspense, useEffect } from "react"; 4 | import { Route, Routes } from "react-router-dom"; 5 | import { getRoles } from "../services/role.service"; 6 | import Loader from "../components/shared/loaders/Loader"; 7 | import NotFound from "../components/shared/error/NotFound"; 8 | import { getInsights } from "../services/insights.service"; 9 | import { getDepartments } from "../services/department.service"; 10 | 11 | const Dashboard = lazy(() => import("../admin/dashboard/Dashboard")); 12 | const Employee = lazy(() => import("../admin/employee/Employee")); 13 | const AddEmployee = lazy(() => import("../admin/employee/CreateEmployee")); 14 | const EditEmployee = lazy(() => import("../admin/employee/UpdateEmployee")); 15 | const ViewEmployee = lazy(() => import("../admin/employee/ViewEmployee")); 16 | const Department = lazy(() => import("../admin/department/Department")); 17 | const Attendance = lazy(() => import("../admin/attendance/Attendance")); 18 | const CheckAttendance = lazy(() => 19 | import("../admin/attendance/CheckAttendance") 20 | ); 21 | const Feedback = lazy(() => import("../admin/feedback/Feedback")); 22 | const LeaveRequest = lazy(() => import("../admin/leave/LeaveRequest")); 23 | const EmployeeOnLeave = lazy(() => import("../admin/leave/EmployeeOnLeave")); 24 | const Complaint = lazy(() => import("../admin/complaint/Complaint")); 25 | const JobApplications = lazy(() => 26 | import("../admin/recruitment/JobApplications") 27 | ); 28 | const JobOpenings = lazy(() => import("../admin/recruitment/JobOpenings")); 29 | const PostJob = lazy(() => import("../admin/recruitment/PostJob")); 30 | const Performance = lazy(() => import("../admin/performance/Performance")); 31 | const Report = lazy(() => import("../admin/report/Report")); 32 | const Payroll = lazy(() => import("../admin/payroll/Payroll")); 33 | 34 | const AdminApp = () => { 35 | const dispatch = useDispatch(); 36 | 37 | useEffect(() => { 38 | dispatch(getRoles()); 39 | dispatch(getInsights()); 40 | dispatch(getDepartments()); 41 | }, []); 42 | 43 | return ( 44 |
48 | 49 | }> 50 |
54 | 55 | } /> 56 | } /> 57 | } /> 58 | } /> 59 | } /> 60 | } /> 61 | } /> 62 | } /> 63 | } /> 64 | } /> 65 | } /> 66 | } /> 67 | } /> 68 | } /> 69 | } /> 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | 76 |
77 |
78 |
79 | ); 80 | }; 81 | 82 | export default AdminApp; 83 | -------------------------------------------------------------------------------- /client/src/app/employee.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux"; 2 | import { lazy, Suspense, useEffect } from "react"; 3 | import { Route, Routes } from "react-router-dom"; 4 | import Loader from "../components/shared/loaders/Loader"; 5 | import NotFound from "../components/shared/error/NotFound"; 6 | import EmployeeSidebar from "../components/ui/EmployeeSidebar"; 7 | import { getEmployeeInsights } from "../services/insights.service"; 8 | 9 | const Home = lazy(() => import("../pages/home/Home")); 10 | const Leave = lazy(() => import("../pages/leave/Leave")); 11 | const Update = lazy(() => import("../pages/updates/Update")); 12 | const Feedback = lazy(() => import("../pages/feedback/Feedback")); 13 | const Complaint = lazy(() => import("../pages/complaint/Complaint")); 14 | const Attendance = lazy(() => import("../pages/attendance/Attendance")); 15 | const MarkAttendance = lazy(() => import("../pages/attendance/MarkAttendance")); 16 | 17 | const EmployeeApp = () => { 18 | const dispatch = useDispatch(); 19 | 20 | useEffect(() => { 21 | dispatch(getEmployeeInsights()); 22 | }, []); 23 | 24 | return ( 25 |
29 | 30 | }> 31 |
35 | 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | 45 |
46 |
47 |
48 | ); 49 | }; 50 | 51 | export default EmployeeApp; 52 | -------------------------------------------------------------------------------- /client/src/auth/EmailConfirmation.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Helmet } from "react-helmet"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const EmailConfirmation = () => { 6 | return ( 7 | <> 8 | 9 | Email Confirmation - Metro HR 10 | 11 | 12 |
13 |
14 | verify_email 19 |
20 |

21 | A password reset link has been sent to your email. Follow the 22 | instructions to reset password 23 |

24 | 28 | Back to Login Page 29 | 30 |
31 |
32 |
33 | 34 | ); 35 | }; 36 | 37 | export default EmailConfirmation; 38 | -------------------------------------------------------------------------------- /client/src/auth/InvalidResetLink.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Helmet } from "react-helmet"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const InvalidResetLink = () => { 6 | return ( 7 | <> 8 | 9 | Invalid Reset Link - Metro HR 10 | 11 | 12 |
13 |
14 | verify_email 19 |
20 |

21 | Oops! This reset link has expired or is invalid. Try requesting a 22 | new one. 23 |

24 | 28 | Back to Login Page 29 | 30 |
31 |
32 |
33 | 34 | ); 35 | }; 36 | 37 | export default InvalidResetLink; 38 | -------------------------------------------------------------------------------- /client/src/axios/axiosInstance.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getToken } from "../utils"; 3 | 4 | const axiosInstance = axios.create({ 5 | baseURL: import.meta.env.VITE_URL || "http://localhost:3000", 6 | headers: { "Content-Type": "application/json" }, 7 | }); 8 | 9 | axiosInstance.interceptors.request.use( 10 | (config) => { 11 | const token = getToken(); 12 | if (token) config.headers.Authorization = `Bearer ${token}`; 13 | return config; 14 | }, 15 | (error) => Promise.reject(error) 16 | ); 17 | 18 | axiosInstance.interceptors.response.use( 19 | (response) => response, 20 | (error) => { 21 | const msg = error.response?.data?.message?.toLowerCase(); 22 | if ( 23 | error.response?.status === 500 && 24 | (msg.includes("jwt") || 25 | msg.includes("unauthorized") || 26 | msg.includes("session expired")) 27 | ) { 28 | sessionStorage.clear(); 29 | localStorage.clear(); 30 | 31 | window.location.href = "/login"; 32 | } 33 | return Promise.reject(error); 34 | } 35 | ); 36 | 37 | export default axiosInstance; 38 | -------------------------------------------------------------------------------- /client/src/components/shared/buttons/FilterButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const FilterButton = ({ setState, state, filter }) => { 4 | return ( 5 | 20 | ); 21 | }; 22 | 23 | export default FilterButton; 24 | -------------------------------------------------------------------------------- /client/src/components/shared/cards/InfoCard.jsx: -------------------------------------------------------------------------------- 1 | const InfoCard = ({ detail }) => { 2 | return ( 3 |
4 |
5 |

6 | {detail.title} 7 |

8 |
9 | 10 |

11 | {detail?.stats || 0} 12 |

13 | 14 |
15 |

16 | Since last month 17 |

18 |
19 | 20 |
21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | export default InfoCard; 28 | -------------------------------------------------------------------------------- /client/src/components/shared/cards/JobCard.jsx: -------------------------------------------------------------------------------- 1 | import { formatDate } from "../../../utils"; 2 | 3 | const JobCard = ({ job, onApply }) => { 4 | return ( 5 |
9 |
10 |
11 |

{job.title}

12 | 13 | {job.type} 14 | 15 |
16 | 17 |
18 | 19 | {job.department.name} 20 | 21 | 22 | {job.role.name} 23 | 24 |
25 | 26 |
27 |

28 | 29 | {job.location} 30 |

31 |

32 | ${job.minSalary}{" "} 33 | - ${job.maxSalary} 34 |

35 |
36 | 37 |

{job.description}

38 | 39 |
40 |

41 | Deadline:{" "} 42 | {formatDate(job.deadline)} 43 |

44 | 51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | export default JobCard; 58 | -------------------------------------------------------------------------------- /client/src/components/shared/charts/BarGraph.jsx: -------------------------------------------------------------------------------- 1 | import { Bar } from "react-chartjs-2"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | ArcElement, 11 | } from "chart.js"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | ArcElement, 18 | Title, 19 | Tooltip, 20 | Legend 21 | ); 22 | 23 | const BarGraph = ({ 24 | text, 25 | title, 26 | departments, 27 | departmentAttendancePercentage, 28 | }) => { 29 | const data = { 30 | labels: departments || [], 31 | datasets: [ 32 | { 33 | label: "Attendance Rate (%)", 34 | data: departmentAttendancePercentage || [], 35 | backgroundColor: [ 36 | "rgba(30, 144, 255, 0.7)", 37 | "rgba(220, 20, 60, 0.7)", 38 | "rgba(255, 193, 7, 0.7)", 39 | "rgba(32, 178, 170, 0.7)", 40 | "rgba(138, 43, 226, 0.7)", 41 | "rgba(255, 140, 0, 0.7)", 42 | ], 43 | 44 | borderColor: [ 45 | "rgba(30, 144, 255, 1)", 46 | "rgba(220, 20, 60, 1)", 47 | "rgba(255, 193, 7, 1)", 48 | "rgba(32, 178, 170, 1)", 49 | "rgba(138, 43, 226, 1)", 50 | "rgba(255, 140, 0, 1)", 51 | ], 52 | 53 | borderWidth: 1, 54 | }, 55 | ], 56 | }; 57 | 58 | const options = { 59 | responsive: true, 60 | plugins: { 61 | legend: { 62 | position: "top", 63 | }, 64 | title: { 65 | display: true, 66 | text: text, 67 | }, 68 | }, 69 | scales: { 70 | x: { 71 | grid: { 72 | display: true, 73 | }, 74 | }, 75 | y: { 76 | grid: { 77 | display: true, 78 | }, 79 | title: { 80 | display: true, 81 | text: title, 82 | }, 83 | }, 84 | }, 85 | }; 86 | 87 | return ; 88 | }; 89 | 90 | export default BarGraph; 91 | -------------------------------------------------------------------------------- /client/src/components/shared/charts/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Line } from "react-chartjs-2"; 3 | import { 4 | Chart as ChartJS, 5 | CategoryScale, 6 | LinearScale, 7 | PointElement, 8 | LineElement, 9 | Title, 10 | Tooltip, 11 | Legend, 12 | Filler, 13 | } from "chart.js"; 14 | 15 | ChartJS.register( 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement, 20 | Title, 21 | Tooltip, 22 | Legend, 23 | Filler 24 | ); 25 | 26 | const LineChart = ({ label, title, chartData }) => { 27 | const data = { 28 | labels: [ 29 | "Jan", 30 | "Feb", 31 | "Mar", 32 | "Apr", 33 | "May", 34 | "Jun", 35 | "Jul", 36 | "Aug", 37 | "Sep", 38 | "Oct", 39 | "Nov", 40 | "Dec", 41 | ], 42 | datasets: [ 43 | { 44 | label: label, 45 | data: chartData || [10, 30, 40, 25, 40, 74, 20, 60, 65, 76, 50, 40], 46 | fill: true, 47 | backgroundColor: "rgba(54, 162, 235, 0.2)", 48 | borderColor: "rgba(54, 162, 235, 1)", 49 | tension: 0.4, 50 | }, 51 | ], 52 | }; 53 | 54 | const options = { 55 | responsive: true, 56 | maintainAspectRatio: false, 57 | plugins: { 58 | title: { 59 | display: true, 60 | text: title, 61 | }, 62 | }, 63 | scales: { 64 | x: { 65 | position: "bottom", 66 | }, 67 | y: { 68 | position: "left", 69 | min: 0, 70 | max: 100, 71 | ticks: { 72 | stepSize: 20, 73 | callback: (value) => `${value}%`, 74 | }, 75 | }, 76 | }, 77 | }; 78 | 79 | return ( 80 |
81 | 82 |
83 | ); 84 | }; 85 | 86 | export default LineChart; 87 | -------------------------------------------------------------------------------- /client/src/components/shared/charts/Pie.jsx: -------------------------------------------------------------------------------- 1 | import { Pie } from "react-chartjs-2"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | Title, 8 | Tooltip, 9 | Legend, 10 | ArcElement, 11 | } from "chart.js"; 12 | import { useSelector } from "react-redux"; 13 | 14 | ChartJS.register( 15 | CategoryScale, 16 | LinearScale, 17 | BarElement, 18 | ArcElement, 19 | Title, 20 | Tooltip, 21 | Legend 22 | ); 23 | 24 | const PieGraph = ({ labels, title, label, data1, data2 }) => { 25 | const pieData = { 26 | labels: [labels.category1, labels.category2], 27 | datasets: [ 28 | { 29 | label: label, 30 | data: [data1, data2], 31 | backgroundColor: ["rgba(30, 144, 255, 0.7)", "rgba(220, 20, 60, 0.7)"], 32 | 33 | borderColor: ["rgba(30, 144, 255, 1)", "rgba(220, 20, 60, 1)"], 34 | 35 | borderWidth: 1, 36 | }, 37 | ], 38 | }; 39 | 40 | const pieOptions = { 41 | responsive: true, 42 | plugins: { 43 | legend: { 44 | position: "top", 45 | }, 46 | title: { 47 | display: true, 48 | text: title, 49 | }, 50 | }, 51 | }; 52 | 53 | return ; 54 | }; 55 | 56 | export default PieGraph; 57 | -------------------------------------------------------------------------------- /client/src/components/shared/chat/ContactItem.jsx: -------------------------------------------------------------------------------- 1 | const ContactItem = ({ employee, isSelected, onSelect }) => ( 2 |
onSelect(employee)} 9 | > 10 |
11 | {employee.name} 16 | 21 |
22 |
23 |

24 | {employee.name} 25 |

26 |
27 |

28 | {employee.designation} 29 |

30 |

31 | ~ {employee.lastSeen} 32 |

33 |
34 |
35 |
36 | ); 37 | 38 | export default ContactItem; 39 | -------------------------------------------------------------------------------- /client/src/components/shared/chat/Message.jsx: -------------------------------------------------------------------------------- 1 | const Message = ({ isSender, text, time }) => ( 2 |
5 |
12 |

{text}

13 |

20 | {time} 21 |

22 |
23 |
24 | ); 25 | 26 | export default Message; 27 | -------------------------------------------------------------------------------- /client/src/components/shared/error/Error.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Error = () => { 4 | return ( 5 |
6 |

⚠️

7 |

8 | Internal Server Error, Try Again Later 9 |

10 |
11 | ); 12 | }; 13 | 14 | export default Error; 15 | -------------------------------------------------------------------------------- /client/src/components/shared/error/FetchError.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const FetchError = ({ error }) => { 4 | 5 | return ( 6 |
7 |
8 | 9 |

{error}

10 |
11 |
12 | ); 13 | }; 14 | 15 | export default FetchError; 16 | -------------------------------------------------------------------------------- /client/src/components/shared/error/NoDataMessage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NoDataMessage = ({ message }) => { 4 | return ( 5 |
6 | 7 |

{message}

8 |
9 | ); 10 | }; 11 | 12 | export default NoDataMessage; 13 | -------------------------------------------------------------------------------- /client/src/components/shared/error/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NotFound = () => { 4 | return ( 5 |
6 |
7 | 8 |

404 Page not Found

9 |
10 |
11 | ); 12 | }; 13 | 14 | export default NotFound; 15 | -------------------------------------------------------------------------------- /client/src/components/shared/loaders/ButtonLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ButtonLoader = () => { 4 | return ( 5 | 11 | 19 | 24 | 25 | ); 26 | }; 27 | 28 | export default ButtonLoader; 29 | -------------------------------------------------------------------------------- /client/src/components/shared/loaders/ChatbotLoader.jsx: -------------------------------------------------------------------------------- 1 | const ChatbotLoader = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default ChatbotLoader; 12 | -------------------------------------------------------------------------------- /client/src/components/shared/loaders/ComponentLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ComponentLoader = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | export default ComponentLoader; 16 | -------------------------------------------------------------------------------- /client/src/components/shared/loaders/Loader.jsx: -------------------------------------------------------------------------------- 1 | const Loader = () => { 2 | return ( 3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | export default Loader; 16 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/ApplicantionModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { jobStatus } from "../../../constants"; 4 | import { updateApplication } from "../../../services/recruitment.service"; 5 | 6 | const ApplicationModal = ({ onClose, application, jobId }) => { 7 | const dispatch = useDispatch(); 8 | 9 | const [formData, setFormData] = useState({ 10 | status: application.status, 11 | }); 12 | 13 | const handleChange = (e) => { 14 | const { name, value } = e.target; 15 | setFormData({ ...formData, [name]: value }); 16 | }; 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | 21 | const formattedData = { 22 | status: formData.status, 23 | }; 24 | 25 | dispatch( 26 | updateApplication({ 27 | jobId: jobId, 28 | applicantId: application._id, 29 | application: formattedData, 30 | }) 31 | ); 32 | 33 | onClose(); 34 | }; 35 | 36 | return ( 37 |
38 | 81 |
82 | ); 83 | }; 84 | 85 | export default ApplicationModal; 86 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/InviteModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { inviteForInterview } from "../../../services/recruitment.service"; 4 | 5 | const InviteModal = ({ onClose, application, jobId }) => { 6 | const dispatch = useDispatch(); 7 | 8 | const [formData, setFormData] = useState({ 9 | interviewDate: new Date().toISOString().split("T")[0], 10 | interviewTime: "10:00", 11 | }); 12 | 13 | const handleChange = (e) => { 14 | const { name, value } = e.target; 15 | setFormData({ ...formData, [name]: value }); 16 | }; 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | 21 | dispatch( 22 | inviteForInterview({ 23 | jobId: jobId, 24 | applicationId: application._id, 25 | interviewDetails: formData, 26 | }) 27 | ); 28 | 29 | onClose(); 30 | }; 31 | 32 | return ( 33 |
34 | 84 |
85 | ); 86 | }; 87 | 88 | export default InviteModal; 89 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/JobOpeningModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { updateJob } from "../../../services/recruitment.service"; 4 | 5 | const JobOpeningModal = ({ onClose, job }) => { 6 | const dispatch = useDispatch(); 7 | 8 | const [formData, setFormData] = useState({ 9 | status: job.status, 10 | deadline: job.deadline.split("T")[0], 11 | }); 12 | 13 | const handleChange = (e) => { 14 | const { name, value } = e.target; 15 | setFormData({ ...formData, [name]: value }); 16 | }; 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | 21 | const formattedData = { 22 | status: formData.status, 23 | deadline: formData.deadline, 24 | }; 25 | 26 | dispatch(updateJob({ id: job._id, job: formattedData })); 27 | 28 | onClose(); 29 | }; 30 | 31 | return ( 32 |
33 | 86 |
87 | ); 88 | }; 89 | 90 | export default JobOpeningModal; 91 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Modal = ({ onClose, isConfirm, action }) => { 4 | return ( 5 |
6 | 34 |
35 | ); 36 | }; 37 | 38 | export default Modal; 39 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/MonthSheetModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { months } from "../../../constants"; 3 | 4 | const MonthSheetModal = ({ 5 | onClose, 6 | departments, 7 | selectedDate, 8 | setSelectedDate, 9 | handleModalSubmit, 10 | selectedDepartment, 11 | setSelectedDepartment, 12 | }) => { 13 | return ( 14 |
15 | 76 |
77 | ); 78 | }; 79 | 80 | export default MonthSheetModal; 81 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/PayrollModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { updatePayroll } from "../../../services/payroll.service"; 4 | 5 | const PayrollModal = ({ onClose, payroll }) => { 6 | const dispatch = useDispatch(); 7 | 8 | const [formData, setFormData] = useState({ 9 | allowances: payroll?.allowances || 0, 10 | bonuses: payroll?.bonuses || 0, 11 | deductions: payroll?.deductions || 0, 12 | }); 13 | 14 | const handleChange = (e) => { 15 | const { name, value } = e.target; 16 | setFormData({ 17 | ...formData, 18 | [name]: Number(value) || 0, 19 | }); 20 | }; 21 | 22 | const handleSubmit = (e) => { 23 | e.preventDefault(); 24 | dispatch( 25 | updatePayroll({ 26 | id: payroll._id, 27 | formData, 28 | }) 29 | ); 30 | onClose(); 31 | }; 32 | 33 | return ( 34 |
35 |
39 | {/* Modal Header */} 40 |
41 |

Update Payroll

42 | 49 |
50 | 51 | {/* Allowances */} 52 |
53 | 56 | 64 |
65 | 66 | {/* Bonuses */} 67 |
68 | 71 | 79 |
80 | 81 | {/* Deductions */} 82 |
83 | 86 | 94 |
95 | 96 | {/* Submit Button */} 97 |
98 | 104 |
105 |
106 |
107 | ); 108 | }; 109 | 110 | export default PayrollModal; 111 | -------------------------------------------------------------------------------- /client/src/components/shared/modals/PerformanceModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { updatePerformance } from "../../../services/performance.service"; 4 | 5 | const PerformanceModal = ({ onClose, performance }) => { 6 | const dispatch = useDispatch(); 7 | 8 | const [formData, setFormData] = useState({ 9 | rating: performance.rating || "", 10 | feedback: performance.feedback || "", 11 | }); 12 | 13 | const handleChange = (e) => { 14 | const { name, value } = e.target; 15 | setFormData({ ...formData, [name]: value }); 16 | }; 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | 21 | const formattedData = { 22 | rating: parseInt(formData.rating), 23 | feedback: formData.feedback, 24 | kpis: performance.kpis, 25 | }; 26 | 27 | dispatch( 28 | updatePerformance({ id: performance._id, performance: formattedData }) 29 | ); 30 | 31 | onClose(); 32 | }; 33 | 34 | return ( 35 |
36 |