├── .gitignore ├── Frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package.json ├── public │ └── images │ │ ├── github │ │ ├── Admins.png │ │ ├── Category.png │ │ ├── Dashboard.png │ │ ├── Employee Dashboard.jpeg │ │ ├── Employee.png │ │ ├── Home.png │ │ ├── Login.png │ │ └── Office.png │ │ └── worksuite_img.png ├── src │ ├── App.jsx │ ├── assets │ │ ├── SidebarClose.jsx │ │ └── SidebarOpen.jsx │ ├── components │ │ ├── AddCategory.jsx │ │ ├── AddEmployee.jsx │ │ ├── Category.jsx │ │ ├── ClockIn.jsx │ │ ├── Dashboard.jsx │ │ ├── EditEmployee.jsx │ │ ├── Employee.jsx │ │ ├── EmployeeCalendar.css │ │ ├── EmployeeCalendar.jsx │ │ ├── EmployeeDetail.jsx │ │ ├── EmployeeDetails.css │ │ ├── EmployeeLogin.jsx │ │ ├── Home.css │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── ManageAdmin.jsx │ │ ├── Office.jsx │ │ ├── PMT │ │ │ ├── AdminProjects.jsx │ │ │ ├── AdminTasks.jsx │ │ │ ├── Clients.jsx │ │ │ ├── EmployeeTasks.css │ │ │ └── EmployeeTasks.jsx │ │ ├── PrivateRoute.jsx │ │ ├── Sidebar.jsx │ │ ├── Start.jsx │ │ └── style.css │ ├── index.css │ └── main.jsx └── vite.config.js ├── README.md ├── Server ├── .gitignore ├── Routes │ ├── AdminRoute.js │ ├── AttendanceRoute.js │ ├── ClientsRoute.js │ ├── EmployeeRoute.js │ ├── NotificationsRoute.js │ ├── ProjectRoute.js │ ├── TaskRoute.js │ └── TaskStatusRoute.js ├── index.js ├── middlewares │ ├── auth.js │ ├── verifyAdmin.js │ └── verifyUser.js ├── package.json ├── public │ └── images │ │ ├── image_1714386729091.jpg │ │ ├── image_1714503454639.jpg │ │ ├── image_1714808711692.png │ │ ├── image_1716625171724.jpg │ │ ├── image_1739772759938.jpg │ │ ├── image_1739775412179.jpg │ │ └── image_1739988964275.jpg └── utils │ └── db.js └── database_schema.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules 2 | node_modules/ 3 | 4 | # Ignore environment variables 5 | .env 6 | 7 | # Ignore build files 8 | dist/ 9 | out/ 10 | build/ 11 | 12 | # Ignore logs 13 | npm-debug.log 14 | yarn-debug.log 15 | yarn-error.log 16 | pnpm-debug.log 17 | 18 | # Ignore dependency lock files (optional) 19 | package-lock.json 20 | yarn.lock 21 | pnpm-lock.yaml 22 | 23 | # Ignore VS Code settings 24 | .vscode/ 25 | 26 | # Ignore macOS system files 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /Frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /Frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules 2 | node_modules/ 3 | 4 | # Ignore environment variables 5 | .env 6 | 7 | # Ignore build files 8 | dist/ 9 | out/ 10 | build/ 11 | 12 | # Ignore logs 13 | npm-debug.log 14 | yarn-debug.log 15 | yarn-error.log 16 | pnpm-debug.log 17 | 18 | # Ignore dependency lock files (optional) 19 | package-lock.json 20 | yarn.lock 21 | pnpm-lock.yaml 22 | 23 | # Ignore VS Code settings 24 | .vscode/ 25 | 26 | # Ignore macOS system files 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /Frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + 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 | -------------------------------------------------------------------------------- /Frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WORKSUITE 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worksuit", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.11.4", 14 | "@emotion/styled": "^11.11.5", 15 | "@fortawesome/free-solid-svg-icons": "^6.5.2", 16 | "@fortawesome/react-fontawesome": "^0.2.0", 17 | "@mui/material": "^5.15.16", 18 | "axios": "^1.6.8", 19 | "bootstrap": "^5.3.3", 20 | "bootstrap-icons": "^1.11.3", 21 | "chart.js": "^4.4.7", 22 | "multer": "^1.4.5-lts.1", 23 | "react": "^18.2.0", 24 | "react-bootstrap": "^2.10.2", 25 | "react-calendar": "^5.0.0", 26 | "react-chartjs-2": "^5.3.0", 27 | "react-dom": "^18.2.0", 28 | "react-fontawesome-svg-icon": "^1.1.2", 29 | "react-icons": "^5.5.0", 30 | "react-pro-sidebar": "^1.1.0", 31 | "react-router-dom": "^6.23.0", 32 | "react-scroll": "^1.9.0", 33 | "react-spring": "^9.7.3", 34 | "react-toastify": "^10.0.5", 35 | "react-tooltip": "^5.26.4", 36 | "socket.io-client": "^4.8.1" 37 | }, 38 | "devDependencies": { 39 | "@types/react": "^18.2.66", 40 | "@types/react-dom": "^18.2.22", 41 | "@vitejs/plugin-react": "^1.3.2", 42 | "eslint": "^8.57.0", 43 | "eslint-plugin-react": "^7.34.1", 44 | "eslint-plugin-react-hooks": "^4.6.0", 45 | "eslint-plugin-react-refresh": "^0.4.6", 46 | "vite": "^6.1.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Frontend/public/images/github/Admins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Admins.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Category.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Dashboard.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Employee Dashboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Employee Dashboard.jpeg -------------------------------------------------------------------------------- /Frontend/public/images/github/Employee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Employee.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Home.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Login.png -------------------------------------------------------------------------------- /Frontend/public/images/github/Office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/github/Office.png -------------------------------------------------------------------------------- /Frontend/public/images/worksuite_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Frontend/public/images/worksuite_img.png -------------------------------------------------------------------------------- /Frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.min.css"; 2 | import "react-toastify/dist/ReactToastify.css"; 3 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 4 | import Login from "./components/Login"; 5 | import Dashboard from "./components/Dashboard"; 6 | import Home from "./components/Home"; 7 | import Employee from "./components/Employee"; 8 | import Category from "./components/Category"; 9 | import ManageAdmin from "./components/ManageAdmin"; 10 | import AddCategory from "./components/AddCategory"; 11 | import AddEmployee from "./components/AddEmployee"; 12 | import EditEmployee from "./components/EditEmployee"; 13 | import Start from "./components/Start"; 14 | import EmployeeLogin from "./components/EmployeeLogin"; 15 | import EmployeeDetail from "./components/EmployeeDetail"; 16 | import PrivateRoute from "./components/PrivateRoute"; 17 | import Office from "./components/Office"; 18 | import AdminProjects from "./components/PMT/AdminProjects"; 19 | import AdminTasks from "./components/PMT/AdminTasks"; 20 | import Clients from "./components/PMT/Clients"; 21 | 22 | function App() { 23 | return ( 24 | 25 | 26 | }> 27 | }> 28 | }> 29 | 33 | 34 | 35 | } 36 | /> 37 | 41 | 42 | 43 | } 44 | > 45 | }>{" "} 46 | }>{" "} 47 | }>{" "} 48 | }>{" "} 49 | }>{" "} 50 | }>{" "} 51 | }>{" "} 52 | }>{" "} 53 | }>{" "} 54 | }>{" "} 55 | }>{" "} 56 | 57 | 58 | 59 | ); 60 | } 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /Frontend/src/assets/SidebarClose.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function SidebarClose() { 4 | return ( 5 |
6 | 14 | 19 | 28 | 32 | 33 | 34 |
35 | ); 36 | } 37 | 38 | export default SidebarClose; 39 | -------------------------------------------------------------------------------- /Frontend/src/assets/SidebarOpen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function SidebarOpen() { 4 | return ( 5 |
6 | 14 | 19 | 29 | 32 | 33 | 34 |
35 | ); 36 | } 37 | 38 | export default SidebarOpen; 39 | -------------------------------------------------------------------------------- /Frontend/src/components/AddCategory.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import axios from 'axios'; 3 | import { useNavigate } from 'react-router-dom'; 4 | 5 | function AddCategory() { 6 | const [category, setCategory] = useState(""); 7 | const [error, setError] = useState(""); 8 | const [successMessage, setSuccessMessage] = useState(""); 9 | const navigate = useNavigate(); 10 | const apiUrl = import.meta.env.VITE_API_URL; 11 | 12 | const handleInputChange = (e) => { 13 | setCategory(e.target.value); 14 | }; 15 | 16 | const handleSubmit = (e) => { 17 | e.preventDefault(); 18 | axios.post(`${apiUrl}/auth/add_category`, { name: category }) 19 | .then(result => { 20 | console.log(result.data); 21 | if (result.data.success) { 22 | setSuccessMessage(result.data.message); 23 | setError(""); 24 | setCategory(""); 25 | } else { 26 | setError(result.data.message); 27 | setSuccessMessage(""); 28 | } 29 | }) 30 | .catch(err => { 31 | console.log(err); 32 | setError("An error occurred while adding the category."); 33 | setSuccessMessage(""); 34 | }) 35 | }; 36 | 37 | const handleNavigateBack = () => { 38 | navigate('/dashboard/category'); 39 | }; 40 | 41 | return ( 42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 |

Add Category

50 |
51 |
52 |
53 | 54 | 63 |
64 | 65 | {error &&
{error}
} 66 | {successMessage &&
{successMessage}
} 67 |
68 |
69 |
70 |
71 | ); 72 | } 73 | 74 | export default AddCategory; 75 | -------------------------------------------------------------------------------- /Frontend/src/components/AddEmployee.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import './style.css'; 3 | import axios from 'axios'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | 7 | function AddEmployee() { 8 | const [employee, setEmployee] = useState({ 9 | name: "", 10 | email: "", 11 | password: "", 12 | salary: "", 13 | address: "", 14 | category_id: "", 15 | image: null, 16 | }); 17 | 18 | 19 | const [error, setError] = useState(""); 20 | const [successMessage, setSuccessMessage] = useState(""); 21 | const [formKey, setFormKey] = useState(Date.now()); 22 | const navigate = useNavigate(); 23 | const apiUrl = import.meta.env.VITE_API_URL; 24 | 25 | const [categories, setCategories] = useState([]); 26 | 27 | useEffect(() => { 28 | axios.get(`${apiUrl}/auth/category`) 29 | .then(response => { 30 | setCategories(response.data.categories); 31 | }) 32 | .catch(err => console.log(err)); 33 | }, []); 34 | 35 | const handleNavigateBack = () => { 36 | navigate('/dashboard/employee'); 37 | }; 38 | 39 | 40 | const handleSubmit = (e) => { 41 | e.preventDefault(); 42 | const formData = new FormData(); 43 | formData.append('name', employee.name); 44 | formData.append('email', employee.email.toLowerCase()); 45 | formData.append('password', employee.password); 46 | formData.append('address', employee.address); 47 | formData.append('salary', employee.salary); 48 | formData.append('image', employee.image); 49 | formData.append('category_id', employee.category_id); 50 | 51 | axios.post('http://localhost:3000/auth/add_employee', formData) 52 | .then(result => { 53 | console.log(result.data); 54 | if (result.data.success) { 55 | setSuccessMessage(result.data.message); 56 | setError(""); 57 | setEmployee({ 58 | name: "", 59 | email: "", 60 | password: "", 61 | salary: "", 62 | address: "", 63 | category_id: "", 64 | image: "", 65 | }); 66 | setFormKey(Date.now()); 67 | } else { 68 | setError(result.data.message); 69 | setSuccessMessage(""); 70 | } 71 | }) 72 | .catch(err => { 73 | console.log(err); 74 | setError("An error occurred while adding the Employee."); 75 | setSuccessMessage(""); 76 | }); 77 | }; 78 | return ( 79 |
80 |
81 |
82 |
83 | 84 |
85 |
86 |

Add Employee

87 |
88 |
89 |
90 |
91 | 94 | setEmployee({ ...employee, name: e.target.value })} 100 | /> 101 |
102 |
103 | 106 | setEmployee({ ...employee, email: e.target.value })} 113 | /> 114 |
115 |
116 | 119 | setEmployee({ ...employee, password: e.target.value })} 125 | /> 126 |
127 |
128 | 131 | setEmployee({ ...employee, salary: e.target.value })} 138 | /> 139 |
140 |
141 | 144 | setEmployee({ ...employee, address: e.target.value })} 151 | /> 152 |
153 |
154 | 157 | 168 |
169 |
170 | 173 | setEmployee({ ...employee, image: e.target.files[0] })} 179 | /> 180 |
181 |
182 | 183 |
184 | {error &&
{error}
} 185 | {successMessage &&
{successMessage}
} 186 |
187 |
188 |
189 |
190 |
191 | ); 192 | } 193 | 194 | 195 | export default AddEmployee 196 | -------------------------------------------------------------------------------- /Frontend/src/components/Category.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import axios from 'axios' 4 | 5 | function Category() { 6 | const [category, setCategory] = useState([]) 7 | const apiUrl = import.meta.env.VITE_API_URL; 8 | useEffect(() => { 9 | axios.get(`${apiUrl}/auth/category`) 10 | .then(result => { 11 | setCategory(result.data.categories) 12 | }).catch(err => console.log(err)) 13 | }, []) 14 | 15 | return ( 16 |
17 |
18 |

Cetegory List

19 |
20 | Add Cetegory 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {category.map(category => ( 30 | 31 | 32 | 33 | ))} 34 | 35 |
Name
{category.name}
36 |
37 | 38 |
39 | ) 40 | } 41 | export default Category 42 | -------------------------------------------------------------------------------- /Frontend/src/components/ClockIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import { useParams } from 'react-router-dom'; 4 | import { toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | import "bootstrap-icons/font/bootstrap-icons.css"; 7 | 8 | const ClockIn = () => { 9 | const { id } = useParams(); 10 | const [location, setLocation] = useState('office'); 11 | const [workFromType, setWorkFromType] = useState('office'); 12 | const [loading, setLoading] = useState(false); 13 | const [clockedIn, setClockedIn] = useState(false); 14 | const [showModal, setShowModal] = useState(false); 15 | const [officeLocations, setOfficeLocations] = useState([]); 16 | const apiUrl = import.meta.env.VITE_API_URL; 17 | 18 | useEffect(() => { 19 | // Fetch office location data when component mounts 20 | const fetchOfficeLocations = async () => { 21 | try { 22 | const response = await axios.get(`${apiUrl}/employee/office_location`); 23 | setOfficeLocations(response.data.officeLocations); 24 | 25 | // Check local storage for clock-in status 26 | const clockInStatus = localStorage.getItem('clockInStatus'); 27 | if (clockInStatus) { 28 | setClockedIn(JSON.parse(clockInStatus)); 29 | } 30 | } catch (error) { 31 | console.error('Error fetching office locations:', error); 32 | } 33 | }; 34 | 35 | fetchOfficeLocations(); 36 | }, []); 37 | 38 | const updateClockInStatus = (status) => { 39 | localStorage.setItem('clockInStatus', JSON.stringify(status)); 40 | setClockedIn(status); 41 | }; 42 | 43 | const handleClockIn = async (e) => { 44 | e.preventDefault(); 45 | setLoading(true); 46 | 47 | if (location === 'home') { 48 | try { 49 | const response = await axios.post(`${apiUrl}/employee/employee_clockin/${id}`, { 50 | work_from_type: 'Home' 51 | }); 52 | if (response.data.status === 'success') { 53 | console.log('Clock-in successful'); 54 | updateClockInStatus(true); 55 | setShowModal(false); 56 | toast.success('Clock-in successful'); 57 | } 58 | } catch (error) { 59 | console.error('Error while clocking in:', error); 60 | } finally { 61 | setLoading(false); 62 | } 63 | } else { 64 | // Get user's current location 65 | navigator.geolocation.getCurrentPosition( 66 | async (position) => { 67 | const userLatitude = position.coords.latitude; 68 | const userLongitude = position.coords.longitude; 69 | 70 | // Log employee's current location 71 | console.log('Employee Current Location:', userLatitude, userLongitude); 72 | 73 | // Compare user's location with office locations 74 | const isAtOfficeLocation = checkOfficeLocation(userLatitude, userLongitude); 75 | 76 | // Log office locations 77 | console.log('Office Locations:', officeLocations); 78 | 79 | if (!isAtOfficeLocation) { 80 | setLoading(false); 81 | toast.error('You are not at the office location.'); 82 | console.error('Employee is not at the office location.'); 83 | return; 84 | } 85 | 86 | try { 87 | const response = await axios.post(`${apiUrl}/employee/employee_clockin/${id}`, { 88 | work_from_type: 'Office' 89 | }); 90 | if (response.data.status === 'success') { 91 | console.log('Clock-in successful'); 92 | updateClockInStatus(true); 93 | setShowModal(false); 94 | toast.success('Clock-in successful'); 95 | } 96 | } catch (error) { 97 | console.error('Error while clocking in:', error); 98 | } finally { 99 | setLoading(false); 100 | } 101 | }, 102 | (error) => { 103 | console.error('Error getting user location:', error); 104 | setLoading(false); 105 | toast.error('Error getting your location. Please try again.'); 106 | } 107 | ); 108 | } 109 | }; 110 | 111 | 112 | const checkOfficeLocation = (userLatitude, userLongitude) => { 113 | // Iterate through office locations and check if user's location matches any office location 114 | for (const officeLocation of officeLocations) { 115 | const officeLatitude = officeLocation.latitude; 116 | const officeLongitude = officeLocation.longitude; 117 | const YOUR_TOLERANCE = 5; // Adjust tolerance as needed 118 | const distance = calculateDistance(userLatitude, userLongitude, officeLatitude, officeLongitude); 119 | if (distance <= YOUR_TOLERANCE) { 120 | return true; 121 | } 122 | } 123 | return false; 124 | }; 125 | 126 | const calculateDistance = (lat1, lon1, lat2, lon2) => { 127 | // Calculation of distance between two points using haversine formula 128 | const R = 6371; 129 | const dLat = (lat2 - lat1) * (Math.PI / 180); 130 | const dLon = (lon2 - lon1) * (Math.PI / 180); 131 | const a = 132 | Math.sin(dLat / 2) * Math.sin(dLat / 2) + 133 | Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); 134 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 135 | const distance = R * c; 136 | return distance; 137 | }; 138 | 139 | const handleClockOut = async () => { 140 | setLoading(true); 141 | try { 142 | const response = await axios.post(`${apiUrl}/employee/employee_clockout/${id}`); 143 | if (response.data.success) { 144 | console.log('Clock-out successful'); 145 | updateClockInStatus(false); 146 | toast.success('Clock-out successful'); 147 | } 148 | } catch (error) { 149 | console.error('Error while clocking out:', error); 150 | } finally { 151 | setLoading(false); 152 | } 153 | }; 154 | 155 | return ( 156 |
157 | {!clockedIn && ( 158 | 162 | )} 163 | {showModal && ( 164 |
165 |
166 |
167 |
168 |
Clock In
169 | 170 |
171 |
172 |
173 |
174 | 175 | 179 |
180 | {location === 'office' && ( 181 |
182 | 183 | 186 |
187 | )} 188 |
189 |
190 | 191 | 192 |
193 |
194 |
195 |
196 |
197 | )} 198 | {clockedIn && ( 199 |
200 | 203 |
204 | )} 205 |
206 | ); 207 | }; 208 | 209 | export default ClockIn; 210 | -------------------------------------------------------------------------------- /Frontend/src/components/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Outlet, useNavigate } from "react-router-dom"; 3 | import axios from "axios"; 4 | import { useSpring, animated } from "react-spring"; 5 | import Sidebar from "./Sidebar"; 6 | 7 | const Dashboard = () => { 8 | const navigate = useNavigate(); 9 | axios.defaults.withCredentials = true; 10 | const apiUrl = import.meta.env.VITE_API_URL; 11 | 12 | const [sidebarOpen, setSidebarOpen] = useState(true); 13 | 14 | const handleLogout = () => { 15 | axios.get(`${apiUrl}/auth/logout`).then((result) => { 16 | if (result.data.Status) { 17 | localStorage.removeItem("valid"); 18 | navigate("/"); 19 | } 20 | }); 21 | }; 22 | 23 | const toggleSidebar = () => { 24 | setSidebarOpen(!sidebarOpen); 25 | }; 26 | 27 | const AnimatedComponent = () => { 28 | const props = useSpring({ 29 | from: { opacity: 0 }, 30 | to: { opacity: 1 }, 31 | config: { duration: 600 }, 32 | }); 33 | 34 | return ( 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | return ( 42 |
43 |
48 | 53 |
54 |
55 |

56 | 60 | WORKSUITE 61 | {" "} 62 | - 63 | 71 | {" "} 72 | Project Management System 73 | 74 |

75 |
76 |
80 | 81 |
82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | export default Dashboard; 89 | -------------------------------------------------------------------------------- /Frontend/src/components/EditEmployee.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { useEffect, useState } from 'react'; 3 | import { useNavigate, useParams } from 'react-router-dom'; 4 | import './style.css'; 5 | 6 | const EditEmployee = () => { 7 | const { id } = useParams(); 8 | const [employee, setEmployee] = useState({ 9 | name: "", 10 | email: "", 11 | salary: "", 12 | address: "", 13 | category_id: "", 14 | }); 15 | const [category, setCategory] = useState([]); 16 | const navigate = useNavigate(); 17 | const apiUrl = import.meta.env.VITE_API_URL; 18 | 19 | useEffect(() => { 20 | // Fetch categories 21 | axios.get(`${apiUrl}/auth/category`) 22 | .then(result => { 23 | if (result.data.success) { 24 | setCategory(result.data.categories); 25 | } else { 26 | alert(result.data.message); 27 | } 28 | }) 29 | .catch(err => console.log(err)); 30 | 31 | // Fetch employee details 32 | axios.get(`${apiUrl}/auth/employee/${id}`) 33 | .then(result => { 34 | const employeeData = result.data.Result[0]; 35 | setEmployee({ 36 | name: employeeData.name, 37 | email: employeeData.email, 38 | address: employeeData.address, 39 | salary: employeeData.salary, 40 | category_id: employeeData.category_id, 41 | }); 42 | }) 43 | .catch(err => console.log(err)); 44 | }, [id]); 45 | 46 | const handleSubmit = (e) => { 47 | e.preventDefault(); 48 | axios.put(`${apiUrl}/auth/edit_employee/${id}`, employee) 49 | .then(result => { 50 | if (result.data.success) { 51 | navigate('/dashboard/employee'); 52 | } else { 53 | alert(result.data.message); 54 | } 55 | }) 56 | .catch(err => console.log(err)); 57 | }; 58 | const handleNavigateBack = () => { 59 | navigate('/dashboard/employee'); 60 | }; 61 | 62 | return ( 63 |
64 |
65 |
66 |
67 | 68 |
69 |

Edit Employee

70 |
71 |
72 | 73 | setEmployee({ ...employee, name: e.target.value })} 80 | /> 81 |
82 |
83 | 84 | setEmployee({ ...employee, email: e.target.value })} 91 | /> 92 |
93 |
94 | 95 | setEmployee({ ...employee, salary: e.target.value })} 102 | /> 103 |
104 |
105 | 106 | setEmployee({ ...employee, address: e.target.value })} 113 | /> 114 |
115 |
116 | 117 | 128 |
129 |
130 | 131 |
132 |
133 |
134 |
135 |
136 | ); 137 | }; 138 | 139 | export default EditEmployee; 140 | -------------------------------------------------------------------------------- /Frontend/src/components/Employee.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | 5 | const Employee = () => { 6 | const [employees, setEmployees] = useState([]); 7 | const navigate = useNavigate(); 8 | const apiUrl = import.meta.env.VITE_API_URL; 9 | 10 | useEffect(() => { 11 | axios 12 | .get(`${apiUrl}/auth/employee`) 13 | .then((response) => { 14 | if (response.data.success) { 15 | setEmployees(response.data.Result); 16 | } else { 17 | alert(response.data.message); 18 | } 19 | }) 20 | .catch((error) => console.log(error)); 21 | }, []); 22 | 23 | const handleDelete = (id) => { 24 | axios 25 | .delete(`${apiUrl}/auth/delete_employee/${id}`) 26 | .then((response) => { 27 | if (response.data.success) { 28 | // Refresh the employee list 29 | axios 30 | .get(`${apiUrl}/auth/employee`) 31 | .then((response) => { 32 | if (response.data.success) { 33 | setEmployees(response.data.Result); 34 | } else { 35 | alert(response.data.message); 36 | } 37 | }) 38 | .catch((error) => console.log(error)); 39 | } else { 40 | alert(response.data.message); 41 | } 42 | }) 43 | .catch((error) => console.log(error)); 44 | }; 45 | 46 | return ( 47 |
48 |
49 |

Employee List

50 |
51 | 52 | Add Employee 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {employees.map((employee) => ( 68 | 69 | 70 | 89 | 90 | 91 | 92 | 106 | 107 | ))} 108 | 109 |
NameImageEmailAddressSalaryAction
{employee.name} 71 |
72 | {employee.image ? ( 73 | {employee.name} 79 | ) : ( 80 |
84 | Placeholder 85 |
86 | )} 87 |
88 |
{employee.email}{employee.address}{employee.salary} 93 | 97 | Edit 98 | 99 | 105 |
110 |
111 |
112 | ); 113 | }; 114 | 115 | export default Employee; 116 | -------------------------------------------------------------------------------- /Frontend/src/components/EmployeeCalendar.css: -------------------------------------------------------------------------------- 1 | 2 | .react-calendar { 3 | width: 100% !important; 4 | } 5 | 6 | .react-calendar { 7 | max-width: 100%; 8 | background-color: #fff; 9 | color: #222; 10 | border-radius: 8px; 11 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.167); 12 | font-family: Arial, Helvetica, sans-serif; 13 | line-height: 1.125em; 14 | padding: 20px; 15 | } 16 | .react-calendar__navigation button { 17 | color: #6f48eb; 18 | min-width: 44px; 19 | background: none; 20 | font-size: 16px; 21 | margin-top: 8px; 22 | } 23 | .react-calendar__navigation button:enabled:hover, 24 | .react-calendar__navigation button:enabled:focus { 25 | background-color: #f8f8fa; 26 | } 27 | .react-calendar__navigation button[disabled] { 28 | background-color: #f0f0f0; 29 | } 30 | abbr[title] { 31 | text-decoration: none; 32 | } 33 | /* .react-calendar__month-view__days__day--weekend { 34 | color: #d10000; 35 | } */ 36 | .react-calendar__tile:enabled:hover, 37 | .react-calendar__tile:enabled:focus { 38 | background: #f8f8fa; 39 | color: #6f48eb; 40 | border-radius: 6px; 41 | } 42 | .react-calendar__tile--now { 43 | background: #6f48eb33; 44 | border-radius: 6px; 45 | font-weight: bold; 46 | color: #6f48eb; 47 | } 48 | .react-calendar__tile--now:enabled:hover, 49 | .react-calendar__tile--now:enabled:focus { 50 | background: #6f48eb33; 51 | border-radius: 6px; 52 | font-weight: bold; 53 | color: #6f48eb; 54 | } 55 | .react-calendar__tile--hasActive:enabled:hover, 56 | .react-calendar__tile--hasActive:enabled:focus { 57 | background: #f8f8fa; 58 | } 59 | .react-calendar__tile--active { 60 | background: #6f48eb !important; 61 | border-radius: 6px; 62 | font-weight: bold; 63 | color: white; 64 | } 65 | .react-calendar__tile--active:enabled:hover, 66 | .react-calendar__tile--active:enabled:focus { 67 | background: #6f48eb !important; 68 | color: white; 69 | } 70 | .react-calendar--selectRange .react-calendar__tile--hover { 71 | background-color: #f8f8fa; 72 | } 73 | .react-calendar__tile--range { 74 | background: #f8f8fa; 75 | color: #6f48eb; 76 | border-radius: 0; 77 | } 78 | .react-calendar__tile--rangeStart { 79 | border-top-right-radius: 0; 80 | border-bottom-right-radius: 0; 81 | border-top-left-radius: 6px; 82 | border-bottom-left-radius: 6px; 83 | background: #6f48eb; 84 | color: white; 85 | } 86 | .react-calendar__tile--rangeEnd { 87 | border-top-left-radius: 6px; 88 | border-bottom-left-radius: 6px; 89 | border-top-right-radius: 6px; 90 | border-bottom-right-radius: 6px; 91 | background: #6f48eb; 92 | color: white; 93 | } 94 | 95 | 96 | .react-calendar__month-view__weekdays__weekday{ 97 | border: 0px solid rgb(245, 245, 245); 98 | background-color: #f8f8fa; 99 | border-radius: 0px; 100 | margin-bottom: 5px; 101 | max-width: 180px; 102 | min-width: 50px 103 | } 104 | 105 | .react-calendar__tile{ 106 | border: 1px solid rgb(255, 255, 255) !important; 107 | background-color: #b9c1c630; 108 | margin: 5px; 109 | } 110 | 111 | .calendarPara{ 112 | margin-bottom: .2rem !important; 113 | } -------------------------------------------------------------------------------- /Frontend/src/components/EmployeeCalendar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import Calendar from 'react-calendar'; // Assuming you're using react-calendar library 4 | import 'react-calendar/dist/Calendar.css'; 5 | import './EmployeeCalendar.css'; 6 | 7 | function EmployeeCalendar({ employeeId }) { 8 | const [calendarData, setCalendarData] = useState([]); 9 | const [selectedDate, setSelectedDate] = useState(new Date()); // Initialize selectedDate with current date 10 | const apiUrl = import.meta.env.VITE_API_URL; 11 | 12 | useEffect(() => { 13 | fetchData(); 14 | }, [employeeId]); 15 | 16 | 17 | const fetchData = () => { 18 | const formattedDate = selectedDate.toISOString().slice(0, 10); // Format date as 'YYYY-MM-DD' 19 | axios.get(`${apiUrl}/employee/calendar/${employeeId}?date=${formattedDate}`) 20 | .then(result => { 21 | if (result.data.success && Array.isArray(result.data.calendarData)) { 22 | setCalendarData(result.data.calendarData); 23 | } else { 24 | console.error("Invalid response format:", result.data); 25 | } 26 | }) 27 | .catch(err => console.error(err)); 28 | }; 29 | 30 | // Function to format timestamp strings 31 | const formatDate = (timestamp) => { 32 | const date = new Date(timestamp); 33 | return date.toLocaleTimeString('en-US'); 34 | }; 35 | 36 | return ( 37 |
38 |

Attendance Calendar

39 | {/* Render the calendar component */} 40 |
41 | { 45 | const formattedDate = date.toDateString(); 46 | const entries = calendarData.filter(entry => { 47 | const entryDate = new Date(entry.clockIn).toDateString(); 48 | return entryDate === formattedDate; 49 | }); 50 | return ( 51 |
52 | {entries.map((entry, index) => ( 53 |
54 |

{entry.dayName}

55 |

Clock In:
{formatDate(entry.clockIn)}

56 |

Clock Out:
{formatDate(entry.clockOut)}

57 | {entry.location &&

Location: {entry.location}

} 58 |

Work From Type: {entry.workFromType}

59 |
60 | ))} 61 |
62 | ); 63 | }} 64 | style={{ width: '100%' }} 65 | /> 66 |
67 |
68 | ); 69 | } 70 | 71 | export default EmployeeCalendar; 72 | -------------------------------------------------------------------------------- /Frontend/src/components/EmployeeDetail.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import axios from "axios"; 4 | import "./EmployeeDetails.css"; 5 | import { useNavigate, Link } from "react-router-dom"; 6 | import "bootstrap-icons/font/bootstrap-icons.css"; 7 | import ClockIn from "./ClockIn"; 8 | import EmployeeCalendar from "./EmployeeCalendar"; 9 | import EmployeeTasks from "./PMT/EmployeeTasks"; 10 | 11 | function EmployeeDetail() { 12 | const [employee, setEmployee] = useState(null); 13 | const [category, setCategory] = useState(null); 14 | const { id } = useParams(); 15 | const navigate = useNavigate(); 16 | const apiUrl = import.meta.env.VITE_API_URL; 17 | 18 | useEffect(() => { 19 | axios 20 | .get(`${apiUrl}/employee/detail/${id}`) 21 | .then((result) => { 22 | if (result.data.success && Array.isArray(result.data.Result)) { 23 | setEmployee(result.data.Result[0]); 24 | } else { 25 | console.error("Invalid response format:", result.data); 26 | } 27 | }) 28 | .catch((err) => console.error(err)); 29 | }, [id]); 30 | 31 | useEffect(() => { 32 | if (employee) { 33 | axios 34 | .get(`${apiUrl}/employee/category/${employee.category_id}`) 35 | .then((result) => { 36 | if (result.data.success && result.data.category) { 37 | setCategory(result.data.category); 38 | } else { 39 | console.error("Invalid response format:", result.data); 40 | } 41 | }) 42 | .catch((err) => console.error(err)); 43 | } 44 | }, [employee]); 45 | 46 | const handleLogout = () => { 47 | axios.defaults.withCredentials = true; 48 | axios 49 | .get(`${apiUrl}/employee/logout`) 50 | .then((result) => { 51 | if (result.data.Status) { 52 | localStorage.removeItem("valid"); 53 | navigate("/"); 54 | } 55 | }) 56 | .catch((err) => console.error(err)); 57 | }; 58 | 59 | if (!employee) { 60 | return
Loading...
; 61 | } 62 | 63 | const currentTime = new Date().toLocaleTimeString([], { 64 | hour: "2-digit", 65 | minute: "2-digit", 66 | }); 67 | const currentDay = new Date().toLocaleDateString("en-US", { 68 | weekday: "long", 69 | }); 70 | 71 | return ( 72 |
73 |
77 |
78 |
79 |
80 |
Welcome {employee.name}
81 |
82 |
83 |
84 | 85 | {currentTime} 86 | 87 |
88 | {currentDay} 89 |
90 |
91 | 96 | Logout 97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | Employee 116 |
117 |
118 |
119 |

{employee.name}

120 |
121 |

122 | Employee ID:{" "} 123 | {employee.id} 124 |

125 |

126 | Email:{" "} 127 | {employee.email} 128 |

129 | {category && ( 130 |

131 | Category:{" "} 132 | {category.name} 133 |

134 | )} 135 |
136 |
137 | 138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | 148 |
149 |
150 | 151 |
152 |
153 | ); 154 | } 155 | 156 | export default EmployeeDetail; 157 | -------------------------------------------------------------------------------- /Frontend/src/components/EmployeeDetails.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --green: #0a1823; 3 | --lightgreen1: #a4bdb7; 4 | --brown: #b80d15; 5 | --gray: rgb(162, 162, 162); 6 | --lightgreen2: rgba(164, 189, 183, 0.5); 7 | --box: #ededed; 8 | } 9 | 10 | .empPageColor{ 11 | background-image: linear-gradient(90deg, #ebffff 0%, #eff3ff 30%, #feeeff 100%); 12 | } 13 | 14 | .card { 15 | width: auto; 16 | margin: 0 auto; 17 | border-radius: 12px !important; 18 | padding: 20px; 19 | text-align: center; 20 | box-shadow: rgba(99, 104, 109, 0.112) 0px 5px 15px !important; 21 | border: 1px solid #f8f7ff; 22 | max-width: 700px; 23 | } 24 | 25 | .card-img { 26 | width: 230px; 27 | height: 230px; 28 | border-radius: 8%; 29 | display: block; 30 | object-fit: cover; 31 | margin-right: 20px; 32 | margin-left: 20px; 33 | } 34 | 35 | .card-title { 36 | font-size: 1.5rem; 37 | font-weight: bold; 38 | margin-bottom: 20px; 39 | color: var(--green); 40 | text-align: left; 41 | } 42 | 43 | .details { 44 | margin-bottom: 20px; 45 | font-size: 1.1rem; 46 | } 47 | 48 | .buttons { 49 | display: flex; 50 | justify-content: left; 51 | gap: 10px; 52 | } 53 | 54 | .buttons .message { 55 | background: var(--box); 56 | border: 2px solid var(--green); 57 | color: var(--green); 58 | margin-right: 10px; 59 | } 60 | 61 | .buttons .connect { 62 | background-color: var(--brown); 63 | color: #fff; 64 | border: none; 65 | } 66 | 67 | .buttons:hover { 68 | letter-spacing: 1px; 69 | transition: 0.5s; 70 | } 71 | 72 | .details{ 73 | color: #57595d; 74 | text-align: left !important; 75 | justify-content: left !important; 76 | } 77 | 78 | .detailsTitle{ 79 | font-weight: 600; 80 | color: #444; 81 | text-align: left !important; 82 | justify-content: left !important; 83 | } 84 | 85 | lable{ 86 | display: flex !important; 87 | justify-content: left !important; 88 | } 89 | 90 | .empCard{ 91 | display: flex; 92 | } -------------------------------------------------------------------------------- /Frontend/src/components/EmployeeLogin.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | import './style.css'; 4 | import axios from 'axios'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { toast } from 'react-toastify'; 7 | import 'react-toastify/dist/ReactToastify.css'; 8 | 9 | function EmployeeLogin() { 10 | const apiUrl = import.meta.env.VITE_API_URL; 11 | const [values, setValues] = useState({ 12 | email: '', 13 | password: '' 14 | }); 15 | const navigate = useNavigate(); 16 | axios.defaults.withCredentials = true; 17 | 18 | const handleInputChange = (e) => { 19 | const { name, value } = e.target; 20 | setValues({ ...values, [name]: value }); 21 | }; 22 | 23 | const handleSubmit = (e) => { 24 | e.preventDefault(); 25 | 26 | if (!values.email || !values.password) { 27 | toast.error("Email and password are required."); 28 | return; 29 | } 30 | 31 | axios.post(`${apiUrl}/employee/employeelogin`, values) 32 | .then(result => { 33 | if (result.data.loginStatus) { 34 | localStorage.setItem('valid', true); 35 | navigate('/employeedetail/'+result.data.id); 36 | toast.success("Login successful"); 37 | } else { 38 | const errorMessage = result.data.error || "Invalid email or password."; 39 | toast.error(errorMessage); 40 | } 41 | }) 42 | .catch(err => { 43 | if (err.response && err.response.status === 404) { 44 | toast.error("Invalid email or password."); 45 | } else { 46 | console.log(err); 47 | toast.error("An error occurred."); 48 | } 49 | }); 50 | }; 51 | 52 | return ( 53 |
54 |
55 |
56 |
57 |

Login Page

58 |
59 |
60 | 61 | 71 |
72 |
73 | 74 | 82 |
83 | 84 |
85 |
86 |
87 |
88 |
89 | ); 90 | } 91 | 92 | export default EmployeeLogin; 93 | -------------------------------------------------------------------------------- /Frontend/src/components/Home.css: -------------------------------------------------------------------------------- 1 | /* Container styling */ 2 | .home-container { 3 | padding: 2rem; 4 | min-height: 100vh; 5 | background-color: #f7f8fa; /* Subtle grey background */ 6 | font-family: "Roboto", sans-serif; /* Or any premium font */ 7 | } 8 | 9 | /* Cards */ 10 | .home-card { 11 | border: none; /* Remove default border */ 12 | border-radius: 10px; 13 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 14 | } 15 | 16 | /* Card Title */ 17 | .home-card-title { 18 | font-size: 1.25rem; /* Slightly larger than default */ 19 | font-weight: 600; 20 | color: #333; 21 | } 22 | 23 | /* Tables */ 24 | .home-table thead th { 25 | font-size: 0.65rem !important; 26 | font-weight: 600; 27 | background-color: #e9ecef; /* Light grey background for table header */ 28 | } 29 | 30 | /* Notification List */ 31 | .notification-list { 32 | list-style-type: none; 33 | padding: 0; 34 | margin: 0; 35 | } 36 | 37 | .notification-item { 38 | background-color: #f7f7f7; 39 | margin-bottom: 0.5rem; 40 | padding: 0.75rem; 41 | border-left: 4px solid #007bff; /* Accent color on left */ 42 | border-radius: 4px; 43 | } 44 | 45 | .notification-item:hover { 46 | background-color: #f0f4ff; 47 | } 48 | 49 | /* Chart Wrapper */ 50 | .chart-wrapper { 51 | height: 180px; 52 | padding: 1rem; 53 | } 54 | 55 | /* Responsive: On smaller screens, reduce padding, etc. */ 56 | @media (max-width: 767px) { 57 | .home-container { 58 | padding: 1rem; 59 | } 60 | .home-card-title { 61 | font-size: 1.1rem; 62 | } 63 | } 64 | 65 | .home-table { 66 | font-size: 0.8rem !important; 67 | } 68 | -------------------------------------------------------------------------------- /Frontend/src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import { Container, Row, Col, Card, Table } from "react-bootstrap"; 4 | import { Bar } from "react-chartjs-2"; 5 | import Chart from "chart.js/auto"; 6 | import "./Home.css"; // <-- Import your custom CSS 7 | 8 | const Home = () => { 9 | const [ongoingProjects, setOngoingProjects] = useState([]); 10 | const [ongoingTasks, setOngoingTasks] = useState([]); 11 | const [notifications, setNotifications] = useState([]); 12 | const [attendanceData, setAttendanceData] = useState({ 13 | present: 0, 14 | absent: 0, 15 | }); 16 | 17 | const apiUrl = import.meta.env.VITE_API_URL; 18 | 19 | useEffect(() => { 20 | fetchOngoingProjects(); 21 | fetchOngoingTasks(); 22 | fetchNotifications(); 23 | fetchAttendanceData(); 24 | }, []); 25 | 26 | const fetchOngoingProjects = () => { 27 | axios 28 | .get(`${apiUrl}/projects/ongoing`) 29 | .then((result) => { 30 | if (result.data.success) { 31 | setOngoingProjects(result.data.projects); 32 | } 33 | }) 34 | .catch((error) => 35 | console.error("Error fetching ongoing projects:", error) 36 | ); 37 | }; 38 | 39 | const fetchOngoingTasks = () => { 40 | axios 41 | .get(`${apiUrl}/tasks/ongoing`) 42 | .then((result) => { 43 | if (result.data.success) { 44 | setOngoingTasks(result.data.tasks); 45 | } 46 | }) 47 | .catch((error) => console.error("Error fetching ongoing tasks:", error)); 48 | }; 49 | 50 | const fetchNotifications = () => { 51 | axios 52 | .get(`${apiUrl}/notifications`) 53 | .then((result) => { 54 | if (result.data.success) { 55 | setNotifications(result.data.notifications); 56 | } 57 | }) 58 | .catch((error) => console.error("Error fetching notifications:", error)); 59 | }; 60 | 61 | const fetchAttendanceData = () => { 62 | axios 63 | .get(`${apiUrl}/attendance`) 64 | .then((result) => { 65 | if (result.data.success) { 66 | setAttendanceData(result.data.attendance); 67 | } 68 | }) 69 | .catch((error) => 70 | console.error("Error fetching attendance data:", error) 71 | ); 72 | }; 73 | 74 | const attendanceChartData = { 75 | labels: ["Present", "Absent"], 76 | datasets: [ 77 | { 78 | label: "Attendance", 79 | data: [attendanceData.present, attendanceData.absent], 80 | backgroundColor: ["#4CAF50", "#F44336"], // Light green and red colors 81 | }, 82 | ], 83 | }; 84 | 85 | const chartOptions = { 86 | responsive: true, 87 | maintainAspectRatio: false, 88 | plugins: { 89 | legend: { 90 | position: "bottom", 91 | }, 92 | }, 93 | }; 94 | 95 | return ( 96 | 97 | {/* Row 1: Notifications & Attendance */} 98 | 99 | {/* Column 1: Notifications */} 100 | 101 | 102 | 103 | 104 | Notifications & Alerts 105 | 106 |
    107 | {notifications.map((notification) => ( 108 |
  • 109 | {notification.message} 110 |
  • 111 | ))} 112 |
113 |
114 |
115 | 116 | 117 | {/* Column 2: Attendance Chart */} 118 | 119 | 120 | 121 | 122 | Employee Attendance Overview 123 | 124 |
125 | 126 |
127 |
128 |
129 | 130 |
131 | 132 | {/* Row 2: Ongoing Projects & Ongoing Tasks */} 133 | 134 | {/* Column 1: Ongoing Projects */} 135 | 136 | 137 | 138 | 139 | Ongoing Projects 140 | 141 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | {ongoingProjects.map((project) => ( 157 | 158 | 159 | 162 | 163 | 164 | ))} 165 | 166 |
NameStart DateStatus
{project.title} 160 | {new Date(project.start_date).toLocaleDateString()} 161 | {project.status}
167 |
168 |
169 | 170 | 171 | {/* Column 2: Ongoing Tasks */} 172 | 173 | 174 | 175 | Ongoing Tasks 176 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | {ongoingTasks.map((task) => ( 193 | 194 | 195 | 196 | 197 | 198 | 199 | ))} 200 | 201 |
NameDeadlineAssigned ToStatus
{task.description}{new Date(task.deadline).toLocaleDateString()}{task.employee_names.join(", ")}{task.status}
202 |
203 |
204 | 205 |
206 |
207 | ); 208 | }; 209 | 210 | export default Home; 211 | -------------------------------------------------------------------------------- /Frontend/src/components/Login.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import axios from 'axios'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { ToastContainer, toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | 7 | function Login() { 8 | const apiUrl = import.meta.env.VITE_API_URL; 9 | const [values, setValues] = useState({ 10 | email: '', 11 | password: '' 12 | }); 13 | const navigate = useNavigate(); 14 | axios.defaults.withCredentials = true; 15 | 16 | const handleInputChange = (e) => { 17 | const { name, value } = e.target; 18 | setValues({ ...values, [name]: value }); 19 | }; 20 | 21 | const handleSubmit = async (e) => { 22 | e.preventDefault(); 23 | 24 | // Check if email or password is empty 25 | if (!values.email || !values.password) { 26 | toast.error("Email and password are required."); 27 | return; 28 | } 29 | 30 | try { 31 | const result = await axios.post(`${apiUrl}/auth/adminlogin`, values); 32 | if (result.data.loginStatus) { 33 | localStorage.setItem('valid', true); 34 | toast.success("Login successful!"); 35 | navigate('/dashboard'); 36 | } else { 37 | const errorMessage = result.data.error || "Invalid email or password."; 38 | toast.error(errorMessage); 39 | } 40 | } catch (error) { 41 | if (error.response && error.response.status === 404) { 42 | toast.error("Invalid email or password."); 43 | } else { 44 | console.error(error); 45 | toast.error("An error occurred."); 46 | } 47 | } 48 | }; 49 | 50 | return ( 51 |
52 |
53 | 54 |
55 |
56 |

Login Page

57 |
58 |
59 | 60 | 70 |
71 |
72 | 73 | 81 |
82 | 83 |
84 |
85 |
86 |
87 |
88 | ); 89 | } 90 | 91 | export default Login; 92 | -------------------------------------------------------------------------------- /Frontend/src/components/ManageAdmin.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | 4 | const ManageAdmin = () => { 5 | const [admins, setAdmins] = useState([]); 6 | const [email, setEmail] = useState(""); 7 | const [password, setPassword] = useState(""); 8 | const apiUrl = import.meta.env.VITE_API_URL; 9 | 10 | useEffect(() => { 11 | fetchAdmins(); 12 | }, []); 13 | 14 | const fetchAdmins = async () => { 15 | try { 16 | const response = await axios.get(`${apiUrl}/auth/admin_records`); 17 | setAdmins(response.data.Result); 18 | } catch (error) { 19 | console.error("Error fetching admins:", error); 20 | } 21 | }; 22 | 23 | const handleAddAdmin = async () => { 24 | if (!email.trim() || !password.trim()) { 25 | alert("Email and password cannot be empty"); 26 | return; 27 | } 28 | 29 | const emailExists = admins.some((admin) => admin.email === email); 30 | if (emailExists) { 31 | alert("An admin with this email already exists"); 32 | return; 33 | } 34 | 35 | try { 36 | const response = await axios.post(`${apiUrl}/auth/add_admin`, { 37 | email, 38 | password, 39 | }); 40 | console.log(response.data); 41 | fetchAdmins(); 42 | setEmail(""); 43 | setPassword(""); 44 | } catch (error) { 45 | console.error("Error adding admin:", error); 46 | } 47 | }; 48 | 49 | 50 | const handleDeleteAdmin = async (id) => { 51 | try { 52 | const response = await axios.delete(`${apiUrl}/auth/delete_admin/${id}`); 53 | console.log(response.data); 54 | fetchAdmins(); // Refresh the admin list after deletion 55 | } catch (error) { 56 | console.error("Error deleting admin:", error); 57 | } 58 | }; 59 | 60 | return ( 61 |
62 |

Add Admin

63 |
64 |
65 | setEmail(e.target.value)} 72 | key="emailInput" 73 | /> 74 |
75 |
76 | setPassword(e.target.value)} 83 | key="passwordInput" 84 | /> 85 |
86 |
87 | 88 |
89 |
90 | 91 |

Admins List

92 | 100 |
101 | ); 102 | }; 103 | 104 | export default ManageAdmin; 105 | -------------------------------------------------------------------------------- /Frontend/src/components/Office.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | function Office() { 5 | const [officeLocations, setOfficeLocations] = useState([]); 6 | const [loading, setLoading] = useState(true); 7 | const apiUrl = import.meta.env.VITE_API_URL; 8 | const [newLocation, setNewLocation] = useState({ 9 | name: '', 10 | latitude: '', 11 | longitude: '', 12 | address: '' 13 | }); 14 | const [error, setError] = useState(''); 15 | const [successMessage, setSuccessMessage] = useState(''); 16 | 17 | useEffect(() => { 18 | // Fetch office locations from the backend API 19 | axios.get(`${apiUrl}/employee/office_location`) 20 | .then(response => { 21 | setOfficeLocations(response.data.officeLocations); 22 | setLoading(false); 23 | }) 24 | .catch(error => { 25 | console.error('Error fetching office locations:', error); 26 | setLoading(false); 27 | }); 28 | }, []); 29 | 30 | const handleDeleteLocation = (id) => { 31 | // Delete office location by ID 32 | axios.delete(`${apiUrl}/employee/office_location/${id}`) 33 | .then(response => { 34 | // Remove the deleted location from the state 35 | setOfficeLocations(officeLocations.filter(location => location.id !== id)); 36 | setSuccessMessage('Office location deleted successfully.'); 37 | }) 38 | .catch(error => { 39 | console.error('Error deleting office location:', error); 40 | setError('An error occurred while deleting office location.'); 41 | }); 42 | }; 43 | 44 | const handleSubmit = (e) => { 45 | e.preventDefault(); 46 | axios.post(`${apiUrl}/employee/office_location`, newLocation) 47 | .then(response => { 48 | setOfficeLocations([...officeLocations, response.data.officeLocation]); 49 | setNewLocation({ name: '', latitude: '', longitude: '', address: '' }); 50 | setSuccessMessage('Office location added successfully.'); 51 | }) 52 | .catch(error => { 53 | console.error('Error adding office location:', error); 54 | setError('An error occurred while adding office location.'); 55 | }); 56 | }; 57 | 58 | return ( 59 |
60 |

Office Locations

61 | {loading ? ( 62 |

Loading...

63 | ) : ( 64 | <> 65 | 73 | {error &&

Error: {error}

} 74 | {successMessage &&

{successMessage}

} 75 |

Add New Office Location

76 |
77 |
78 | 79 | setNewLocation({ ...newLocation, name: e.target.value })} required /> 80 |
81 |
82 | 83 | setNewLocation({ ...newLocation, latitude: e.target.value })} required /> 84 |
85 |
86 | 87 | setNewLocation({ ...newLocation, longitude: e.target.value })} required /> 88 |
89 |
90 | 91 | setNewLocation({ ...newLocation, address: e.target.value })} required /> 92 |
93 | 94 |
95 | 96 | )} 97 |
98 | ); 99 | } 100 | 101 | export default Office; 102 | -------------------------------------------------------------------------------- /Frontend/src/components/PMT/AdminProjects.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | import io from "socket.io-client"; 4 | import { 5 | Card, 6 | Button, 7 | Container, 8 | Row, 9 | Col, 10 | Modal, 11 | Form, 12 | } from "react-bootstrap"; 13 | 14 | const socket = io("http://localhost:3000"); 15 | 16 | const AdminProjects = () => { 17 | const [projects, setProjects] = useState([]); 18 | const [showModal, setShowModal] = useState(false); 19 | const [showEditModal, setShowEditModal] = useState(false); 20 | const [showDeleteModal, setShowDeleteModal] = useState(false); 21 | const [newProject, setNewProject] = useState({ 22 | title: "", 23 | description: "", 24 | status: "Not Started", 25 | completion_date: "", 26 | start_date: "", 27 | priority: "Medium", 28 | client_id: "", 29 | }); 30 | const [editProject, setEditProject] = useState(null); 31 | const [projectToDelete, setProjectToDelete] = useState(null); 32 | const [clients, setClients] = useState([]); 33 | const [filter, setFilter] = useState("all"); 34 | const apiUrl = import.meta.env.VITE_API_URL; 35 | 36 | useEffect(() => { 37 | fetchProjects(); 38 | fetchClients(); 39 | 40 | // Listen for real-time updates 41 | socket.on("taskUpdated", (updatedTask) => { 42 | setProjects((prevProjects) => 43 | prevProjects.map((project) => { 44 | if (project.project_id === updatedTask.project_id) { 45 | return { 46 | ...project, 47 | taskCount: 48 | project.taskCount + 49 | (updatedTask.status === "completed" ? -1 : 1), 50 | }; 51 | } 52 | return project; 53 | }) 54 | ); 55 | }); 56 | 57 | return () => { 58 | socket.off("taskUpdated"); 59 | }; 60 | }, []); 61 | 62 | const fetchProjects = () => { 63 | axios 64 | .get(`${apiUrl}/projects`, { withCredentials: true }) 65 | .then((res) => { 66 | if (res.data.success) { 67 | setProjects(res.data.projects); 68 | } 69 | }) 70 | .catch((err) => console.error("Error fetching projects:", err)); 71 | }; 72 | 73 | const fetchClients = () => { 74 | axios 75 | .get(`${apiUrl}/clients`, { withCredentials: true }) 76 | .then((res) => { 77 | if (res.data.success) { 78 | setClients(res.data.clients); 79 | } 80 | }) 81 | .catch((err) => console.error("Error fetching clients:", err)); 82 | }; 83 | 84 | const handleAddProject = () => { 85 | const token = localStorage.getItem("jwt"); // Assuming the token is stored in localStorage 86 | axios 87 | .post(`${apiUrl}/projects`, newProject, { 88 | withCredentials: true, 89 | headers: { 90 | Authorization: `Bearer ${token}`, 91 | }, 92 | }) 93 | .then((res) => { 94 | if (res.data.success) { 95 | setProjects([...projects, res.data.project]); // Update UI 96 | setShowModal(false); 97 | setNewProject({ 98 | title: "", 99 | description: "", 100 | status: "Not Started", 101 | completion_date: "", 102 | start_date: "", 103 | priority: "Medium", 104 | client_id: "", 105 | }); 106 | } 107 | }) 108 | .catch((err) => console.error("Error adding project:", err)); 109 | }; 110 | 111 | const handleEditProject = () => { 112 | const token = localStorage.getItem("jwt"); // Assuming the token is stored in localStorage 113 | axios 114 | .put(`${apiUrl}/projects/${editProject.project_id}`, editProject, { 115 | withCredentials: true, 116 | headers: { 117 | Authorization: `Bearer ${token}`, 118 | }, 119 | }) 120 | .then((res) => { 121 | if (res.data.success) { 122 | setProjects((prevProjects) => 123 | prevProjects.map((project) => 124 | project.project_id === editProject.project_id 125 | ? res.data.project 126 | : project 127 | ) 128 | ); 129 | setShowEditModal(false); 130 | setEditProject(null); 131 | } 132 | }) 133 | .catch((err) => console.error("Error editing project:", err)); 134 | }; 135 | 136 | const handleDeleteProject = () => { 137 | const token = localStorage.getItem("jwt"); // Assuming the token is stored in localStorage 138 | axios 139 | .delete(`${apiUrl}/projects/${projectToDelete.project_id}`, { 140 | withCredentials: true, 141 | headers: { 142 | Authorization: `Bearer ${token}`, 143 | }, 144 | }) 145 | .then((res) => { 146 | if (res.data.success) { 147 | setProjects((prevProjects) => 148 | prevProjects.filter( 149 | (project) => project.project_id !== projectToDelete.project_id 150 | ) 151 | ); 152 | setShowDeleteModal(false); 153 | setProjectToDelete(null); 154 | } 155 | }) 156 | .catch((err) => console.error("Error deleting project:", err)); 157 | }; 158 | 159 | const handleFilterChange = (e) => { 160 | setFilter(e.target.value); 161 | }; 162 | 163 | const filteredProjects = projects.filter((project) => { 164 | if (filter === "all") return true; 165 | const now = new Date(); 166 | const projectDate = new Date(project.start_date); 167 | if (filter === "today") { 168 | return ( 169 | projectDate.getDate() === now.getDate() && 170 | projectDate.getMonth() === now.getMonth() && 171 | projectDate.getFullYear() === now.getFullYear() 172 | ); 173 | } 174 | if (filter === "week") { 175 | const oneWeekAgo = new Date(); 176 | oneWeekAgo.setDate(now.getDate() - 7); 177 | return projectDate >= oneWeekAgo && projectDate <= now; 178 | } 179 | if (filter === "month") { 180 | return ( 181 | projectDate.getMonth() === now.getMonth() && 182 | projectDate.getFullYear() === now.getFullYear() 183 | ); 184 | } 185 | return true; 186 | }); 187 | 188 | return ( 189 | 190 |

Project Management

191 | 198 | 199 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | {filteredProjects.map((project) => ( 212 | 213 | 214 | 215 | {project.title} 216 | {project.description} 217 |

218 | Status: {project.status} 219 |

220 |

221 | Priority: {project.priority} 222 |

223 |

224 | Client:{" "} 225 | {clients.find( 226 | (client) => client.client_id === project.client_id 227 | )?.name || "N/A"} 228 |

229 |

230 | Start Date:{" "} 231 | {project.start_date 232 | ? new Date(project.start_date).toLocaleDateString() 233 | : "N/A"} 234 |

235 |

236 | Completion Date:{" "} 237 | {project.completion_date 238 | ? new Date(project.completion_date).toLocaleDateString() 239 | : "N/A"} 240 |

241 | 251 | 260 |
261 |
262 | 263 | ))} 264 |
265 | 266 | {/* Modal for Creating New Project */} 267 | setShowModal(false)}> 268 | 269 | Create New Project 270 | 271 | 272 |
273 | 274 | Project Title 275 | 280 | setNewProject({ ...newProject, title: e.target.value }) 281 | } 282 | /> 283 | 284 | 285 | 286 | Description 287 | 292 | setNewProject({ ...newProject, description: e.target.value }) 293 | } 294 | /> 295 | 296 | 297 | 298 | Status 299 | 302 | setNewProject({ ...newProject, status: e.target.value }) 303 | } 304 | > 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | Completion Date 315 | 319 | setNewProject({ 320 | ...newProject, 321 | completion_date: e.target.value, 322 | }) 323 | } 324 | /> 325 | 326 | 327 | 328 | Start Date 329 | 333 | setNewProject({ ...newProject, start_date: e.target.value }) 334 | } 335 | /> 336 | 337 | 338 | 339 | Priority 340 | 343 | setNewProject({ ...newProject, priority: e.target.value }) 344 | } 345 | > 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | Client 355 | 358 | setNewProject({ ...newProject, client_id: e.target.value }) 359 | } 360 | > 361 | 362 | {clients.map((client) => ( 363 | 366 | ))} 367 | 368 | 369 | 370 | 373 |
374 |
375 |
376 | 377 | {/* Modal for Editing Project */} 378 | {editProject && ( 379 | setShowEditModal(false)}> 380 | 381 | Edit Project 382 | 383 | 384 |
385 | 386 | Project Title 387 | 392 | setEditProject({ ...editProject, title: e.target.value }) 393 | } 394 | /> 395 | 396 | 397 | 398 | Description 399 | 404 | setEditProject({ 405 | ...editProject, 406 | description: e.target.value, 407 | }) 408 | } 409 | /> 410 | 411 | 412 | 413 | Status 414 | 417 | setEditProject({ ...editProject, status: e.target.value }) 418 | } 419 | > 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | Completion Date 430 | 434 | setEditProject({ 435 | ...editProject, 436 | completion_date: e.target.value, 437 | }) 438 | } 439 | /> 440 | 441 | 442 | 443 | Start Date 444 | 448 | setEditProject({ 449 | ...editProject, 450 | start_date: e.target.value, 451 | }) 452 | } 453 | /> 454 | 455 | 456 | 457 | Priority 458 | 461 | setEditProject({ ...editProject, priority: e.target.value }) 462 | } 463 | > 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | Client 473 | 476 | setEditProject({ 477 | ...editProject, 478 | client_id: e.target.value, 479 | }) 480 | } 481 | > 482 | 483 | {clients.map((client) => ( 484 | 487 | ))} 488 | 489 | 490 | 491 | 494 |
495 |
496 |
497 | )} 498 | 499 | {/* Modal for Deleting Project */} 500 | setShowDeleteModal(false)}> 501 | 502 | Delete Project 503 | 504 | 505 | Are you sure you want to delete the project` {projectToDelete?.title} 506 | `? 507 | 508 | 509 | 512 | 515 | 516 | 517 |
518 | ); 519 | }; 520 | 521 | export default AdminProjects; 522 | -------------------------------------------------------------------------------- /Frontend/src/components/PMT/AdminTasks.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import axios from "axios"; 4 | import io from "socket.io-client"; 5 | import { 6 | Table, 7 | Button, 8 | Container, 9 | Form, 10 | Modal, 11 | Row, 12 | Col, 13 | } from "react-bootstrap"; 14 | 15 | const socket = io("http://localhost:3000"); 16 | 17 | const AdminTasks = () => { 18 | const { projectId } = useParams(); 19 | const [tasks, setTasks] = useState([]); 20 | const [employees, setEmployees] = useState([]); 21 | const [projects, setProjects] = useState([]); 22 | const [newTask, setNewTask] = useState({ 23 | description: "", 24 | employee_ids: [], 25 | deadline: "", 26 | project_id: projectId || "", 27 | status: "pending", // default status 28 | }); 29 | const [editTask, setEditTask] = useState(null); 30 | 31 | const [showModal, setShowModal] = useState(false); 32 | const [showEditModal, setShowEditModal] = useState(false); 33 | const [filter, setFilter] = useState({ project: "", employee: "" }); 34 | const apiUrl = import.meta.env.VITE_API_URL; 35 | 36 | useEffect(() => { 37 | fetchTasks(); 38 | fetchProjects(); 39 | 40 | // Listen for real-time updates 41 | socket.on("taskUpdated", (updatedTask) => { 42 | setTasks((prevTasks) => 43 | prevTasks.map((task) => 44 | task.task_id === updatedTask.task_id ? updatedTask : task 45 | ) 46 | ); 47 | }); 48 | 49 | return () => { 50 | socket.off("taskUpdated"); 51 | }; 52 | }, [projectId]); 53 | 54 | // Fetch tasks under this project 55 | const fetchTasks = () => { 56 | axios 57 | .get(`${apiUrl}/tasks?project_id=${projectId}`) 58 | .then((res) => { 59 | if (res.data.success) { 60 | setTasks(res.data.tasks); 61 | } 62 | }) 63 | .catch((err) => console.error("Error fetching tasks:", err)); 64 | }; 65 | 66 | // Fetch available employees 67 | // Fetch employees when the component mounts 68 | useEffect(() => { 69 | fetchEmployees(); 70 | }, []); 71 | 72 | // Function to fetch employees 73 | const fetchEmployees = async () => { 74 | try { 75 | const response = await axios.get(`${apiUrl}/tasks/list`); 76 | if (response.data.success) { 77 | setEmployees(response.data.employees); // Set the employees state 78 | } else { 79 | console.error("Failed to fetch employees:", response.data.message); 80 | } 81 | } catch (error) { 82 | console.error("Error fetching employees:", error); 83 | } 84 | }; 85 | 86 | // Fetch available projects 87 | const fetchProjects = () => { 88 | axios 89 | .get(`${apiUrl}/projects`) 90 | .then((res) => { 91 | if (res.data.success) { 92 | setProjects(res.data.projects); 93 | } 94 | }) 95 | .catch((err) => console.error("Error fetching projects:", err)); 96 | }; 97 | 98 | // Handle adding a new task 99 | const handleAddTask = () => { 100 | axios 101 | .post(`${apiUrl}/tasks`, { 102 | ...newTask, 103 | project_id: newTask.project_id || projectId, 104 | }) 105 | .then((res) => { 106 | if (res.data.success) { 107 | setTasks([...tasks, res.data.task]); // Update UI 108 | setShowModal(false); 109 | setNewTask({ 110 | description: "", 111 | employee_ids: [], 112 | deadline: "", 113 | project_id: projectId || "", 114 | status: "pending", // reset status to default 115 | }); 116 | 117 | socket.emit("taskAssigned", res.data.task); // Notify employee 118 | } 119 | }) 120 | .catch((err) => console.error("Error adding task:", err)); 121 | }; 122 | 123 | // Handle deleting a task 124 | const handleDeleteTask = (taskId) => { 125 | axios 126 | .delete(`${apiUrl}/tasks/${taskId}`) 127 | .then((res) => { 128 | if (res.data.success) { 129 | // Remove the deleted task from local state 130 | setTasks((prevTasks) => 131 | prevTasks.filter((t) => t.task_id !== taskId) 132 | ); 133 | } 134 | }) 135 | .catch((err) => console.error("Error deleting task:", err)); 136 | }; 137 | 138 | // Handle opening the edit modal 139 | const openEditModal = (task) => { 140 | setEditTask(task); 141 | setShowEditModal(true); 142 | }; 143 | 144 | // Handle editing a task 145 | const handleEditTask = () => { 146 | axios 147 | .put(`${apiUrl}/tasks/${editTask.task_id}`, editTask) 148 | .then((res) => { 149 | if (res.data.success) { 150 | // Update local state so the changes reflect 151 | setTasks((prevTasks) => 152 | prevTasks.map((t) => 153 | t.task_id === editTask.task_id ? res.data.task : t 154 | ) 155 | ); 156 | setShowEditModal(false); 157 | setEditTask(null); 158 | } 159 | }) 160 | .catch((err) => console.error("Error updating task:", err)); 161 | }; 162 | 163 | // Handle filtering tasks 164 | const handleFilterChange = (e) => { 165 | const { name, value } = e.target; 166 | setFilter({ ...filter, [name]: value }); 167 | }; 168 | 169 | const filteredTasks = tasks.filter((task) => { 170 | const matchesProject = filter.project 171 | ? task.project_id === parseInt(filter.project) 172 | : true; 173 | const matchesEmployee = filter.employee 174 | ? task.employee_ids.includes(parseInt(filter.employee)) 175 | : true; 176 | return matchesProject && matchesEmployee; 177 | }); 178 | 179 | return ( 180 | 181 |

Project Tasks

182 | 183 | 184 | 189 | 190 | {projects.map((project) => ( 191 | 194 | ))} 195 | 196 | 197 | 198 | 203 | 204 | {employees.map((emp) => ( 205 | 208 | ))} 209 | 210 | 211 | 212 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | {filteredTasks.map((task) => ( 233 | 234 | 235 | 236 | 237 | 242 | 243 | 260 | 261 | ))} 262 | 263 |
Task IDDescriptionAssigned EmployeesDeadlineStatusActions
{task.task_id}{task.description}{task.employee_names?.join(", ") || "N/A"} 238 | {task.deadline 239 | ? new Date(task.deadline).toLocaleString() 240 | : "N/A"} 241 | {task.status} 244 | 252 | 259 |
264 | 265 | {/* Modal for Adding Task */} 266 | setShowModal(false)}> 267 | 268 | Assign New Task 269 | 270 | 271 |
272 | 273 | Task Description 274 | 280 | setNewTask({ ...newTask, description: e.target.value }) 281 | } 282 | /> 283 | 284 | 285 | 286 | Assign To 287 | 292 | setNewTask({ 293 | ...newTask, 294 | employee_ids: Array.from( 295 | e.target.selectedOptions, 296 | (option) => parseInt(option.value, 10) 297 | ), 298 | }) 299 | } 300 | > 301 | {employees.map((emp) => ( 302 | 305 | ))} 306 | 307 | 308 | 309 | 310 | Project 311 | 315 | setNewTask({ ...newTask, project_id: e.target.value }) 316 | } 317 | > 318 | 319 | {projects.map((project) => ( 320 | 323 | ))} 324 | 325 | 326 | 327 | 328 | Deadline 329 | 333 | setNewTask({ ...newTask, deadline: e.target.value }) 334 | } 335 | /> 336 | 337 | 338 | 341 |
342 |
343 |
344 | 345 | {/* Modal for Editing Task */} 346 | {editTask && ( 347 | setShowEditModal(false)}> 348 | 349 | Edit Task 350 | 351 | 352 |
353 | 354 | Task Description 355 | 361 | setEditTask({ ...editTask, description: e.target.value }) 362 | } 363 | /> 364 | 365 | 366 | 367 | Assign To 368 | 373 | setEditTask({ 374 | ...editTask, 375 | employee_ids: Array.from( 376 | e.target.selectedOptions, 377 | (option) => parseInt(option.value, 10) 378 | ), 379 | }) 380 | } 381 | > 382 | {employees.map((emp) => ( 383 | 386 | ))} 387 | 388 | 389 | 390 | 391 | Project 392 | 396 | setEditTask({ ...editTask, project_id: e.target.value }) 397 | } 398 | > 399 | 400 | {projects.map((project) => ( 401 | 404 | ))} 405 | 406 | 407 | 408 | 409 | Deadline 410 | 414 | setEditTask({ ...editTask, deadline: e.target.value }) 415 | } 416 | /> 417 | 418 | 419 | 420 | Status 421 | 425 | setEditTask({ ...editTask, status: e.target.value }) 426 | } 427 | > 428 | 429 | 430 | 431 | 432 | 433 | 434 | 437 |
438 |
439 |
440 | )} 441 |
442 | ); 443 | }; 444 | 445 | export default AdminTasks; 446 | -------------------------------------------------------------------------------- /Frontend/src/components/PMT/Clients.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | import { Table, Button, Modal, Form, Container } from "react-bootstrap"; 4 | 5 | const Clients = () => { 6 | const [clients, setClients] = useState([]); 7 | const [showAddModal, setShowAddModal] = useState(false); 8 | const [showDeleteModal, setShowDeleteModal] = useState(false); 9 | const [newClient, setNewClient] = useState({ 10 | name: "", 11 | contact_person: "", 12 | email: "", 13 | phone: "", 14 | address: "", 15 | }); 16 | const [clientToDelete, setClientToDelete] = useState(null); 17 | const apiUrl = import.meta.env.VITE_API_URL; 18 | 19 | useEffect(() => { 20 | fetchClients(); 21 | }, []); 22 | 23 | const fetchClients = () => { 24 | axios 25 | .get(`${apiUrl}/clients`) 26 | .then((res) => { 27 | if (res.data.success) { 28 | setClients(res.data.clients); 29 | } 30 | }) 31 | .catch((err) => console.error("Error fetching clients:", err)); 32 | }; 33 | 34 | const handleAddClient = () => { 35 | axios 36 | .post(`${apiUrl}/clients`, newClient) 37 | .then((res) => { 38 | if (res.data.success) { 39 | setClients([...clients, res.data.client]); 40 | setShowAddModal(false); 41 | setNewClient({ 42 | name: "", 43 | contact_person: "", 44 | email: "", 45 | phone: "", 46 | address: "", 47 | }); 48 | } 49 | }) 50 | .catch((err) => console.error("Error adding client:", err)); 51 | }; 52 | 53 | const handleDeleteClient = () => { 54 | axios 55 | .delete(`${apiUrl}/clients/${clientToDelete.client_id}`) 56 | .then((res) => { 57 | if (res.data.success) { 58 | setClients( 59 | clients.filter( 60 | (client) => client.client_id !== clientToDelete.client_id 61 | ) 62 | ); 63 | setShowDeleteModal(false); 64 | setClientToDelete(null); 65 | } 66 | }) 67 | .catch((err) => console.error("Error deleting client:", err)); 68 | }; 69 | 70 | return ( 71 | 72 |

Clients

73 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {clients.map((client) => ( 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 113 | 114 | ))} 115 | 116 |
Client IDNameContact PersonEmailPhoneAddressActions
{client.client_id}{client.name}{client.contact_person}{client.email}{client.phone}{client.address} 102 | 112 |
117 | 118 | {/* Modal for Adding Client */} 119 | setShowAddModal(false)}> 120 | 121 | Add New Client 122 | 123 | 124 |
125 | 126 | Name 127 | 132 | setNewClient({ ...newClient, name: e.target.value }) 133 | } 134 | /> 135 | 136 | 137 | Contact Person 138 | 143 | setNewClient({ ...newClient, contact_person: e.target.value }) 144 | } 145 | /> 146 | 147 | 148 | Email 149 | 154 | setNewClient({ ...newClient, email: e.target.value }) 155 | } 156 | /> 157 | 158 | 159 | Phone 160 | 165 | setNewClient({ ...newClient, phone: e.target.value }) 166 | } 167 | /> 168 | 169 | 170 | Address 171 | 177 | setNewClient({ ...newClient, address: e.target.value }) 178 | } 179 | /> 180 | 181 | 184 |
185 |
186 |
187 | 188 | {/* Modal for Deleting Client */} 189 | setShowDeleteModal(false)}> 190 | 191 | Delete Client 192 | 193 | 194 | Are you sure you want to delete the client `{clientToDelete?.name}`? 195 | 196 | 197 | 200 | 203 | 204 | 205 |
206 | ); 207 | }; 208 | 209 | export default Clients; 210 | -------------------------------------------------------------------------------- /Frontend/src/components/PMT/EmployeeTasks.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | font-weight: 600; 3 | text-align: center; 4 | margin-bottom: 15px; 5 | } 6 | 7 | .table { 8 | width: 100%; 9 | border-collapse: separate; 10 | border-spacing: 0; 11 | border-radius: 8px; 12 | overflow: hidden; 13 | } 14 | 15 | .table thead { 16 | background-color: #c4ced9; 17 | color: white; 18 | font-weight: 600; 19 | } 20 | 21 | .table th { 22 | padding: 12px; 23 | text-align: center; 24 | font-weight: 600; 25 | } 26 | .table td { 27 | padding: 12px; 28 | text-align: center; 29 | font-weight: 400; 30 | } 31 | 32 | .table tr:nth-child(1) { 33 | background-color: #e6e0e7 !important; 34 | } 35 | 36 | .table tr:nth-child(even) { 37 | background-color: #f2f2f2; 38 | } 39 | 40 | .table tr:hover { 41 | background-color: #e6e6e6; 42 | } 43 | 44 | .btn { 45 | margin: 2px; 46 | padding: 5px 10px; 47 | font-size: 14px; 48 | border-radius: 6px; 49 | } 50 | 51 | .btn-warning { 52 | background-color: #ffc107; 53 | border: none; 54 | } 55 | 56 | .btn-success { 57 | background-color: #28a745; 58 | border: none; 59 | } 60 | -------------------------------------------------------------------------------- /Frontend/src/components/PMT/EmployeeTasks.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState, useEffect } from "react"; 3 | import { Table, Button, Container } from "react-bootstrap"; 4 | import "./EmployeeTasks.css"; 5 | import io from "socket.io-client"; 6 | import axios from "axios"; 7 | 8 | const socket = io("http://localhost:3000"); 9 | 10 | const EmployeeTasks = ({ employeeId }) => { 11 | const [tasks, setTasks] = useState([]); 12 | const apiUrl = import.meta.env.VITE_API_URL; 13 | 14 | useEffect(() => { 15 | if (!employeeId) { 16 | console.error("Employee ID is undefined"); 17 | return; 18 | } 19 | 20 | console.log("Fetching tasks for employeeId:", employeeId); 21 | 22 | // Fetch tasks assigned to logged-in employee 23 | axios 24 | .get(`${apiUrl}/tasks/employee/${employeeId}`, { 25 | withCredentials: true, 26 | }) 27 | .then((res) => { 28 | console.log("API Response:", res.data); // Log full API response 29 | if (res.data.success) { 30 | console.log("Setting tasks state:", res.data.tasks); 31 | setTasks(res.data.tasks); 32 | } else { 33 | console.error("Failed to fetch tasks:", res.data.message); 34 | } 35 | }) 36 | .catch((err) => console.error("Error fetching tasks:", err)); 37 | }, [employeeId]); 38 | 39 | // Listen for real-time task updates 40 | useEffect(() => { 41 | socket.on("taskUpdated", (updatedTask) => { 42 | console.log("Task updated:", updatedTask); 43 | setTasks((prevTasks) => 44 | prevTasks.map((task) => 45 | task.task_id === updatedTask.task_id ? updatedTask : task 46 | ) 47 | ); 48 | }); 49 | 50 | socket.on("taskAssigned", (newTask) => { 51 | console.log("New task assigned:", newTask); 52 | setTasks((prevTasks) => [...prevTasks, newTask]); 53 | }); 54 | 55 | socket.on("taskDeleted", (deletedTask) => { 56 | console.log("Task deleted:", deletedTask); 57 | setTasks((prevTasks) => 58 | prevTasks.filter((task) => task.task_id !== deletedTask.taskId) 59 | ); 60 | }); 61 | 62 | return () => { 63 | socket.off("taskUpdated"); 64 | socket.off("taskAssigned"); 65 | socket.off("taskDeleted"); 66 | }; 67 | }, []); 68 | 69 | // Function to update task status 70 | const handleTaskStatusChange = (taskId, status) => { 71 | axios 72 | .put( 73 | `${apiUrl}/taskstatus/${taskId}`, 74 | { status }, 75 | { withCredentials: true } 76 | ) 77 | .then((res) => { 78 | if (res.data.success) { 79 | console.log(`Task status changed to ${status}:`, res.data.task); 80 | // Update the state immediately to reflect the change 81 | setTasks((prevTasks) => 82 | prevTasks.map((task) => 83 | task.task_id === taskId ? { ...task, status } : task 84 | ) 85 | ); 86 | socket.emit("updateTask", res.data.task); // Notify real-time change 87 | } else { 88 | console.error("Failed to update task:", res.data.message); 89 | } 90 | }) 91 | .catch((err) => console.error("Error updating task:", err)); 92 | }; 93 | 94 | return ( 95 | 96 |

Your Assigned Tasks

97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | {tasks.length === 0 ? ( 109 | 110 | 113 | 114 | ) : ( 115 | tasks.map((task) => ( 116 | 117 | 118 | 119 | 124 | 125 | 153 | 154 | )) 155 | )} 156 | 157 |
TaskProjectDeadlineStatusAction
111 | No tasks assigned yet. 112 |
{task.description}{task.project_title || "N/A"} 120 | {task.deadline 121 | ? new Date(task.deadline).toLocaleString() 122 | : "N/A"} 123 | {task.status} 126 | {task.status !== "completed" && ( 127 | <> 128 | {task.status !== "in_progress" && ( 129 | 139 | )} 140 | 150 | 151 | )} 152 |
158 |
159 | ); 160 | }; 161 | 162 | export default EmployeeTasks; 163 | -------------------------------------------------------------------------------- /Frontend/src/components/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navigate } from 'react-router-dom'; 3 | 4 | const PrivateRoute = ({ children }) => { 5 | return localStorage.getItem('valid') ? children : ; 6 | } 7 | 8 | export default PrivateRoute; 9 | -------------------------------------------------------------------------------- /Frontend/src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useLocation } from "react-router-dom"; 2 | import { Button } from "react-bootstrap"; 3 | import SidebarClose from "../assets/SidebarClose"; 4 | import SidebarOpen from "../assets/SidebarOpen"; 5 | 6 | const Sidebar = ({ sidebarOpen, toggleSidebar, handleLogout }) => { 7 | const location = useLocation(); 8 | 9 | return ( 10 |
16 |
21 | 25 | 26 | Logo Image 36 |

45 | {sidebarOpen ? "WORKSUITE" : ""} 46 |

47 |
48 | 49 | 214 | 229 |
233 | 242 |

246 | {!sidebarOpen ? "" : "v1.0"} 247 |

248 |
249 |
250 |
251 | ); 252 | }; 253 | 254 | export default Sidebar; 255 | -------------------------------------------------------------------------------- /Frontend/src/components/Start.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect } from "react"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | const Start = () => { 6 | const navigate = useNavigate(); 7 | const apiUrl = import.meta.env.VITE_API_URL; 8 | 9 | useEffect(() => { 10 | axios.get(`${apiUrl}/verify`, { withCredentials: true }) 11 | .then(result => { 12 | if (result.data.Status) { 13 | if (result.data.role === "admin") { 14 | navigate('/dashboard'); 15 | } else { 16 | navigate('/employeedetail/' + result.data.id); 17 | } 18 | } 19 | }) 20 | .catch(err => console.log(err)); 21 | }, [navigate]); 22 | 23 | const handleEmployeeLogin = () => { 24 | navigate('/employeelogin'); 25 | }; 26 | 27 | const handleAdminLogin = () => { 28 | navigate('/adminlogin'); 29 | }; 30 | 31 | return ( 32 |
33 |
34 |
35 |
36 |

Login As

37 |
38 | 41 | 44 |
45 |
46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | export default Start; 53 | -------------------------------------------------------------------------------- /Frontend/src/components/style.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar-track { 2 | background-color:#fff !important; 3 | } 4 | ::-webkit-scrollbar { 5 | width:8px; 6 | background-color:#ffffff !important; 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | border-radius:2px !important; 10 | background-color: #bec8d34a 11 | !important; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb:hover { 15 | cursor:pointer ; 16 | } 17 | 18 | 19 | .loginPage { 20 | background-color: #ffffff; 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | height: auto; 25 | margin-top : 50px !important; 26 | margin-bottom : 40px; 27 | } 28 | 29 | .loginPage2 { 30 | background-color: #ffffff; 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | height: auto; 35 | margin-top : 0px !important; 36 | margin-bottom : 0px; 37 | } 38 | 39 | .loginForm { 40 | border-radius: 16px !important; 41 | width: 500px; 42 | } 43 | 44 | .loginForm h2 { 45 | color: #333333; 46 | margin-bottom: 20px; 47 | font-weight: 600; 48 | } 49 | 50 | .loginForm input, 51 | .loginForm input { 52 | width: 100%; 53 | padding: 12px 16px; 54 | box-shadow: #422800 0px 0px 0 0; 55 | border: 1px solid #000000 !important; 56 | border-radius: 30px; 57 | margin-bottom: 20px; 58 | font-size: .8rem !important; 59 | } 60 | 61 | 62 | 63 | .loginForm button:focus { 64 | outline: none; 65 | } 66 | 67 | .bold-form-label{ 68 | font-weight: 500; 69 | } 70 | 71 | .loginForm button { 72 | width: 45%; 73 | padding: 12px; 74 | font-weight: 500; 75 | border: none; 76 | color: white; 77 | cursor: pointer; 78 | transition: background-color 0.3s ease; 79 | background-color: transparent !important; 80 | margin-top:10px; 81 | } 82 | 83 | 84 | .button-74 { 85 | align-items: center; 86 | appearance: none; 87 | background-clip: padding-box; 88 | background-color: initial; 89 | background-image: none; 90 | border-style: none; 91 | box-sizing: border-box; 92 | color: #fff; 93 | cursor: pointer; 94 | display: inline-block; 95 | flex-direction: row; 96 | flex-shrink: 0; 97 | font-family: Eina01,sans-serif; 98 | font-size: 16px; 99 | font-weight: 800; 100 | justify-content: center; 101 | line-height: 24px; 102 | margin: 0; 103 | min-height: 64px; 104 | outline: none; 105 | overflow: visible; 106 | padding: 19px 26px; 107 | pointer-events: auto; 108 | position: relative; 109 | text-align: center; 110 | text-decoration: none; 111 | text-transform: none; 112 | user-select: none; 113 | -webkit-user-select: none; 114 | touch-action: manipulation; 115 | vertical-align: middle; 116 | width: auto; 117 | word-break: keep-all; 118 | z-index: 0; 119 | } 120 | 121 | @media (min-width: 768px) { 122 | .button-74 { 123 | padding: 19px 32px; 124 | } 125 | } 126 | 127 | .button-74:before, 128 | .button-74:after { 129 | border-radius: 80px; 130 | } 131 | 132 | .button-74:before { 133 | background-color: rgba(249, 58, 19, .32); 134 | content: ""; 135 | display: block; 136 | height: 100%; 137 | left: 0; 138 | overflow: hidden; 139 | position: absolute; 140 | top: 0; 141 | width: 100%; 142 | z-index: -2; 143 | } 144 | 145 | .button-74:after { 146 | background-color: initial; 147 | background-image: linear-gradient(92.83deg, #ff7426 0, #f93a13 100%); 148 | bottom: 4px; 149 | content: ""; 150 | display: block; 151 | left: 4px; 152 | overflow: hidden; 153 | position: absolute; 154 | right: 4px; 155 | top: 4px; 156 | transition: all 100ms ease-out; 157 | z-index: -1; 158 | } 159 | 160 | .button-74:hover:not(:disabled):after { 161 | bottom: 0; 162 | left: 0; 163 | right: 0; 164 | top: 0; 165 | transition-timing-function: ease-in; 166 | } 167 | 168 | .button-74:active:not(:disabled) { 169 | color: #ccc; 170 | } 171 | 172 | .button-74:active:not(:disabled):after { 173 | background-image: linear-gradient(0deg, rgba(0, 0, 0, .2), rgba(0, 0, 0, .2)), linear-gradient(92.83deg, #ff7426 0, #f93a13 100%); 174 | bottom: 4px; 175 | left: 4px; 176 | right: 4px; 177 | top: 4px; 178 | } 179 | 180 | .button-74:disabled { 181 | cursor: default; 182 | opacity: .24; 183 | } 184 | 185 | 186 | @media (min-width: 768px) { 187 | .button-74 { 188 | min-width: 120px; 189 | padding: 0 25px; 190 | } 191 | } 192 | 193 | .form-control::placeholder{ 194 | color: #bebebe; 195 | } 196 | 197 | .top--title{ 198 | background-color: #bec8d326; 199 | } 200 | 201 | .back-button { 202 | padding: 0px 8px !important; 203 | font-weight: 500 !important; 204 | color: #9d9d9d !important; 205 | cursor: pointer !important; 206 | transition: background-color 0.3s ease !important; 207 | margin-top: 10px !important; 208 | border: none !important; 209 | background-color: transparent !important; 210 | box-shadow: none !important; 211 | font-size: 12px; 212 | margin-left:0px !important; 213 | width:100%; 214 | max-width:100%; 215 | text-align: left; 216 | } 217 | 218 | .form-container { 219 | overflow-y: auto; 220 | overflow-x: hidden; 221 | padding:2em .4em; 222 | } 223 | 224 | .employee-image { 225 | max-width: 100px; 226 | height: auto; 227 | display: block; 228 | margin: 0 auto; 229 | } 230 | 231 | .HomeCard { 232 | box-shadow: rgba(0, 0, 0, 0.048) 0px 5px 12px, rgba(0, 0, 0, 0.045) 0px 5px 10px; 233 | border-radius: 16px !important; 234 | padding: 30px !important; 235 | transition: transform 0.3s ease; 236 | border: 5px solid #fff; 237 | } 238 | 239 | .HomeCard:hover { 240 | transform: scale(1.04); 241 | animation: bounce 0.5s ease; 242 | } 243 | 244 | 245 | /* Sidebar */ 246 | .sidebar-toggle { 247 | position: absolute; 248 | top: 15px; 249 | right: 15px; 250 | z-index: 999; 251 | background-color: transparent; 252 | border: none; 253 | cursor: pointer; 254 | } 255 | 256 | .sidebar-toggle i { 257 | color: #ffffff; 258 | } 259 | 260 | .sidebar-toggle:focus { 261 | outline: none; 262 | } 263 | 264 | .sidebar { 265 | transition: width 0.3s ease; 266 | width: 81px; 267 | } 268 | 269 | 270 | .sidebar-open .sidebar { 271 | width: 250px; 272 | } 273 | 274 | /* Sidebar Menu */ 275 | .nav-link { 276 | color: #ffffff; 277 | transition: background-color 0.3s ease, color 0.3s ease; 278 | } 279 | 280 | .nav-link:hover { 281 | background-color: #373b3e; 282 | } 283 | 284 | .nav-link.active { 285 | background-color: #ffffff; 286 | color: #000000; 287 | } 288 | 289 | /* Animation for Logo */ 290 | .animate-charcter { 291 | text-transform: uppercase; 292 | background-image: linear-gradient(-225deg, #4797ffb3 0%, #ae62ff 29%, #ff1361 67%, #fff800 100%); 293 | background-size: auto auto; 294 | background-clip: border-box; 295 | background-size: 200% auto; 296 | color: #fff; 297 | background-clip: text; 298 | text-fill-color: transparent; 299 | -webkit-background-clip: text; 300 | -webkit-text-fill-color: transparent; 301 | animation: textclip 5s linear infinite; 302 | display: inline-block; 303 | font-size: 1.2rem; 304 | font-weight: 900; 305 | font-family: Quicksand; 306 | } 307 | 308 | @keyframes textclip { 309 | to { 310 | background-position: 200% center; 311 | } 312 | } 313 | 314 | 315 | .sidebar-closed .d-none.d-sm-inline { 316 | display: none !important; 317 | } 318 | 319 | .sidebar { 320 | transition: width 0.3s ease; 321 | } 322 | 323 | .nav-link { 324 | display: flex; 325 | align-items: center; 326 | transition: padding 0.3s ease; 327 | } 328 | 329 | .nav-link-logout { 330 | display: flex; 331 | align-items: center; 332 | justify-content: center; 333 | transition: padding 0.3s ease; 334 | overflow: hidden; 335 | } 336 | 337 | .loginForm { 338 | border-top: 5px solid #f93a13; 339 | border-radius: 16px; 340 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 341 | } -------------------------------------------------------------------------------- /Frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Quicksand:wght@300..700&display=swap'); 2 | 3 | 4 | html { 5 | scroll-behavior: smooth; 6 | } 7 | 8 | .inter-custom { 9 | font-family: "Inter", sans-serif; 10 | font-optical-sizing: auto; 11 | font-weight: 400; 12 | font-style: normal; 13 | font-variation-settings: 14 | "slnt" 0; 15 | } 16 | 17 | body { 18 | font-family: 'Inter', sans-serif; 19 | -webkit-font-smoothing: antialiased; 20 | } 21 | 22 | 23 | 24 | .sidebar { 25 | background-color: #1f2d3d; 26 | transition: all 0.3s ease-in-out; 27 | box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); 28 | } 29 | 30 | .sidebar .nav-link { 31 | padding: 15px 20px; 32 | color: #cfd8dc; 33 | transition: all 0.2s; 34 | } 35 | 36 | .sidebar .nav-link:hover, .sidebar .nav-link.active { 37 | background-color: #37474f; 38 | color: #ffffff; 39 | } 40 | 41 | 42 | .card { 43 | border-radius: 8px; 44 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 45 | padding: 20px; 46 | margin: 15px; 47 | } 48 | 49 | 50 | .button { 51 | border-radius: 5px; 52 | padding: 10px 15px; 53 | transition: background-color 0.2s; 54 | } 55 | 56 | .button.edit { 57 | background-color: #007bff; 58 | color: #fff; 59 | } 60 | 61 | .button.edit:hover { 62 | background-color: #0056b3; 63 | } 64 | 65 | .button.delete { 66 | background-color: #dc3545; 67 | color: #fff; 68 | } 69 | 70 | .button.delete:hover { 71 | background-color: #c82333; 72 | } 73 | 74 | 75 | h4 { 76 | font-size: 1.5rem; 77 | font-weight: bold; 78 | margin-bottom: 20px; 79 | } 80 | 81 | .table { 82 | width: 100%; 83 | margin: 20px 0; 84 | border-collapse: collapse; 85 | } 86 | 87 | .table th, .table td { 88 | padding: 10px 15px; 89 | text-align: left; 90 | } 91 | 92 | .table th { 93 | font-weight: 600; 94 | } 95 | 96 | .table-responsive { 97 | overflow-x: auto; 98 | margin: 20px 0; 99 | } 100 | 101 | table { 102 | width: 100%; 103 | border-collapse: collapse; 104 | margin: 0; 105 | padding: 0; 106 | table-layout: fixed; 107 | } 108 | 109 | .table th { 110 | background-color: #bec8d326; 111 | } 112 | 113 | .table th { 114 | padding: 12px 15px; 115 | font-size: 1rem; 116 | font-weight: bold; 117 | text-transform: uppercase; 118 | } 119 | 120 | .table td { 121 | padding: 12px 15px; 122 | border-bottom: 1px solid #efefef; 123 | } 124 | 125 | .table tr:nth-of-type(odd) { 126 | background-color: #f9f9f9; 127 | } 128 | 129 | .table tr:nth-of-type(even) { 130 | background-color: #ffffff; 131 | } 132 | 133 | .table tr:hover { 134 | background-color: #f1f1f1; 135 | } 136 | 137 | @media (max-width: 768px) { 138 | 139 | .table td { 140 | display: block; 141 | text-align: right; 142 | padding: 10px 15px; 143 | position: relative; 144 | } 145 | 146 | .table td::before { 147 | content: attr(data-label); 148 | position: absolute; 149 | left: 0; 150 | width: 50%; 151 | padding-left: 15px; 152 | font-weight: bold; 153 | text-align: left; 154 | } 155 | 156 | .table td:last-child { 157 | border-bottom: 0; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | // index.js or Start.jsx 2 | import React from "react"; 3 | import { createRoot } from "react-dom/client"; // Update the import 4 | import App from "./App.jsx"; 5 | import "./index.css"; 6 | import { ToastContainer } from "react-toastify"; 7 | import "react-toastify/dist/ReactToastify.css"; 8 | 9 | const container = document.getElementById("root"); 10 | const root = createRoot(container); // createRoot from 'react-dom/client' 11 | root.render( 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /Frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | outDir: 'dist' 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WORKSUITE - Advanced Employee & Project Management System 2 | 3 | ## Home Screen 4 | ![Home Screen](Frontend/public/images/github/Home.png) 5 | 6 | ## Login Screen 7 | ![Login Screen](Frontend/public/images/github/Login.png) 8 | 9 | ## Employee Dashboard 10 | ![Employee Dashboard](Frontend/public/images/github/Employee%20Dashboard.jpeg) 11 | 12 | ## Main Dashboard 13 | ![Main Dashboard](Frontend/public/images/github/Dashboard.png) 14 | 15 | ## Admin Interface 16 | ![Admin Interface](Frontend/public/images/github/Admins.png) 17 | 18 | ## Category Management 19 | ![Category Management](Frontend/public/images/github/Category.png) 20 | 21 | ## Employee Management 22 | ![Employee Management](Frontend/public/images/github/Employee.png) 23 | 24 | ## Office Branch Locations 25 | ![Office Management](Frontend/public/images/github/Office.png) 26 | ======= 27 | ![Office Management](Frontend/public/images/github/Office.png) 28 | -------------------------------------------------------------------------------- /Server/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules 2 | node_modules/ 3 | 4 | # Ignore environment variables 5 | .env 6 | 7 | # Ignore build files 8 | dist/ 9 | out/ 10 | build/ 11 | 12 | # Ignore logs 13 | npm-debug.log 14 | yarn-debug.log 15 | yarn-error.log 16 | pnpm-debug.log 17 | 18 | # Ignore dependency lock files (optional) 19 | package-lock.json 20 | yarn.lock 21 | pnpm-lock.yaml 22 | 23 | # Ignore VS Code settings 24 | .vscode/ 25 | 26 | # Ignore macOS system files 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /Server/Routes/AdminRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | import bcrypt from "bcrypt"; 4 | import jwt from "jsonwebtoken"; 5 | import dotenv from "dotenv"; 6 | import multer from "multer"; 7 | import path from "path"; 8 | import fs from "fs"; 9 | 10 | 11 | dotenv.config(); 12 | 13 | const router = express.Router(); 14 | 15 | //Router for Login Form 16 | router.post("/adminlogin", async (req, res) => { 17 | const { email, password } = req.body; 18 | 19 | try { 20 | const result = await db.query("SELECT * FROM admin WHERE email = $1", [ 21 | email, 22 | ]); 23 | 24 | if (result.rows.length > 0) { 25 | const user = result.rows[0]; 26 | const storedHashedPassword = user.password; 27 | 28 | const passwordsMatch = await bcrypt.compare( 29 | password, 30 | storedHashedPassword 31 | ); 32 | if (passwordsMatch) { 33 | const token = jwt.sign( 34 | { role: "admin", email: user.email, id: user.id }, 35 | process.env.JWT_SECRET, 36 | { expiresIn: "1d" } 37 | ); 38 | 39 | res.cookie("jwt", token, { 40 | httpOnly: true, 41 | maxAge: 3600000, 42 | secure: true, 43 | }); 44 | 45 | return res 46 | .status(200) 47 | .json({ loginStatus: true, message: "You are logged in" }); 48 | } else { 49 | return res 50 | .status(401) 51 | .json({ loginStatus: false, error: "Incorrect Email or Password" }); 52 | } 53 | } else { 54 | return res.status(404).json({ error: "User not found" }); 55 | } 56 | } catch (err) { 57 | console.error("Error:", err); 58 | return res.status(500).json({ error: "Internal Server Error" }); 59 | } 60 | }); 61 | router.get("/category", async (req, res) => { 62 | try { 63 | const showCategory = await db.query("SELECT * FROM category"); 64 | res.json({ success: true, categories: showCategory.rows }); 65 | } catch (error) { 66 | console.error("Error displaying category:", error); 67 | res.json({ success: false, message: "Failed to load category" }); 68 | } 69 | }); 70 | 71 | router.post("/add_category", async (req, res) => { 72 | const { name } = req.body; 73 | 74 | try { 75 | const addCategory = await db.query( 76 | "INSERT INTO category (name) VALUES ($1)", 77 | [name] 78 | ); 79 | res 80 | .status(200) 81 | .json({ success: true, message: "Category added successfully" }); 82 | } catch (error) { 83 | console.error("Error adding category:", error); 84 | res.status(500).json({ success: false, message: "Failed to add category" }); 85 | } 86 | }); 87 | 88 | 89 | // AddEmployee 90 | const storage = multer.diskStorage({ 91 | destination: (req, file, cb) => { 92 | cb(null, "public/images"); 93 | }, 94 | filename: (req, file, cb) => { 95 | cb( 96 | null, 97 | file.fieldname + "_" + Date.now() + path.extname(file.originalname) 98 | ); 99 | }, 100 | }); 101 | const upload = multer({ storage: storage }); 102 | 103 | // Route to add employee 104 | router.post("/add_employee", upload.single("image"), async (req, res) => { 105 | try { 106 | // Hash the password 107 | const hashedPassword = await bcrypt.hash(req.body.password, 10); 108 | 109 | // Insert employee into database 110 | const addEmployee = await db.query( 111 | "INSERT INTO employee (name, email, password, address, salary, image, category_id) VALUES ($1, $2, $3, $4, $5, $6, $7)", 112 | [ 113 | req.body.name, 114 | req.body.email, 115 | hashedPassword, 116 | req.body.address, 117 | req.body.salary, 118 | req.file.filename, 119 | req.body.category_id, 120 | ] 121 | ); 122 | 123 | // Send success response 124 | res 125 | .status(200) 126 | .json({ success: true, message: "Employee added successfully" }); 127 | } catch (error) { 128 | // Log error and send error response 129 | console.error("Error adding employee:", error); 130 | res.status(500).json({ success: false, message: "Failed to add employee" }); 131 | } 132 | }); 133 | 134 | 135 | router.get("/employee", async (req, res) => { 136 | try { 137 | const getEmployees = await db.query("SELECT * FROM employee"); 138 | res.json({ success: true, Result: getEmployees.rows }); 139 | } catch (error) { 140 | console.error("Error fetching employees:", error); 141 | res.json({ success: false, message: "Failed to fetch employees" }); 142 | } 143 | }); 144 | 145 | // Router to delete an employee 146 | router.delete("/delete_employee/:id", async (req, res) => { 147 | const { id } = req.params; 148 | 149 | try { 150 | // Get the image filename of the employee to be deleted 151 | const getImageFilenameQuery = await db.query("SELECT image FROM employee WHERE id = $1", [ 152 | id, 153 | ]); 154 | const imageFilename = getImageFilenameQuery.rows[0].image; 155 | 156 | // Delete the employee record from the database 157 | const deleteEmployee = await db.query("DELETE FROM employee WHERE id = $1", [ 158 | id, 159 | ]); 160 | 161 | // Delete the image file from the server's file system 162 | if (imageFilename) { 163 | fs.unlinkSync(`public/images/${imageFilename}`); 164 | } 165 | 166 | res.status(200).json({ success: true, message: "Employee deleted" }); 167 | } catch (error) { 168 | console.error("Error deleting employee:", error); 169 | res.status(500).json({ success: false, message: "Failed to delete employee" }); 170 | } 171 | }); 172 | 173 | 174 | 175 | // get category 176 | router.get("/category", async (req, res) => { 177 | try { 178 | const showCategory = await db.query("SELECT * FROM category"); 179 | res.json({ success: true, categories: showCategory.rows }); 180 | } catch (error) { 181 | console.error("Error displaying category:", error); 182 | res.json({ success: false, message: "Failed to load category" }); 183 | } 184 | }); 185 | 186 | 187 | // Edit Employee 188 | router.get("/employee/:id", async (req, res) => { 189 | const id = req.params.id; 190 | try { 191 | const getEmployee = await db.query("SELECT * FROM employee WHERE id = $1", [id]); 192 | res.json({ success: true, Result: getEmployee.rows }); 193 | } catch (error) { 194 | console.error("Error fetching employee:", error); 195 | res.json({ success: false, message: "Failed to fetch employee" }); 196 | } 197 | }); 198 | 199 | router.put("/edit_employee/:id", async (req, res) => { 200 | const id = req.params.id; 201 | const { name, email, salary, address, category_id } = req.body; 202 | try { 203 | const updateEmployee = await db.query( 204 | "UPDATE employee SET name = $1, email = $2, salary = $3, address = $4, category_id = $5 WHERE id = $6", 205 | [name, email, salary, address, category_id, id] 206 | ); 207 | res.json({ success: true, message: "Employee updated successfully" }); 208 | } catch (error) { 209 | console.error("Error updating employee:", error); 210 | res.status(500).json({ success: false, message: "Failed to update employee" }); 211 | } 212 | }); 213 | 214 | //Home 215 | 216 | router.get('/admin_count', async (req, res) => { 217 | try { 218 | const adminCountQuery = await db.query("SELECT COUNT(id) AS admin FROM admin"); 219 | res.json({ Status: true, Result: adminCountQuery.rows }); 220 | } catch (error) { 221 | console.error("Error fetching admin count:", error); 222 | res.json({ Status: false, Error: "Failed to fetch admin count" }); 223 | } 224 | }); 225 | 226 | router.get('/employee_count', async (req, res) => { 227 | try { 228 | const employeeCountQuery = await db.query("SELECT COUNT(id) AS employee FROM employee"); 229 | res.json({ Status: true, Result: employeeCountQuery.rows }); 230 | } catch (error) { 231 | console.error("Error fetching employee count:", error); 232 | res.json({ Status: false, Error: "Failed to fetch employee count" }); 233 | } 234 | }); 235 | 236 | router.get('/salary_count', async (req, res) => { 237 | try { 238 | const salaryCountQuery = await db.query("SELECT SUM(salary) AS salaryOFEmp FROM employee"); 239 | res.json({ Status: true, Result: salaryCountQuery.rows }); 240 | } catch (error) { 241 | console.error("Error fetching salary count:", error); 242 | res.json({ Status: false, Error: "Failed to fetch salary count" }); 243 | } 244 | }); 245 | 246 | router.get('/admin_records', async (req, res) => { 247 | try { 248 | const adminRecordsQuery = await db.query("SELECT * FROM admin"); 249 | res.json({ Status: true, Result: adminRecordsQuery.rows }); 250 | } catch (error) { 251 | console.error("Error fetching admin records:", error); 252 | res.json({ Status: false, Error: "Failed to fetch admin records" }); 253 | } 254 | }); 255 | 256 | 257 | 258 | //logOut 259 | router.get('/logout', (req, res) => { 260 | res.clearCookie('jwt'); 261 | return res.json({ Status: true }); 262 | }); 263 | 264 | //Edit employee 265 | router.put("/edit_admin/:id", async (req, res) => { 266 | const id = req.params.id; 267 | const { email } = req.body; 268 | try { 269 | const updateAdmin = await db.query( 270 | "UPDATE admin SET email = $1 WHERE id = $2", 271 | [email, id] 272 | ); 273 | res.json({ success: true, message: "Admin updated successfully" }); 274 | } catch (error) { 275 | console.error("Error updating admin:", error); 276 | res.status(500).json({ success: false, message: "Failed to update admin" }); 277 | } 278 | }); 279 | 280 | // Delete Admin 281 | router.delete("/delete_admin/:id", async (req, res) => { 282 | const { id } = req.params; 283 | try { 284 | const deleteAdmin = await db.query("DELETE FROM admin WHERE id = $1", [id]); 285 | res.status(200).json({ success: true, message: "Admin deleted" }); 286 | } catch (error) { 287 | console.error("Error deleting admin:", error); 288 | res.status(500).json({ success: false, message: "Failed to delete admin" }); 289 | } 290 | }); 291 | 292 | router.post("/add_admin", async (req, res) => { 293 | const { email, password } = req.body; 294 | 295 | try { 296 | // Hash the password 297 | const hashedPassword = await bcrypt.hash(password, 10); 298 | 299 | // Insert admin into database 300 | const addAdmin = await db.query( 301 | "INSERT INTO admin (email, password) VALUES ($1, $2)", 302 | [email, hashedPassword] 303 | ); 304 | 305 | res.status(200).json({ success: true, message: "Admin added successfully" }); 306 | } catch (error) { 307 | console.error("Error adding admin:", error); 308 | res.status(500).json({ success: false, message: "Failed to add admin" }); 309 | } 310 | }); 311 | 312 | 313 | export { router as adminRouter }; 314 | -------------------------------------------------------------------------------- /Server/Routes/AttendanceRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/", async (req, res) => { 7 | try { 8 | const today = new Date().toISOString().split("T")[0]; // Get today's date in YYYY-MM-DD format 9 | const result = await db.query( 10 | `SELECT 11 | COUNT(*) FILTER (WHERE clock_in::date = $1) AS present, 12 | (SELECT COUNT(*) FROM employee) - COUNT(*) FILTER (WHERE clock_in::date = $1) AS absent 13 | FROM clock_records 14 | WHERE clock_in::date = $1`, 15 | [today] 16 | ); 17 | res.status(200).json({ success: true, attendance: result.rows[0] }); 18 | } catch (error) { 19 | console.error("Error fetching attendance data:", error); 20 | res.status(500).json({ success: false, message: "Server Error" }); 21 | } 22 | }); 23 | 24 | export { router as attendanceRouter }; 25 | -------------------------------------------------------------------------------- /Server/Routes/ClientsRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | 4 | const router = express.Router(); 5 | 6 | // GET /clients - Fetch all clients 7 | router.get("/", async (req, res) => { 8 | try { 9 | const result = await db.query( 10 | "SELECT * FROM clients ORDER BY client_id ASC" 11 | ); 12 | res.status(200).json({ success: true, clients: result.rows }); 13 | } catch (error) { 14 | console.error("Error fetching clients:", error); 15 | res.status(500).json({ success: false, message: "Internal Server Error" }); 16 | } 17 | }); 18 | 19 | // POST /clients - Add a new client 20 | router.post("/", async (req, res) => { 21 | const { name, contact_person, email, phone, address } = req.body; 22 | 23 | if (!name || !email) { 24 | return res.status(400).json({ 25 | success: false, 26 | message: "Name and email are required", 27 | }); 28 | } 29 | 30 | try { 31 | const result = await db.query( 32 | "INSERT INTO clients (name, contact_person, email, phone, address, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) RETURNING *", 33 | [name, contact_person, email, phone, address] 34 | ); 35 | res.status(201).json({ success: true, client: result.rows[0] }); 36 | } catch (error) { 37 | console.error("Error adding client:", error); 38 | res.status(500).json({ success: false, message: "Internal Server Error" }); 39 | } 40 | }); 41 | 42 | // DELETE /clients/:clientId - Delete a client 43 | router.delete("/:clientId", async (req, res) => { 44 | const { clientId } = req.params; 45 | 46 | try { 47 | const result = await db.query( 48 | "DELETE FROM clients WHERE client_id = $1 RETURNING *", 49 | [clientId] 50 | ); 51 | if (result.rows.length === 0) { 52 | return res 53 | .status(404) 54 | .json({ success: false, message: "Client not found" }); 55 | } 56 | res 57 | .status(200) 58 | .json({ success: true, message: "Client deleted successfully" }); 59 | } catch (error) { 60 | console.error("Error deleting client:", error); 61 | res.status(500).json({ success: false, message: "Internal Server Error" }); 62 | } 63 | }); 64 | 65 | export { router as clientsRouter }; 66 | -------------------------------------------------------------------------------- /Server/Routes/EmployeeRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | import bcrypt from "bcrypt"; 4 | import jwt from "jsonwebtoken"; 5 | 6 | const router = express.Router(); 7 | 8 | //Router for Login Form 9 | router.post("/employeelogin", async (req, res) => { 10 | const { email, password } = req.body; 11 | 12 | try { 13 | const result = await db.query("SELECT * FROM employee WHERE email = $1", [ 14 | email, 15 | ]); 16 | 17 | if (result.rows.length > 0) { 18 | const user = result.rows[0]; 19 | const storedHashedPassword = user.password; 20 | 21 | const passwordsMatch = await bcrypt.compare( 22 | password, 23 | storedHashedPassword 24 | ); 25 | if (passwordsMatch) { 26 | const token = jwt.sign( 27 | { role: "employee", email: user.email, id: user.id }, 28 | process.env.JWT_SECRET, 29 | { expiresIn: "1d" } 30 | ); 31 | 32 | // Set JWT token as a cookie 33 | res.cookie("jwt", token, { 34 | httpOnly: true, 35 | maxAge: 3600000, 36 | secure: true, 37 | }); 38 | 39 | // Send success response 40 | return res.status(200).json({ 41 | loginStatus: true, 42 | message: "You are logged in", 43 | id: user.id, 44 | }); // Access the user's ID property 45 | } else { 46 | // Send response for incorrect password 47 | return res 48 | .status(401) 49 | .json({ loginStatus: false, error: "Incorrect Email or Password" }); 50 | } 51 | } else { 52 | // Send response for user not found 53 | return res.status(404).json({ error: "User not found" }); 54 | } 55 | } catch (err) { 56 | // Send response for internal server error 57 | console.error("Error:", err); 58 | return res.status(500).json({ error: "Internal Server Error" }); 59 | } 60 | }); 61 | 62 | router.get("/detail/:id", async (req, res) => { 63 | const id = req.params.id; 64 | try { 65 | const getEmployee = await db.query("SELECT * FROM employee WHERE id = $1", [ 66 | id, 67 | ]); 68 | res.json({ success: true, Result: getEmployee.rows }); 69 | } catch (error) { 70 | console.error("Error fetching employee:", error); 71 | res.json({ success: false, message: "Failed to fetch employee" }); 72 | } 73 | }); 74 | 75 | router.get("/logout", (req, res) => { 76 | res.clearCookie("jwt"); 77 | return res.json({ Status: true }); 78 | }); 79 | 80 | // Route to check if employee is currently clocked in 81 | router.get("/employee_is_clocked_in/:id", async (req, res) => { 82 | const { id } = req.params; // Extract employee ID from URL parameters 83 | 84 | try { 85 | // Check if there is a clock-in record without a corresponding clock-out time 86 | const result = await db.query( 87 | "SELECT * FROM clock_records WHERE employee_id = $1 AND clock_out IS NULL", 88 | [id] 89 | ); 90 | 91 | // Send success response with clock-in status 92 | return res.status(200).json({ clockedIn: result.rows.length > 0 }); 93 | } catch (error) { 94 | console.error("Error while checking clock-in status:", error); 95 | return res 96 | .status(500) 97 | .json({ success: false, message: "Internal Server Error" }); 98 | } 99 | }); 100 | 101 | // Route to handle employee clock-in 102 | router.post("/employee_clockin/:id", async (req, res) => { 103 | const { id } = req.params; // Extract employee ID from URL parameters 104 | const { location, work_from_type } = req.body; 105 | 106 | try { 107 | // Insert clock-in record into the database 108 | await db.query( 109 | "INSERT INTO clock_records (employee_id, clock_in, location, work_from_type) VALUES ($1, NOW(), $2, $3)", 110 | [id, location, work_from_type] 111 | ); 112 | 113 | // Send success response 114 | return res.status(200).json({ status: "success" }); 115 | } catch (error) { 116 | console.error("Error while clocking in:", error); 117 | return res 118 | .status(500) 119 | .json({ status: "error", message: "Internal Server Error" }); 120 | } 121 | }); 122 | 123 | // Route to handle employee clock-out 124 | router.post("/employee_clockout/:id", async (req, res) => { 125 | const { id } = req.params; // Extract employee ID from URL parameters 126 | 127 | try { 128 | // Update the clock-out time for the employee 129 | await db.query( 130 | "UPDATE clock_records SET clock_out = NOW() WHERE employee_id = $1 AND clock_out IS NULL", 131 | [id] 132 | ); 133 | 134 | // Send success response 135 | return res.status(200).json({ success: true }); 136 | } catch (error) { 137 | console.error("Error while clocking out:", error); 138 | return res 139 | .status(500) 140 | .json({ success: false, message: "Internal Server Error" }); 141 | } 142 | }); 143 | 144 | // Route to fetch calendar data for a specific employee 145 | router.get("/calendar/:employeeId", async (req, res) => { 146 | const { employeeId } = req.params; 147 | 148 | try { 149 | // Fetch clock records for the employee from the database 150 | const result = await db.query( 151 | "SELECT * FROM clock_records WHERE employee_id = $1", 152 | [employeeId] 153 | ); 154 | 155 | // Process the result and format the data as needed 156 | const calendarData = result.rows.map((row) => { 157 | // Extract date from timestamp and format it as 'YYYY-MM-DD' 158 | const date = row.clock_in.toISOString().slice(0, 10); 159 | // Get day name from the date 160 | const dayName = new Date(row.clock_in).toLocaleDateString("en-US", { 161 | weekday: "long", 162 | }); 163 | 164 | return { 165 | date: date, 166 | dayName: dayName, 167 | clockIn: row.clock_in, 168 | clockOut: row.clock_out, 169 | location: row.location, 170 | workFromType: row.work_from_type, 171 | }; 172 | }); 173 | 174 | // Send success response with formatted calendar data 175 | res.status(200).json({ success: true, calendarData }); 176 | } catch (error) { 177 | console.error("Error fetching calendar data:", error); 178 | res.status(500).json({ success: false, message: "Internal Server Error" }); 179 | } 180 | }); 181 | 182 | // Define a route to get category by ID 183 | router.get("/category/:id", async (req, res) => { 184 | const categoryId = req.params.id; 185 | 186 | try { 187 | const category = await db.query("SELECT * FROM category WHERE id = $1", [ 188 | categoryId, 189 | ]); 190 | 191 | if (category.rows.length === 0) { 192 | return res 193 | .status(404) 194 | .json({ success: false, error: "Category not found" }); 195 | } 196 | res.status(200).json({ success: true, category: category.rows[0] }); 197 | } catch (error) { 198 | console.error("Error fetching category:", error); 199 | res.status(500).json({ success: false, error: "Internal Server Error" }); 200 | } 201 | }); 202 | 203 | // Route to get office location data 204 | router.get("/office_location", async (req, res) => { 205 | try { 206 | const result = await db.query("SELECT * FROM office_locations"); 207 | res.status(200).json({ success: true, officeLocations: result.rows }); 208 | } catch (error) { 209 | console.error("Error fetching office locations:", error); 210 | res.status(500).json({ success: false, error: "Internal Server Error" }); 211 | } 212 | }); 213 | 214 | // Route to add a new office location 215 | router.post("/office_location", async (req, res) => { 216 | const { name, latitude, longitude, address } = req.body; 217 | 218 | try { 219 | const result = await db.query( 220 | "INSERT INTO office_locations (name, latitude, longitude, address) VALUES ($1, $2, $3, $4) RETURNING *", 221 | [name, latitude, longitude, address] 222 | ); 223 | 224 | res.status(201).json({ success: true, officeLocation: result.rows[0] }); 225 | } catch (error) { 226 | console.error("Error adding office location:", error); 227 | res.status(500).json({ success: false, error: "Internal Server Error" }); 228 | } 229 | }); 230 | 231 | // Route to delete an office location by ID 232 | router.delete("/office_location/:id", async (req, res) => { 233 | const id = req.params.id; 234 | 235 | try { 236 | await db.query("DELETE FROM office_locations WHERE id = $1", [id]); 237 | res 238 | .status(200) 239 | .json({ success: true, message: "Office location deleted successfully" }); 240 | } catch (error) { 241 | console.error("Error deleting office location:", error); 242 | res.status(500).json({ success: false, error: "Internal Server Error" }); 243 | } 244 | }); 245 | 246 | // Route to fetch all employees 247 | router.get("/employee/list", async (req, res) => { 248 | try { 249 | // Fetch all employees from the database 250 | const result = await db.query("SELECT id, name, role FROM employee"); 251 | 252 | // Send the response with the list of employees 253 | res.status(200).json({ success: true, employees: result.rows }); 254 | } catch (error) { 255 | console.error("Error fetching employees:", error); 256 | res.status(500).json({ success: false, message: "Internal Server Error" }); 257 | } 258 | }); 259 | 260 | export { router as employeeRouter }; 261 | -------------------------------------------------------------------------------- /Server/Routes/NotificationsRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | 4 | const router = express.Router(); 5 | 6 | // Fetch notifications 7 | router.get("/", async (req, res) => { 8 | try { 9 | const result = await db.query( 10 | "SELECT * FROM notifications ORDER BY created_at DESC" 11 | ); 12 | res.status(200).json({ success: true, notifications: result.rows }); 13 | } catch (error) { 14 | console.error("Error fetching notifications:", error); 15 | res.status(500).json({ success: false, message: "Server Error" }); 16 | } 17 | }); 18 | 19 | // Create a new notification 20 | router.post("/", async (req, res) => { 21 | const { message } = req.body; 22 | 23 | try { 24 | const result = await db.query( 25 | "INSERT INTO notifications (message, created_at) VALUES ($1, NOW()) RETURNING *", 26 | [message] 27 | ); 28 | res.status(201).json({ success: true, notification: result.rows[0] }); 29 | } catch (error) { 30 | console.error("Error creating notification:", error); 31 | res.status(500).json({ success: false, message: "Server Error" }); 32 | } 33 | }); 34 | 35 | export { router as notificationRouter }; 36 | -------------------------------------------------------------------------------- /Server/Routes/ProjectRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | 4 | const router = express.Router(); 5 | 6 | /** 7 | * GET /projects/ongoing 8 | * Retrieve projects created within the last week. 9 | */ 10 | router.get("/ongoing", async (req, res) => { 11 | try { 12 | const oneWeekAgo = new Date(); 13 | oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); 14 | const result = await db.query( 15 | "SELECT * FROM projects WHERE created_at >= $1 ORDER BY start_date ASC", 16 | [oneWeekAgo] 17 | ); 18 | res.status(200).json({ success: true, projects: result.rows }); 19 | } catch (error) { 20 | console.error("Error fetching ongoing projects:", error); 21 | res.status(500).json({ success: false, message: "Server Error" }); 22 | } 23 | }); 24 | 25 | /** 26 | * GET /projects 27 | * Retrieve all projects. 28 | */ 29 | router.get("/", async (req, res) => { 30 | try { 31 | const result = await db.query( 32 | "SELECT * FROM projects ORDER BY project_id ASC" 33 | ); 34 | res.status(200).json({ success: true, projects: result.rows }); 35 | } catch (error) { 36 | console.error("Error fetching projects:", error); 37 | res.status(500).json({ success: false, message: "Server Error" }); 38 | } 39 | }); 40 | 41 | /** 42 | * GET /projects/:id 43 | * Retrieve a single project by ID. 44 | */ 45 | router.get("/:id", async (req, res) => { 46 | const { id } = req.params; 47 | try { 48 | const result = await db.query( 49 | "SELECT * FROM projects WHERE project_id = $1", 50 | [id] 51 | ); 52 | if (result.rows.length === 0) { 53 | return res 54 | .status(404) 55 | .json({ success: false, message: "Project not found" }); 56 | } 57 | res.status(200).json({ success: true, project: result.rows[0] }); 58 | } catch (error) { 59 | console.error("Error fetching project:", error); 60 | res.status(500).json({ success: false, message: "Server Error" }); 61 | } 62 | }); 63 | 64 | /** 65 | * POST /projects 66 | * Create a new project. 67 | * Example body: { "title": "New Project", "description": "Some details", "status": "Not Started", "completion_date": "2025-12-31", "start_date": "2025-01-01", "priority": "Medium", "client_id": 1 } 68 | */ 69 | router.post("/", async (req, res) => { 70 | const { 71 | title, 72 | description, 73 | status, 74 | completion_date, 75 | start_date, 76 | priority, 77 | client_id, 78 | } = req.body; 79 | 80 | try { 81 | const insertQuery = ` 82 | INSERT INTO projects (title, description, status, completion_date, start_date, priority, client_id, created_by) 83 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 84 | RETURNING *; 85 | `; 86 | const { rows } = await db.query(insertQuery, [ 87 | title, 88 | description, 89 | status, 90 | completion_date, 91 | start_date, 92 | priority, 93 | client_id, 94 | 1, 95 | ]); // Assuming admin ID is 1 96 | res.status(201).json({ success: true, project: rows[0] }); 97 | } catch (error) { 98 | console.error("Error creating project:", error); 99 | res.status(500).json({ success: false, message: "Server Error" }); 100 | } 101 | }); 102 | 103 | /** 104 | * PUT /projects/:id 105 | * Update a project by ID. 106 | * Example body: { "title": "Updated Project", "description": "Updated details", "status": "In Progress", "completion_date": "2025-12-31", "start_date": "2025-01-01", "priority": "High", "client_id": 1 } 107 | */ 108 | router.put("/:id", async (req, res) => { 109 | const { id } = req.params; 110 | const { 111 | title, 112 | description, 113 | status, 114 | completion_date, 115 | start_date, 116 | priority, 117 | client_id, 118 | } = req.body; 119 | 120 | try { 121 | const updateQuery = ` 122 | UPDATE projects 123 | SET title = $1, 124 | description = $2, 125 | status = $3, 126 | completion_date = $4, 127 | start_date = $5, 128 | priority = $6, 129 | client_id = $7, 130 | updated_at = NOW() 131 | WHERE project_id = $8 132 | RETURNING *; 133 | `; 134 | const { rows } = await db.query(updateQuery, [ 135 | title, 136 | description, 137 | status, 138 | completion_date, 139 | start_date, 140 | priority, 141 | client_id, 142 | id, 143 | ]); 144 | if (rows.length === 0) { 145 | return res 146 | .status(404) 147 | .json({ success: false, message: "Project not found" }); 148 | } 149 | res.status(200).json({ success: true, project: rows[0] }); 150 | } catch (error) { 151 | console.error("Error updating project:", error); 152 | res.status(500).json({ success: false, message: "Server Error" }); 153 | } 154 | }); 155 | 156 | /** 157 | * DELETE /projects/:id 158 | * Delete a project by ID. 159 | */ 160 | router.delete("/:id", async (req, res) => { 161 | const { id } = req.params; 162 | 163 | try { 164 | const deleteQuery = ` 165 | DELETE FROM projects 166 | WHERE project_id = $1 167 | RETURNING *; 168 | `; 169 | const { rows } = await db.query(deleteQuery, [id]); 170 | if (rows.length === 0) { 171 | return res 172 | .status(404) 173 | .json({ success: false, message: "Project not found" }); 174 | } 175 | res 176 | .status(200) 177 | .json({ success: true, message: "Project deleted successfully" }); 178 | } catch (error) { 179 | console.error("Error deleting project:", error); 180 | res.status(500).json({ success: false, message: "Server Error" }); 181 | } 182 | }); 183 | 184 | export { router as projectRouter }; 185 | -------------------------------------------------------------------------------- /Server/Routes/TaskRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | import { io } from "../index.js"; 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * PUT /tasks/:taskId 9 | * Update a task. 10 | */ 11 | router.put("/:taskId", async (req, res) => { 12 | const { taskId } = req.params; 13 | const { description, deadline, status, employee_ids, project_id } = req.body; 14 | 15 | // Validate required fields 16 | if (!description || !deadline || !status || !project_id) { 17 | return res.status(400).json({ 18 | success: false, 19 | message: "Description, deadline, status, and project ID are required", 20 | }); 21 | } 22 | 23 | try { 24 | // 1. Fetch the task from the database 25 | const taskResult = await db.query( 26 | "SELECT * FROM tasks WHERE task_id = $1", 27 | [taskId] 28 | ); 29 | 30 | if (taskResult.rows.length === 0) { 31 | return res 32 | .status(404) 33 | .json({ success: false, message: "Task not found" }); 34 | } 35 | 36 | // 2. Perform the update operation on the task 37 | const updateQuery = ` 38 | UPDATE tasks 39 | SET description = $1, 40 | deadline = $2, 41 | status = $3, 42 | project_id = $4, 43 | updated_at = NOW() 44 | WHERE task_id = $5 45 | RETURNING *; 46 | `; 47 | const { rows } = await db.query(updateQuery, [ 48 | description, 49 | deadline, 50 | status, 51 | project_id, 52 | taskId, 53 | ]); 54 | const updatedTask = rows[0]; 55 | 56 | // 3. Update task assignments 57 | await db.query("DELETE FROM task_assignments WHERE task_id = $1", [taskId]); 58 | const assignmentPromises = employee_ids.map((employee_id) => 59 | db.query( 60 | "INSERT INTO task_assignments (task_id, employee_id) VALUES ($1, $2)", 61 | [taskId, employee_id] 62 | ) 63 | ); 64 | await Promise.all(assignmentPromises); 65 | 66 | // 4. Real-time notifications after task update 67 | const assignedRows = await db.query( 68 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 69 | [taskId] 70 | ); 71 | 72 | // Notify all assigned employees 73 | assignedRows.rows.forEach((row) => { 74 | io.to(`user_${row.employee_id}`).emit("taskUpdated", { 75 | taskId, 76 | status, 77 | message: `Task #${taskId} has been updated`, 78 | }); 79 | }); 80 | 81 | // 5. Fetch assigned employees for this task 82 | const assignmentQuery = ` 83 | SELECT ta.employee_id, e.name AS employee_name 84 | FROM task_assignments ta 85 | JOIN employee e ON ta.employee_id = e.id 86 | WHERE ta.task_id = $1 87 | `; 88 | const assignmentResult = await db.query(assignmentQuery, [taskId]); 89 | 90 | // Build arrays of IDs and names 91 | const assignedEmployeeIds = assignmentResult.rows.map( 92 | (row) => row.employee_id 93 | ); 94 | const assignedEmployeeNames = assignmentResult.rows.map( 95 | (row) => row.employee_name 96 | ); 97 | 98 | // Attach them to updatedTask 99 | updatedTask.employee_ids = assignedEmployeeIds; 100 | updatedTask.employee_names = assignedEmployeeNames; 101 | 102 | return res.status(200).json({ success: true, task: updatedTask }); 103 | } catch (error) { 104 | console.error("Error updating task:", error); 105 | return res.status(500).json({ success: false, message: "Server Error" }); 106 | } 107 | }); 108 | 109 | /** 110 | * POST /tasks 111 | * Create a new task. 112 | */ 113 | router.post("/", async (req, res) => { 114 | const { description, deadline, status, employee_ids, project_id } = req.body; 115 | 116 | // Validate required fields 117 | if (!description || !deadline || !status || !project_id) { 118 | return res.status(400).json({ 119 | success: false, 120 | message: "Description, deadline, status, and project ID are required", 121 | }); 122 | } 123 | 124 | try { 125 | // 1. Insert the new task into the database 126 | const insertQuery = ` 127 | INSERT INTO tasks (description, deadline, status, project_id, created_at, updated_at) 128 | VALUES ($1, $2, $3, $4, NOW(), NOW()) 129 | RETURNING *; 130 | `; 131 | const { rows } = await db.query(insertQuery, [ 132 | description, 133 | deadline, 134 | status, 135 | project_id, 136 | ]); 137 | const newTask = rows[0]; 138 | 139 | // 2. Insert task assignments 140 | const assignmentPromises = employee_ids.map((employee_id) => 141 | db.query( 142 | "INSERT INTO task_assignments (task_id, employee_id) VALUES ($1, $2)", 143 | [newTask.task_id, employee_id] 144 | ) 145 | ); 146 | await Promise.all(assignmentPromises); 147 | 148 | // 3. Real-time notifications after task creation 149 | const assignedRows = await db.query( 150 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 151 | [newTask.task_id] 152 | ); 153 | 154 | // Notify all assigned employees 155 | assignedRows.rows.forEach((row) => { 156 | io.to(`user_${row.employee_id}`).emit("taskAssigned", { 157 | taskId: newTask.task_id, 158 | status, 159 | message: `Task #${newTask.task_id} has been assigned to you`, 160 | }); 161 | }); 162 | 163 | // 4. Fetch assigned employees for this task 164 | const assignmentQuery = ` 165 | SELECT ta.employee_id, e.name AS employee_name 166 | FROM task_assignments ta 167 | JOIN employee e ON ta.employee_id = e.id 168 | WHERE ta.task_id = $1 169 | `; 170 | const assignmentResult = await db.query(assignmentQuery, [newTask.task_id]); 171 | 172 | // Build arrays of IDs and names 173 | const assignedEmployeeIds = assignmentResult.rows.map( 174 | (row) => row.employee_id 175 | ); 176 | const assignedEmployeeNames = assignmentResult.rows.map( 177 | (row) => row.employee_name 178 | ); 179 | 180 | // Attach them to newTask 181 | newTask.employee_ids = assignedEmployeeIds; 182 | newTask.employee_names = assignedEmployeeNames; 183 | 184 | return res.status(201).json({ success: true, task: newTask }); 185 | } catch (error) { 186 | console.error("Error creating task:", error); 187 | return res.status(500).json({ success: false, message: "Server Error" }); 188 | } 189 | }); 190 | 191 | /** 192 | * DELETE /tasks/:taskId 193 | * Delete a task. 194 | */ 195 | router.delete("/:taskId", async (req, res) => { 196 | const { taskId } = req.params; 197 | 198 | try { 199 | const deleteQuery = ` 200 | DELETE FROM tasks 201 | WHERE task_id = $1 202 | RETURNING *; 203 | `; 204 | const { rows } = await db.query(deleteQuery, [taskId]); 205 | if (rows.length === 0) { 206 | return res 207 | .status(404) 208 | .json({ success: false, message: "Task not found" }); 209 | } 210 | 211 | // Notify all assigned employees about task deletion 212 | const assignedRows = await db.query( 213 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 214 | [taskId] 215 | ); 216 | 217 | assignedRows.rows.forEach((row) => { 218 | io.to(`user_${row.employee_id}`).emit("taskDeleted", { 219 | taskId, 220 | message: `Task #${taskId} has been deleted`, 221 | }); 222 | }); 223 | 224 | return res 225 | .status(200) 226 | .json({ success: true, message: "Task deleted successfully" }); 227 | } catch (error) { 228 | console.error("Error deleting task:", error); 229 | return res.status(500).json({ success: false, message: "Server Error" }); 230 | } 231 | }); 232 | 233 | /** 234 | * PATCH /tasks/:taskId/reassign 235 | * Reassign a task to different employees. 236 | */ 237 | router.patch("/:taskId/reassign", async (req, res) => { 238 | const { taskId } = req.params; 239 | const { employee_ids } = req.body; 240 | 241 | if (!employee_ids || employee_ids.length === 0) { 242 | return res.status(400).json({ 243 | success: false, 244 | message: "Employee IDs are required for reassignment", 245 | }); 246 | } 247 | 248 | try { 249 | // Update task assignments 250 | await db.query("DELETE FROM task_assignments WHERE task_id = $1", [taskId]); 251 | const assignmentPromises = employee_ids.map((employee_id) => 252 | db.query( 253 | "INSERT INTO task_assignments (task_id, employee_id) VALUES ($1, $2)", 254 | [taskId, employee_id] 255 | ) 256 | ); 257 | await Promise.all(assignmentPromises); 258 | 259 | // Real-time notifications after task reassignment 260 | const assignedRows = await db.query( 261 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 262 | [taskId] 263 | ); 264 | 265 | // Notify all assigned employees 266 | assignedRows.rows.forEach((row) => { 267 | io.to(`user_${row.employee_id}`).emit("taskReassigned", { 268 | taskId, 269 | message: `Task #${taskId} has been reassigned`, 270 | }); 271 | }); 272 | 273 | // Fetch assigned employees for this task 274 | const assignmentQuery = ` 275 | SELECT ta.employee_id, e.name AS employee_name 276 | FROM task_assignments ta 277 | JOIN employee e ON ta.employee_id = e.id 278 | WHERE ta.task_id = $1 279 | `; 280 | const assignmentResult = await db.query(assignmentQuery, [taskId]); 281 | 282 | // Build arrays of IDs and names 283 | const assignedEmployeeIds = assignmentResult.rows.map( 284 | (row) => row.employee_id 285 | ); 286 | const assignedEmployeeNames = assignmentResult.rows.map( 287 | (row) => row.employee_name 288 | ); 289 | 290 | return res.status(200).json({ 291 | success: true, 292 | message: "Task reassigned successfully", 293 | employee_ids: assignedEmployeeIds, 294 | employee_names: assignedEmployeeNames, 295 | }); 296 | } catch (error) { 297 | console.error("Error reassigning task:", error); 298 | return res.status(500).json({ success: false, message: "Server Error" }); 299 | } 300 | }); 301 | 302 | // Route to fetch all employees 303 | // After: Join employee and category to get the category name as role 304 | router.get("/list", async (req, res) => { 305 | try { 306 | const result = await db.query(` 307 | SELECT e.id, 308 | e.name, 309 | c.name AS role 310 | FROM employee e 311 | LEFT JOIN category c ON e.category_id = c.id 312 | ORDER BY e.id ASC 313 | `); 314 | res.status(200).json({ success: true, employees: result.rows }); 315 | } catch (error) { 316 | console.error("Error fetching employees:", error); 317 | res.status(500).json({ success: false, message: "Internal Server Error" }); 318 | } 319 | }); 320 | 321 | // GET /tasks 322 | router.get("/", async (req, res) => { 323 | try { 324 | const { project_id } = req.query; 325 | 326 | let query = ` 327 | SELECT 328 | t.task_id, 329 | t.description, 330 | t.deadline, 331 | t.status, 332 | t.project_id, 333 | t.created_at, 334 | t.updated_at 335 | FROM tasks t 336 | WHERE 1=1 337 | `; 338 | const values = []; 339 | 340 | if (project_id && !isNaN(parseInt(project_id, 10))) { 341 | values.push(parseInt(project_id, 10)); 342 | query += ` AND t.project_id = $${values.length}`; 343 | } 344 | 345 | // Run the main tasks query 346 | const tasksResult = await db.query(query, values); 347 | const tasks = tasksResult.rows; // array of tasks 348 | 349 | // Fetch all task assignments and employee names 350 | const assignmentQuery = ` 351 | SELECT ta.task_id, ta.employee_id, e.name AS employee_name 352 | FROM task_assignments ta 353 | JOIN employee e ON ta.employee_id = e.id 354 | `; 355 | const assignmentResult = await db.query(assignmentQuery); 356 | 357 | // Group employees by task_id 358 | const assignmentsByTask = {}; 359 | assignmentResult.rows.forEach((row) => { 360 | if (!assignmentsByTask[row.task_id]) { 361 | assignmentsByTask[row.task_id] = { 362 | employee_ids: [], 363 | employee_names: [], 364 | }; 365 | } 366 | assignmentsByTask[row.task_id].employee_ids.push(row.employee_id); 367 | assignmentsByTask[row.task_id].employee_names.push(row.employee_name); 368 | }); 369 | 370 | // Attach the employee arrays to each task 371 | const finalTasks = tasks.map((task) => { 372 | const assigned = assignmentsByTask[task.task_id] || { 373 | employee_ids: [], 374 | employee_names: [], 375 | }; 376 | return { 377 | ...task, 378 | employee_ids: assigned.employee_ids, 379 | employee_names: assigned.employee_names, 380 | }; 381 | }); 382 | 383 | return res.json({ success: true, tasks: finalTasks }); 384 | } catch (error) { 385 | console.error("Error fetching tasks:", error); 386 | return res.status(500).json({ success: false, message: "Server Error" }); 387 | } 388 | }); 389 | 390 | /** 391 | * GET /tasks/ongoing 392 | * Retrieve ongoing tasks with assigned user information. 393 | */ 394 | router.get("/ongoing", async (req, res) => { 395 | try { 396 | const tasksResult = await db.query( 397 | `SELECT t.task_id, t.description, t.deadline, t.status, t.project_id, t.created_at, t.updated_at 398 | FROM tasks t 399 | WHERE t.status != 'Completed' 400 | ORDER BY t.deadline ASC` 401 | ); 402 | const tasks = tasksResult.rows; // array of tasks 403 | 404 | // Fetch all task assignments and employee names 405 | const assignmentQuery = ` 406 | SELECT ta.task_id, ta.employee_id, e.name AS employee_name 407 | FROM task_assignments ta 408 | JOIN employee e ON ta.employee_id = e.id 409 | `; 410 | const assignmentResult = await db.query(assignmentQuery); 411 | 412 | // Group employees by task_id 413 | const assignmentsByTask = {}; 414 | assignmentResult.rows.forEach((row) => { 415 | if (!assignmentsByTask[row.task_id]) { 416 | assignmentsByTask[row.task_id] = { 417 | employee_ids: [], 418 | employee_names: [], 419 | }; 420 | } 421 | assignmentsByTask[row.task_id].employee_ids.push(row.employee_id); 422 | assignmentsByTask[row.task_id].employee_names.push(row.employee_name); 423 | }); 424 | 425 | // Attach the employee arrays to each task 426 | const finalTasks = tasks.map((task) => { 427 | const assigned = assignmentsByTask[task.task_id] || { 428 | employee_ids: [], 429 | employee_names: [], 430 | }; 431 | return { 432 | ...task, 433 | employee_ids: assigned.employee_ids, 434 | employee_names: assigned.employee_names, 435 | }; 436 | }); 437 | 438 | return res.json({ success: true, tasks: finalTasks }); 439 | } catch (error) { 440 | console.error("Error fetching ongoing tasks:", error); 441 | return res.status(500).json({ success: false, message: "Server Error" }); 442 | } 443 | }); 444 | 445 | /** 446 | * PUT /tasks/:taskId 447 | * Update a task. 448 | */ 449 | router.put("/:taskId", async (req, res) => { 450 | const { taskId } = req.params; 451 | const { status } = req.body; 452 | 453 | // Validate required fields 454 | if (!status) { 455 | return res.status(400).json({ 456 | success: false, 457 | message: "Status is required", 458 | }); 459 | } 460 | 461 | try { 462 | // 1. Fetch the task from the database 463 | const taskResult = await db.query( 464 | "SELECT * FROM tasks WHERE task_id = $1", 465 | [taskId] 466 | ); 467 | 468 | if (taskResult.rows.length === 0) { 469 | return res 470 | .status(404) 471 | .json({ success: false, message: "Task not found" }); 472 | } 473 | 474 | // 2. Perform the update operation on the task 475 | const updateQuery = ` 476 | UPDATE tasks 477 | SET status = $1, 478 | updated_at = NOW() 479 | WHERE task_id = $2 480 | RETURNING *; 481 | `; 482 | const { rows } = await db.query(updateQuery, [status, taskId]); 483 | const updatedTask = rows[0]; 484 | 485 | // 3. Real-time notifications after task update 486 | const assignedRows = await db.query( 487 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 488 | [taskId] 489 | ); 490 | 491 | // Notify all assigned employees 492 | assignedRows.rows.forEach((row) => { 493 | io.to(`user_${row.employee_id}`).emit("taskUpdated", { 494 | taskId, 495 | status, 496 | message: `Task #${taskId} has been updated`, 497 | }); 498 | }); 499 | 500 | return res.status(200).json({ success: true, task: updatedTask }); 501 | } catch (error) { 502 | console.error("Error updating task:", error); 503 | return res.status(500).json({ success: false, message: "Server Error" }); 504 | } 505 | }); 506 | 507 | /** 508 | * GET /tasks/employee/:employeeId 509 | * Retrieve tasks assigned to a specific employee. 510 | */ 511 | router.get("/employee/:employeeId", async (req, res) => { 512 | const { employeeId } = req.params; 513 | 514 | try { 515 | const tasksResult = await db.query( 516 | `SELECT t.task_id, t.description, t.deadline, t.status, t.project_id, t.created_at, t.updated_at, p.title AS project_title 517 | FROM tasks t 518 | JOIN task_assignments ta ON t.task_id = ta.task_id 519 | JOIN projects p ON t.project_id = p.project_id 520 | WHERE ta.employee_id = $1 521 | ORDER BY t.deadline ASC`, 522 | [employeeId] 523 | ); 524 | const tasks = tasksResult.rows; // array of tasks 525 | 526 | return res.json({ success: true, tasks }); 527 | } catch (error) { 528 | console.error("Error fetching tasks:", error); 529 | return res.status(500).json({ success: false, message: "Server Error" }); 530 | } 531 | }); 532 | 533 | export { router as taskRouter }; 534 | -------------------------------------------------------------------------------- /Server/Routes/TaskStatusRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import db from "../utils/db.js"; 3 | import { io } from "../index.js"; 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * PUT /taskstatus/:taskId 9 | * Update the status of a task. 10 | */ 11 | router.put("/:taskId", async (req, res) => { 12 | const { taskId } = req.params; 13 | const { status } = req.body; 14 | 15 | // Validate required fields 16 | if (!status) { 17 | return res.status(400).json({ 18 | success: false, 19 | message: "Status is required", 20 | }); 21 | } 22 | 23 | try { 24 | // 1. Fetch the task from the database 25 | const taskResult = await db.query( 26 | "SELECT * FROM tasks WHERE task_id = $1", 27 | [taskId] 28 | ); 29 | 30 | if (taskResult.rows.length === 0) { 31 | return res 32 | .status(404) 33 | .json({ success: false, message: "Task not found" }); 34 | } 35 | 36 | // 2. Perform the update operation on the task 37 | const updateQuery = ` 38 | UPDATE tasks 39 | SET status = $1, 40 | updated_at = NOW() 41 | WHERE task_id = $2 42 | RETURNING *; 43 | `; 44 | const { rows } = await db.query(updateQuery, [status, taskId]); 45 | const updatedTask = rows[0]; 46 | 47 | // 3. Real-time notifications after task update 48 | const assignedRows = await db.query( 49 | "SELECT employee_id FROM task_assignments WHERE task_id = $1", 50 | [taskId] 51 | ); 52 | 53 | // Notify all assigned employees 54 | assignedRows.rows.forEach((row) => { 55 | io.to(`user_${row.employee_id}`).emit("taskUpdated", { 56 | taskId, 57 | status, 58 | message: `Task #${taskId} has been updated`, 59 | }); 60 | }); 61 | 62 | return res.status(200).json({ success: true, task: updatedTask }); 63 | } catch (error) { 64 | console.error("Error updating task:", error); 65 | return res.status(500).json({ success: false, message: "Server Error" }); 66 | } 67 | }); 68 | 69 | export { router as taskStatusRouter }; 70 | -------------------------------------------------------------------------------- /Server/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import cors from "cors"; 3 | import { adminRouter } from "./Routes/AdminRoute.js"; 4 | import { employeeRouter } from "./Routes/EmployeeRoute.js"; 5 | import { projectRouter } from "./Routes/ProjectRoute.js"; 6 | import { taskRouter } from "./Routes/TaskRoute.js"; 7 | import { clientsRouter } from "./Routes/ClientsRoute.js"; 8 | import { taskStatusRouter } from "./Routes/TaskStatusRoute.js"; 9 | import { notificationRouter } from "./Routes/NotificationsRoute.js"; 10 | import { attendanceRouter } from "./Routes/AttendanceRoute.js"; 11 | import jwt from "jsonwebtoken"; 12 | import cookieParser from "cookie-parser"; 13 | import http from "http"; 14 | import { Server } from "socket.io"; 15 | import dotenv from "dotenv"; 16 | 17 | // Load environment variables from .env file 18 | dotenv.config(); 19 | 20 | const app = express(); 21 | 22 | // Middleware setup 23 | app.use( 24 | cors({ 25 | origin: [process.env.CLIENT_URL || "http://localhost:5173"], // Use env variable for production URL 26 | methods: ["GET", "POST", "PUT", "DELETE"], 27 | credentials: true, 28 | }) 29 | ); 30 | app.use(express.json()); 31 | app.use(cookieParser()); 32 | app.use(express.static("Public")); 33 | 34 | // Middleware to verify user authentication 35 | const verifyUser = (req, res, next) => { 36 | const token = req.cookies.jwt; 37 | if (!token) { 38 | return res.status(401).json({ Status: false, Error: "Not Authenticated" }); 39 | } 40 | 41 | jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { 42 | if (err) { 43 | return res.status(403).json({ Status: false, Error: "Invalid Token" }); 44 | } 45 | 46 | req.role = decoded.role; 47 | req.id = decoded.id; 48 | next(); 49 | }); 50 | }; 51 | 52 | // Routes setup 53 | app.use("/auth", adminRouter); 54 | app.use("/employee", employeeRouter); 55 | app.use("/projects", projectRouter); 56 | app.use("/tasks", taskRouter); 57 | app.use("/clients", clientsRouter); 58 | app.use("/taskstatus", taskStatusRouter); 59 | app.use("/notifications", notificationRouter); 60 | app.use("/attendance", attendanceRouter); 61 | 62 | // Verify route 63 | app.get("/verify", verifyUser, (req, res) => { 64 | return res.json({ Status: true, role: req.role, id: req.id }); 65 | }); 66 | 67 | // Create HTTP server 68 | const server = http.createServer(app); 69 | 70 | // Initialize Socket.io 71 | export const io = new Server(server, { 72 | cors: { 73 | origin: process.env.CLIENT_URL || "http://localhost:5173", // Use env variable for client URL 74 | methods: ["GET", "POST", "PUT", "DELETE"], 75 | credentials: true, 76 | }, 77 | }); 78 | 79 | // Socket.io connection handler 80 | io.on("connection", (socket) => { 81 | console.log(`User connected with socket ID: ${socket.id}`); 82 | 83 | // Listen for a "join" event to place a user in a room 84 | socket.on("join", (userId) => { 85 | socket.join(`user_${userId}`); 86 | console.log(`Socket ${socket.id} joined room user_${userId}`); 87 | }); 88 | 89 | // Add error handling for Socket.io 90 | socket.on("error", (err) => { 91 | console.error("Socket Error:", err); 92 | }); 93 | 94 | socket.on("disconnect", () => { 95 | console.log(`User disconnected with socket ID: ${socket.id}`); 96 | }); 97 | }); 98 | 99 | // Start server on port 100 | const port = process.env.PORT || 3000; 101 | server.listen(port, () => { 102 | console.log(`Server is running on port ${port}`); 103 | }); 104 | -------------------------------------------------------------------------------- /Server/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export const verifyUser = (req, res, next) => { 4 | const token = req.cookies.jwt; 5 | if (!token) { 6 | return res 7 | .status(401) 8 | .json({ success: false, message: "Not Authenticated" }); 9 | } 10 | jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { 11 | if (err) { 12 | return res.status(403).json({ success: false, message: "Invalid Token" }); 13 | } 14 | req.role = decoded.role; 15 | req.id = decoded.id; 16 | next(); 17 | }); 18 | }; 19 | 20 | export const verifyAdmin = (req, res, next) => { 21 | verifyUser(req, res, () => { 22 | if (req.role !== "admin") { 23 | return res.status(403).json({ success: false, message: "Access Denied" }); 24 | } 25 | next(); 26 | }); 27 | }; 28 | 29 | export const verifyManager = (req, res, next) => { 30 | verifyUser(req, res, () => { 31 | if (req.role !== "manager") { 32 | return res.status(403).json({ success: false, message: "Access Denied" }); 33 | } 34 | next(); 35 | }); 36 | }; 37 | 38 | export const verifyAdminOrManager = (req, res, next) => { 39 | verifyUser(req, res, () => { 40 | if (req.role === "admin" || req.role === "manager") { 41 | return next(); 42 | } 43 | return res.status(403).json({ success: false, message: "Access Denied" }); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /Server/middlewares/verifyAdmin.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import db from "../utils/db.js"; 3 | 4 | export const verifyAdmin = async (req, res, next) => { 5 | const token = req.headers.authorization?.split(" ")[1]; 6 | 7 | if (!token) { 8 | return res.status(403).json({ success: false, message: "Access denied" }); 9 | } 10 | 11 | try { 12 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 13 | const adminResult = await db.query("SELECT * FROM admin WHERE id = $1", [ 14 | decoded.id, 15 | ]); 16 | 17 | if (adminResult.rows.length === 0) { 18 | return res.status(403).json({ success: false, message: "Access denied" }); 19 | } 20 | 21 | req.adminId = decoded.id; 22 | next(); 23 | } catch (error) { 24 | console.error("Error verifying admin:", error); 25 | return res.status(403).json({ success: false, message: "Access denied" }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /Server/middlewares/verifyUser.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export const verifyUser = (req, res, next) => { 4 | const token = req.cookies.jwt; 5 | if (!token) { 6 | return res.status(401).json({ Status: false, Error: "Not Authenticated" }); 7 | } 8 | jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { 9 | if (err) { 10 | return res.status(403).json({ Status: false, Error: "Invalid Token" }); 11 | } 12 | // Attach user info to req 13 | req.role = decoded.role; 14 | req.id = decoded.id; 15 | next(); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "@fortawesome/react-fontawesome": "^0.2.0", 16 | "bcrypt": "^5.1.1", 17 | "cookie-parser": "^1.4.6", 18 | "cors": "^2.8.5", 19 | "dotenv": "^16.4.5", 20 | "express": "^4.19.2", 21 | "express-session": "^1.18.0", 22 | "jsonwebtoken": "^9.0.2", 23 | "memorystore": "^1.6.7", 24 | "moment": "^2.30.1", 25 | "multer": "^1.4.5-lts.1", 26 | "nodemon": "^3.1.0", 27 | "path": "^0.12.7", 28 | "pg": "^8.11.5", 29 | "socket.io": "^4.8.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Server/public/images/image_1714386729091.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1714386729091.jpg -------------------------------------------------------------------------------- /Server/public/images/image_1714503454639.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1714503454639.jpg -------------------------------------------------------------------------------- /Server/public/images/image_1714808711692.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1714808711692.png -------------------------------------------------------------------------------- /Server/public/images/image_1716625171724.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1716625171724.jpg -------------------------------------------------------------------------------- /Server/public/images/image_1739772759938.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1739772759938.jpg -------------------------------------------------------------------------------- /Server/public/images/image_1739775412179.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1739775412179.jpg -------------------------------------------------------------------------------- /Server/public/images/image_1739988964275.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shubham-07x/WORKSUITE-Employee-and-Project-Management-System/722c670d0c73196587f6a9bb132336ca34163dcd/Server/public/images/image_1739988964275.jpg -------------------------------------------------------------------------------- /Server/utils/db.js: -------------------------------------------------------------------------------- 1 | import pg from "pg"; 2 | import dotenv from "dotenv"; 3 | 4 | const saltRounds = 10; 5 | dotenv.config(); 6 | 7 | const db = new pg.Client({ 8 | user: "postgres", 9 | host: "localhost", 10 | database: "work_suite_db", 11 | password: process.env.DB_PASSWORD, 12 | port: 5432, 13 | }); 14 | db.connect((err) => { 15 | if (err) { 16 | console.log("Error establishing Connection", err); 17 | } else { 18 | console.log("Connection Succesfull"); 19 | } 20 | }); 21 | 22 | export default db; 23 | -------------------------------------------------------------------------------- /database_schema.sql: -------------------------------------------------------------------------------- 1 | -- Admin Table 2 | CREATE TABLE admin ( 3 | id SERIAL PRIMARY KEY, 4 | email VARCHAR(50) UNIQUE NOT NULL, 5 | password VARCHAR(140) NOT NULL 6 | ); 7 | 8 | -- Category Table 9 | CREATE TABLE category ( 10 | id SERIAL PRIMARY KEY, 11 | name VARCHAR NOT NULL 12 | ); 13 | 14 | -- Employee Table 15 | CREATE TABLE employee ( 16 | id SERIAL PRIMARY KEY, 17 | name VARCHAR(255) NOT NULL, 18 | email VARCHAR(255) UNIQUE NOT NULL, 19 | password VARCHAR(255) NOT NULL, 20 | address VARCHAR(255) NOT NULL, 21 | salary NUMERIC(10,2) NOT NULL, 22 | image VARCHAR(255), 23 | category_id INT REFERENCES category(id) ON DELETE SET NULL 24 | ); 25 | 26 | -- Clock Records Table (Attendance) 27 | CREATE TABLE clock_records ( 28 | id SERIAL PRIMARY KEY, 29 | employee_id INT REFERENCES employee(id) ON DELETE CASCADE, 30 | clock_in TIMESTAMP NOT NULL, 31 | clock_out TIMESTAMP, 32 | location VARCHAR(255), 33 | work_from_type VARCHAR(50) -- e.g., office, home, remote, etc. 34 | ); 35 | 36 | -- Office Locations Table 37 | CREATE TABLE office_locations ( 38 | id SERIAL PRIMARY KEY, 39 | name VARCHAR(100) NOT NULL, 40 | latitude DOUBLE PRECISION NOT NULL, 41 | longitude DOUBLE PRECISION NOT NULL, 42 | address VARCHAR(255) 43 | ); 44 | 45 | -- ================================ 46 | -- 2. New Tables (Project Management Tool - PMT) 47 | -- ================================ 48 | 49 | -- Clients Table: Stores client details. 50 | CREATE TABLE clients ( 51 | client_id SERIAL PRIMARY KEY, 52 | name VARCHAR(150) NOT NULL, 53 | contact_person VARCHAR(150), 54 | email VARCHAR(150), 55 | phone VARCHAR(20), 56 | address TEXT, 57 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 58 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 59 | ); 60 | 61 | -- Projects Table: Stores project details. 62 | CREATE TABLE projects ( 63 | project_id SERIAL PRIMARY KEY, 64 | title VARCHAR(150) NOT NULL, 65 | description TEXT, 66 | created_by INT REFERENCES admin(id) ON DELETE SET NULL, -- admin who created the project 67 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 68 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 69 | status VARCHAR(20) DEFAULT 'Not Started', -- Not Started, In Progress, On Hold, Completed, Canceled 70 | completion_date TIMESTAMP, 71 | start_date TIMESTAMP, 72 | priority VARCHAR(20) DEFAULT 'Medium', -- Low, Medium, High, Urgent 73 | client_id INT REFERENCES clients(client_id) ON DELETE SET NULL 74 | ); 75 | 76 | -- Tasks Table: Stores tasks associated with projects. 77 | CREATE TABLE tasks ( 78 | task_id SERIAL PRIMARY KEY, 79 | description TEXT NOT NULL, 80 | deadline TIMESTAMP, 81 | status VARCHAR(20) DEFAULT 'pending', -- pending, in_progress, completed 82 | project_id INT REFERENCES projects(project_id) ON DELETE CASCADE, 83 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 84 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 85 | ); 86 | 87 | -- Task Assignments Table (Optional): To handle many-to-many relationship between employees and tasks. 88 | -- Use this if a task may be assigned to multiple employees or if you want a separate assignment record. 89 | CREATE TABLE task_assignments ( 90 | id SERIAL PRIMARY KEY, 91 | task_id INT REFERENCES tasks(task_id) ON DELETE CASCADE, 92 | employee_id INT REFERENCES employee(id) ON DELETE CASCADE, 93 | assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 94 | ); 95 | 96 | -- Notifications Table (Optional): For real-time alerts related to tasks. 97 | CREATE TABLE notifications ( 98 | notification_id SERIAL PRIMARY KEY, 99 | user_id INT REFERENCES employee(id) ON DELETE CASCADE, 100 | message TEXT NOT NULL, 101 | is_read BOOLEAN DEFAULT FALSE, 102 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 103 | ); --------------------------------------------------------------------------------