├── .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 |
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 |
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 |
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 |
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 | Name |
26 |
27 |
28 |
29 | {category.map(category => (
30 |
31 | {category.name} |
32 |
33 | ))}
34 |
35 |
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 | setShowModal(false)}>×
170 |
171 |
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 |
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 |
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 | Name |
59 | Image |
60 | Email |
61 | Address |
62 | Salary |
63 | Action |
64 |
65 |
66 |
67 | {employees.map((employee) => (
68 |
69 | {employee.name} |
70 |
71 |
72 | {employee.image ? (
73 | 
79 | ) : (
80 |
84 | Placeholder
85 |
86 | )}
87 |
88 | |
89 | {employee.email} |
90 | {employee.address} |
91 | {employee.salary} |
92 |
93 |
97 | Edit
98 |
99 |
105 | |
106 |
107 | ))}
108 |
109 |
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 |

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 |
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 | Name |
151 | Start Date |
152 | Status |
153 |
154 |
155 |
156 | {ongoingProjects.map((project) => (
157 |
158 | {project.title} |
159 |
160 | {new Date(project.start_date).toLocaleDateString()}
161 | |
162 | {project.status} |
163 |
164 | ))}
165 |
166 |
167 |
168 |
169 |
170 |
171 | {/* Column 2: Ongoing Tasks */}
172 |
173 |
174 |
175 | Ongoing Tasks
176 |
183 |
184 |
185 | Name |
186 | Deadline |
187 | Assigned To |
188 | Status |
189 |
190 |
191 |
192 | {ongoingTasks.map((task) => (
193 |
194 | {task.description} |
195 | {new Date(task.deadline).toLocaleDateString()} |
196 | {task.employee_names.join(", ")} |
197 | {task.status} |
198 |
199 | ))}
200 |
201 |
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 |
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 |
93 | {admins.map((admin) => (
94 | -
95 | {admin.email}{" "}
96 |
97 |
98 | ))}
99 |
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 |
66 | {officeLocations.map(location => (
67 | -
68 | {location.name} - Latitude: {location.latitude}, Longitude: {location.longitude}, Address: {location.address}
69 |
70 |
71 | ))}
72 |
73 | {error &&
Error: {error}
}
74 | {successMessage &&
{successMessage}
}
75 |
Add New Office Location
76 |
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 |
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 |
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 | Task ID |
224 | Description |
225 | Assigned Employees |
226 | Deadline |
227 | Status |
228 | Actions |
229 |
230 |
231 |
232 | {filteredTasks.map((task) => (
233 |
234 | {task.task_id} |
235 | {task.description} |
236 | {task.employee_names?.join(", ") || "N/A"} |
237 |
238 | {task.deadline
239 | ? new Date(task.deadline).toLocaleString()
240 | : "N/A"}
241 | |
242 | {task.status} |
243 |
244 |
252 |
259 | |
260 |
261 | ))}
262 |
263 |
264 |
265 | {/* Modal for Adding Task */}
266 | setShowModal(false)}>
267 |
268 | Assign New Task
269 |
270 |
271 |
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 |
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 | Client ID |
84 | Name |
85 | Contact Person |
86 | Email |
87 | Phone |
88 | Address |
89 | Actions |
90 |
91 |
92 |
93 | {clients.map((client) => (
94 |
95 | {client.client_id} |
96 | {client.name} |
97 | {client.contact_person} |
98 | {client.email} |
99 | {client.phone} |
100 | {client.address} |
101 |
102 |
112 | |
113 |
114 | ))}
115 |
116 |
117 |
118 | {/* Modal for Adding Client */}
119 | setShowAddModal(false)}>
120 |
121 | Add New Client
122 |
123 |
124 |
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 | Task |
101 | Project |
102 | Deadline |
103 | Status |
104 | Action |
105 |
106 |
107 |
108 | {tasks.length === 0 ? (
109 |
110 |
111 | No tasks assigned yet.
112 | |
113 |
114 | ) : (
115 | tasks.map((task) => (
116 |
117 | {task.description} |
118 | {task.project_title || "N/A"} |
119 |
120 | {task.deadline
121 | ? new Date(task.deadline).toLocaleString()
122 | : "N/A"}
123 | |
124 | {task.status} |
125 |
126 | {task.status !== "completed" && (
127 | <>
128 | {task.status !== "in_progress" && (
129 |
139 | )}
140 |
150 | >
151 | )}
152 | |
153 |
154 | ))
155 | )}
156 |
157 |
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 |
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 | 
5 |
6 | ## Login Screen
7 | 
8 |
9 | ## Employee Dashboard
10 | 
11 |
12 | ## Main Dashboard
13 | 
14 |
15 | ## Admin Interface
16 | 
17 |
18 | ## Category Management
19 | 
20 |
21 | ## Employee Management
22 | 
23 |
24 | ## Office Branch Locations
25 | 
26 | =======
27 | 
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 | );
--------------------------------------------------------------------------------