Efficiently managing employees is crucial for any organization's success. From tracking attendance and managing payroll to overseeing performance and ensuring effective communication, an employee management system (EMS) can significantly streamline these tasks.
In this article, we'll walk you through creating a robust and user-friendly Employee Management System using two powerful technologies: React.js for the front end and Django for the back end. React.js, a popular JavaScript library, allows us to build interactive and dynamic user interfaces, while Django, a high-level Python web framework, provides a solid and scalable foundation for the back end.
This guide will cover the essential CRUD (Create, Read, Update, Delete) functionalities, ensuring that our EMS can handle the fundamental operations needed for managing employee data. We'll also focus on creating a robust backend that can support these operations securely and efficiently.
It's important to note that this project is a demo and may not be fully optimized for user-friendliness. However, it serves as a comprehensive starting point for anyone looking to build a more sophisticated EMS tailored to their organization's specific needs.
Whether you're a seasoned developer looking to expand your tech stack or a novice eager to dive into full-stack development, this guide will offer insights and practical steps to help you build an EMS that can handle the complexities of modern workforce management. So, let's roll up our sleeves and start building a system that can make employee management more efficient, transparent, and enjoyable for everyone involved.
Before we dive into coding, we need to set up our development environment and create the Django project. Follow these steps to get started:
First, ensure you have Python installed on your machine. You can download it from the official Python website.
Next, install virtualenv to create an isolated Python environment for your project:
pip install virtualenv
Create a new directory for your project and navigate into it:
mkdir employee_management_system
cd employee_management_system
Create and activate a virtual environment:
python -m venv venv
source venv/bin/activate # On Windows use `venv\Scripts\activate`
You should now see (venv) at the beginning of your command prompt, indicating that the virtual environment is active.
With the virtual environment activated, install Django:
pip install django
pip install djangorestframework
pip install django-cors-headers
Create a new Django project called DjangoAPI:
django-admin startproject DjangoAPI
cd DjangoAPI
Run the development server to ensure everything is set up correctly:
python manage.py runserver
Open your browser and navigate to http://127.0.0.1:8000/. You should see the Django welcome page, indicating that your Django project is up and running.
Now, let's update the Django settings to configure paths, security, installed apps, middleware, templates, and logging. Here's an example of a comprehensive settings.py file for our Employee Management System:
from pathlib import Path
import os
import secrets
# Paths
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
MEDIA_URL = '/Photos/'
MEDIA_ROOT = BASE_DIR / "Photos"
# Security
SECRET_KEY_FILE = BASE_DIR / 'secret_key.txt'
if SECRET_KEY_FILE.exists():
with open(SECRET_KEY_FILE) as f:
SECRET_KEY = f.read().strip()
else:
SECRET_KEY = secrets.token_urlsafe(50)
with open(SECRET_KEY_FILE, 'w') as f:
f.write(SECRET_KEY)
DEBUG = True
ALLOWED_HOSTS = []
# Applications
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'EmployeeApp.apps.EmployeeappConfig'
]
CORS_ORIGIN_ALLOW_ALL = True
# Middleware
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# URL Configuration
ROOT_URLCONF = 'DjangoAPI.urls'
# Templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# WSGI
WSGI_APPLICATION = 'DjangoAPI.wsgi.application'
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password Validation
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static Files
STATIC_URL = '/static/'
# Default Primary Key Field Type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'debug.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
With the Django project set up, the next step is to create the necessary components for our Employee Management System. We will follow a structured approach to build models, views, serializers, tests, URLs, and admin configurations.
First, create a new Django application called EmployeeApp:
python manage.py startapp EmployeeApp
Add EmployeeApp to the INSTALLED_APPS list in settings.py:
# DjangoAPI/settings.py
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'EmployeeApp',
]
Open EmployeeApp/models.py and define the Employee model
Models in Django are used to define the structure of the database tables. They represent the data and the logic that revolves around the data.
# EmployeeApp/models.py
from django.db import models
class Departments(models.Model):
DepartmentId = models.AutoField(primary_key=True)
DepartmentName = models.CharField(max_length=500)
def __str__(self):
return self.DepartmentName
class Employees(models.Model):
EmployeeId = models.AutoField(primary_key=True)
EmployeeName = models.CharField(max_length=500)
Department = models.CharField(max_length=500)
DateOfJoining = models.DateField()
PhotoFileName = models.CharField(max_length=500)
def __str__(self):
return self.EmployeeName
Run the following commands to create and apply migrations:
python manage.py makemigrations EmployeeApp
python manage.py migrate
Create serializers for Departments and Employees in EmployeeApp/serializers.py
Serializers in Django REST Framework are used to convert complex data types, such as querysets and model instances, into native Python datatypes that can then be easily rendered into JSON, XML, or other content types. They also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
# EmployeeApp/serializers.py
from rest_framework import serializers
from EmployeeApp.models import Departments, Employees
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = Departments
fields = ('DepartmentId', 'DepartmentName')
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employees
fields = ('EmployeeId', 'EmployeeName', 'Department', 'DateOfJoining', 'PhotoFileName')
Meta Class: Contains metadata for the serializer.
Define the views for handling API requests in EmployeeApp/views.py
Views in Django REST Framework are used to handle the HTTP requests and return HTTP responses. They contain the logic for the various actions (CRUD operations) that can be performed on the data.
# EmployeeApp/views.py
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Departments, Employees
from .serializers import DepartmentSerializer, EmployeeSerializer
from django.core.files.storage import default_storage
from django.http import Http404
import logging
logger = logging.getLogger(__name__)
@api_view(['GET', 'POST', 'PUT', 'DELETE'])
def departmentApi(request, id=None):
if request.method == 'GET':
if id:
try:
department = Departments.objects.get(DepartmentId=id)
logger.debug(f"GET request for department with id={id}")
except Departments.DoesNotExist:
logger.error(f"Department with id={id} not found")
raise Http404("Department not found")
serializer = DepartmentSerializer(department)
return Response(serializer.data)
else:
departments = Departments.objects.all()
logger.debug("GET request for all departments")
serializer = DepartmentSerializer(departments, many=True)
return Response(serializer.data)
elif request.method == 'POST':
logger.debug(f"POST request with data={request.data}")
serializer = DepartmentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
logger.debug(f"Department created: {serializer.data}")
return Response(serializer.data, status=status.HTTP_201_CREATED)
logger.error(f"Validation errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'PUT':
if id is None:
logger.error("PUT request with id=None")
return Response({"error": "Department id is required for update"}, status=status.HTTP_400_BAD_REQUEST)
try:
department = Departments.objects.get(DepartmentId=id)
logger.debug(f"PUT request for department with id={id}")
except Departments.DoesNotExist:
logger.error(f"Department with id={id} not found")
raise Http404("Department not found")
serializer = DepartmentSerializer(department, data=request.data)
if serializer.is_valid():
serializer.save()
logger.debug(f"Department updated: {serializer.data}")
return Response(serializer.data)
logger.error(f"Validation errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
if id is None:
logger.error("DELETE request with id=None")
return Response({"error": "Department id is required for delete"}, status=status.HTTP_400_BAD_REQUEST)
try:
department = Departments.objects.get(DepartmentId=id)
logger.debug(f"DELETE request for department with id={id}")
except Departments.DoesNotExist:
logger.error(f"Department with id={id} not found")
raise Http404("Department not found")
department.delete()
logger.debug(f"Department with id={id} deleted")
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(['GET', 'POST', 'PUT', 'DELETE'])
def employeeApi(request, id=None):
if request.method == 'GET':
if id:
try:
employee = Employees.objects.get(EmployeeId=id)
logger.debug(f"GET request for employee with id={id}")
except Employees.DoesNotExist:
logger.error(f"Employee with id={id} not found")
raise Http404("Employee not found")
serializer = EmployeeSerializer(employee)
return Response(serializer.data)
else:
employees = Employees.objects.all()
logger.debug("GET request for all employees")
serializer = EmployeeSerializer(employees, many=True)
return Response(serializer.data)
elif request.method == 'POST':
logger.debug(f"POST request with data={request.data}")
serializer = EmployeeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
logger.debug(f"Employee created: {serializer.data}")
return Response(serializer.data, status=status.HTTP_201_CREATED)
logger.error(f"Validation errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'PUT':
if id is None:
logger.error("PUT request with id=None")
return Response({"error": "Employee id is required for update"}, status=status.HTTP_400_BAD_REQUEST)
try:
employee = Employees.objects.get(EmployeeId=id)
logger.debug(f"PUT request for employee with id={id}")
except Employees.DoesNotExist:
logger.error(f"Employee with id={id} not found")
raise Http404("Employee not found")
serializer = EmployeeSerializer(employee, data=request.data)
if serializer.is_valid():
serializer.save()
logger.debug(f"Employee updated: {serializer.data}")
return Response(serializer.data)
logger.error(f"Validation errors: {serializer.errors}")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
if id is None:
logger.error("DELETE request with id=None")
return Response({"error": "Employee id is required for delete"}, status=status.HTTP_400_BAD_REQUEST)
try:
employee = Employees.objects.get(EmployeeId=id)
logger.debug(f"DELETE request for employee with id={id}")
except Employees.DoesNotExist:
logger.error(f"Employee with id={id} not found")
raise Http404("Employee not found")
employee.delete()
logger.debug(f"Employee with id={id} deleted")
return Response(status=status.HTTP_204_NO_CONTENT)
# Handles file upload requests. The uploaded file is saved using Django's default storage system, and the file name is returned in the response.
@api_view(['POST'])
def SaveFile(request):
file = request.FILES['file']
logger.debug(f"File upload request: {file.name}")
file_name = default_storage.save(file.name, file)
logger.debug(f"File saved as: {file_name}")
return Response(file_name, status=status.HTTP_200_OK)
These views utilize the Django REST Framework to provide a RESTful API for the Employee Management System, allowing clients to perform CRUD operations on the Departments and Employees resources, as well as upload files. The logging statements help in tracking the flow of requests and debugging any issues that arise during API interactions.
The urls.py file in Django is used to map URL patterns to views. It determines what code is executed when a particular URL is accessed.
from django.urls import re_path as url
from EmployeeApp import views
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
url(r'^department$', views.departmentApi, name='departmentApi'),
url(r'^department/([0-9]+)$', views.departmentApi, name='departmentApi'),
url(r'^employee$', views.employeeApi, name='employeeApi'),
url(r'^employee/([0-9]+)$', views.employeeApi, name='employeeApi'),
url(r'^employee/savefile$', views.SaveFile, name='saveFile')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Tests are used to ensure that the application works as expected. They help verify that the code performs the intended operations and handles various cases appropriately.
from django.test import TestCase, Client
from django.urls import reverse
from rest_framework import status
from .models import Departments, Employees
import json
import tempfile
class DepartmentApiTests(TestCase):
def setUp(self):
self.client = Client()
self.department = Departments.objects.create(DepartmentName="Finance")
def test_get_all_departments(self):
response = self.client.get(reverse('departmentApi'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_get_single_department(self):
response = self.client.get(
reverse('departmentApi', args=[self.department.DepartmentId]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_create_department(self):
data = {"DepartmentName": "Marketing"}
response = self.client.post(reverse('departmentApi'), data=json.dumps(
data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_update_department(self):
data = {"DepartmentName": "Human Resources"}
response = self.client.put(reverse('departmentApi', args=[
self.department.DepartmentId]), data=json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_delete_department(self):
response = self.client.delete(
reverse('departmentApi', args=[self.department.DepartmentId]))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class EmployeeApiTests(TestCase):
def setUp(self):
self.client = Client()
self.department = Departments.objects.create(DepartmentName="IT")
self.employee = Employees.objects.create(
EmployeeName="John Doe", Department=self.department.DepartmentName, DateOfJoining="2022-01-01", PhotoFileName="john_doe.jpg")
def test_get_all_employees(self):
response = self.client.get(reverse('employeeApi'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_get_single_employee(self):
response = self.client.get(
reverse('employeeApi', args=[self.employee.EmployeeId]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_create_employee(self):
data = {"EmployeeName": "Jane Doe", "Department": "IT",
"DateOfJoining": "2022-01-01", "PhotoFileName": "jane_doe.jpg"}
response = self.client.post(reverse('employeeApi'), data=json.dumps(
data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_update_employee(self):
data = {"EmployeeName": "John Smith", "Department": "IT",
"DateOfJoining": "2022-01-01", "PhotoFileName": "john_smith.jpg"}
response = self.client.put(reverse('employeeApi', args=[
self.employee.EmployeeId]), data=json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_delete_employee(self):
response = self.client.delete(
reverse('employeeApi', args=[self.employee.EmployeeId]))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
class SaveFileTests(TestCase):
def setUp(self):
self.client = Client()
self.test_file = tempfile.NamedTemporaryFile(suffix=".jpg")
def tearDown(self):
self.test_file.close()
def test_save_file(self):
with open(self.test_file.name, 'rb') as testfile:
response = self.client.post(
reverse('saveFile'), {'file': testfile})
self.assertEqual(response.status_code, status.HTTP_200_OK)
These tests ensure the integrity and functionality of the API by verifying that the various endpoints perform the expected operations and handle different scenarios correctly.
Ensure you have Node.js and npm installed on your machine. You can download and install them from nodejs.org.
Open your terminal or command prompt and run the following command to create a new React application using Create React App:
npx create-react-app my-app
cd ui/my-app
npm install
ui/my-app/
|-- public/
| |-- index.html
|-- src/
| |-- components/
| |-- services/
| |-- App.js
| |-- index.js
|-- package.json
By following the steps above, you have successfully set up a React application for the Employee Management System.
App.css
/* Add media queries for responsive navigation */
@media (max-width: 768px) {
.navbar-nav {
display: none; /* Hide the nav items on small screens */
}
.navbar-nav.show {
display: block; /* Show the nav items when toggled */
}
.navbar-toggler {
display: block; /* Show the toggle button on small screens */
}
}
.navbar-toggler {
display: none; /* Hide the toggle button by default */
background-color: #007bff;
border: none;
color: white;
padding: 0.5rem 1rem;
font-size: 1.25rem;
cursor: pointer;
}
.navbar-toggler:focus {
outline: none;
}
.dropdown-menu {
display: none; /* Hide dropdown menu by default */
}
.dropdown.show .dropdown-menu {
display: block; /* Show dropdown menu when toggled */
position: absolute; /* Ensure the dropdown is positioned correctly */
top: 100%; /* Align the dropdown menu with the bottom of the toggle button */
left: 0; /* Align the dropdown menu to the left */
width: 100%; /* Make sure the dropdown menu spans the width of the container */
background-color: white; /* Set background color to white */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add a subtle shadow for visibility */
}
index.css
/* Ensure the background color covers the entire page */
body,
html {
height: 100%;
margin: 0;
background-color: #f9f9f9;
overflow: hidden;
}
@media (max-width: 767px) {
.table th {
font-size: 12px;
padding: 5px;
}
.table td {
font-size: 12px;
padding: 5px;
}
}
/* Responsive table */
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table {
width: 100%;
max-width: 100%;
margin-bottom: 1rem;
background-color: transparent;
}
@media (max-width: 768px) {
.table thead {
display: none;
}
.table,
.table tbody,
.table tr,
.table td {
display: block;
width: 100%;
}
.table tr {
margin-bottom: 15px;
}
.table td {
text-align: right;
padding-left: 50%;
position: relative;
}
.table td::before {
content: attr(data-label);
position: absolute;
left: 0;
width: 50%;
padding-left: 15px;
font-weight: bold;
text-align: left;
}
}
/* Home container to center content */
.home-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 75vh;
padding: 20px;
background-color: #f9f9f9;
}
.home-content {
max-width: 600px;
margin: 0 20px; /* Ensure some margin for smaller screens */
}
/* Centered text and margins */
.text-center {
text-align: center;
}
.my-4 {
margin: 2rem 0;
}
/* Responsive home content styling */
.home-content {
font-size: 16px;
line-height: 1.6;
max-width: 600px;
width: 100%;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center; /* Center align text */
}
.home-content p {
margin-bottom: 1rem;
}
/* Quick links styling */
.quick-links {
list-style-type: none;
padding: 0;
display: flex;
justify-content: center;
}
.quick-links li {
margin: 0 10px;
}
.quick-links li a {
color: #007bff;
text-decoration: none;
}
.quick-links li a:hover {
text-decoration: underline;
}
/* Responsive design for smaller screens */
@media (max-width: 600px) {
.quick-links {
flex-direction: column;
}
.quick-links li {
margin: 10px 0;
}
}
The App.js file is the main entry point of the React application. It sets up the router, navigation, and routes for the different components of the Employee Management System.
import "./App.css";
import Home from "./Home";
import Department from "./Department";
import { Employee } from "./Employee";
import { useState } from "react";
import {
BrowserRouter as Router,
Route, Routes,
NavLink,
} from "react-router-dom";
function App() {
const [isNavOpen, setIsNavOpen] = useState(false);
const toggleNav = () => {
setIsNavOpen(!isNavOpen);
};
return (
<Router>
<div className="App container">
<h3 className="text-center my-4">
Welcome to Employee Management System
</h3>
<nav className="navbar navbar-expand-sm bg-light navbar-light">
<button className="navbar-toggler" onClick={toggleNav}>
โฐ
</button>
<div className={`navbar-nav ${isNavOpen ? "show" : ""}`}>
<div className="dropdown">
<button
className="btn btn-light btn-outline-primary dropdown-toggle"
onClick={toggleNav}
>
Menu
</button>
<div className={`dropdown-menu ${isNavOpen ? "show" : ""}`}>
<NavLink
className="dropdown-item"
to="/"
onClick={() => setIsNavOpen(false)}
>
Home
</NavLink>
<NavLink
className="dropdown-item"
to="/department"
onClick={() => setIsNavOpen(false)}
>
Department
</NavLink>
<NavLink
className="dropdown-item"
to="/employee"
onClick={() => setIsNavOpen(false)}
>
Employee
</NavLink>
</div>
</div>
</div>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/department" element={<Department />} />
<Route path="/employee" element={<Employee />} />
</Routes>
</div>
</Router>
);
}
export default App;
The Home.js file represents the Home page of the application. It provides a brief overview and quick links to navigate to other sections.
import React from "react";
import "./index.css";
const Home = () => {
return (
<div className="home-container">
<div className="home-content">
<p>
This is the home page of our React application. Here, you can find
various resources and links to other sections of the app. Explore the
navigation menu to visit the Departments and Employees sections.
</p>
<p>
Our application is built using React, a popular JavaScript library for
building user interfaces. We have implemented various features to
provide a seamless experience for managing departments and employees.
</p>
<p>Below are some quick links to get you started:</p>
<ul className="quick-links">
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/department">Departments</a>
</li>
<li>
<a href="/employee">Employees</a>
</li>
</ul>
</div>
</div>
);
};
export default Home;
We have two main components:
Both components have similar functionalities:
Defining a JavaScript object named variables using ES6 syntax.
export const variables = {
API_URL: "http://127.0.0.1:8000/",
PHOTO_URL: "http://127.0.0.1:8000/Photos/",
};
The variables object has two properties:
Both Department and Employee components use the DraggableRow component to enable drag-and-drop functionality for table rows.
import React from "react";
import { useDrag, useDrop } from "react-dnd";
const ItemType = "ROW";
const DraggableRow = ({ id, index, moveRow, children }) => {
const ref = React.useRef(null);
const [, drop] = useDrop({
accept: ItemType,
hover(item, monitor) {
if (!ref.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = ref.current.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
moveRow(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemType,
item: { type: ItemType, id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const opacity = isDragging ? 0.5 : 1;
drag(drop(ref));
return (
<tr ref={ref} style={{ opacity }}>
{children}
</tr>
);
};
export default DraggableRow;
import React, { useState, useEffect, useCallback } from "react";
import { variables } from "./Variables.js";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import "./index.css";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEdit, faTrash } from '@fortawesome/free-solid-svg-icons';
import DraggableRow from "./DraggableRow"; // Import the DraggableRow component
const ItemType = "DEPARTMENT";
const Department = () => {
const [departments, setDepartments] = useState([]);
const [employees, setEmployees] = useState([]);
const [modalTitle, setModalTitle] = useState("");
const [departmentName, setDepartmentName] = useState("");
const [departmentId, setDepartmentId] = useState(0);
const [departmentIdFilter, setDepartmentIdFilter] = useState("");
const [departmentNameFilter, setDepartmentNameFilter] = useState("");
const [departmentsWithoutFilter, setDepartmentsWithoutFilter] = useState([]);
useEffect(() => {
refreshList();
}, []);
const refreshList = async () => {
try {
const depResponse = await fetch(variables.API_URL + "department");
const depData = await depResponse.json();
setDepartments(depData);
setDepartmentsWithoutFilter(depData);
const empResponse = await fetch(variables.API_URL + "employee");
const empData = await empResponse.json();
setEmployees(empData);
} catch (error) {
console.error("Failed to fetch data:", error);
}
};
const filterFn = () => {
const filteredData = departmentsWithoutFilter.filter(
(el) =>
el.DepartmentId.toString().toLowerCase().includes(departmentIdFilter.toLowerCase().trim()) &&
el.DepartmentName.toString().toLowerCase().includes(departmentNameFilter.toLowerCase().trim())
);
setDepartments(filteredData);
};
const sortResult = (prop, asc) => {
const sortedData = [...departmentsWithoutFilter].sort((a, b) => {
if (asc) return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0;
return b[prop] > a[prop] ? 1 : b[prop] < a[prop] ? -1 : 0;
});
setDepartments(sortedData);
};
const handleAddClick = () => {
setModalTitle("Add Department");
setDepartmentId(0);
setDepartmentName("");
};
const handleEditClick = (dep) => {
setModalTitle("Edit Department");
setDepartmentId(dep.DepartmentId);
setDepartmentName(dep.DepartmentName);
};
const handleCreateClick = async () => {
try {
const response = await fetch(variables.API_URL + "department", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ DepartmentName: departmentName }),
});
const result = await response.json();
alert(result);
refreshList();
} catch (error) {
alert("Failed");
}
};
const handleUpdateClick = async () => {
try {
const response = await fetch(variables.API_URL + "department/" + departmentId, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
DepartmentId: departmentId,
DepartmentName: departmentName,
}),
});
const result = await response.json();
document.getElementById('modalCloseButton').click();
refreshList();
} catch (error) {
alert("Failed");
}
};
const handleDeleteClick = async (id) => {
if (window.confirm("Are you sure?")) {
try {
const response = await fetch(variables.API_URL + "department/" + id, {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
if (response.status === 204) {
alert("Department deleted successfully");
} else {
const result = await response.json();
alert(result);
}
refreshList();
} catch (error) {
alert("Failed");
}
}
};
const handleFilterChange = (e) => {
const { name, value } = e.target;
if (name === "departmentIdFilter") {
setDepartmentIdFilter(value);
} else if (name === "departmentNameFilter") {
setDepartmentNameFilter(value);
}
filterFn();
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = departments[dragIndex];
const updatedDepartments = [...departments];
updatedDepartments.splice(dragIndex, 1);
updatedDepartments.splice(hoverIndex, 0, dragRow);
setDepartments(updatedDepartments);
},
[departments]
);
return (
<div>
<DndProvider backend={HTML5Backend}>
<button
type="button"
className="btn btn-primary m-2 float-end"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={handleAddClick}
>
Add Department
</button>
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>
<th>
<div className="d-flex flex-row">
<input
className="form-control m-2"
onChange={handleFilterChange}
name="departmentIdFilter"
placeholder="Filter"
/>
<button
type="button"
className="btn btn-light"
onClick={() => sortResult("DepartmentId", true)}
>
โฒ
</button>
<button
type="button"
className="btn btn-light"
onClick={() => sortResult("DepartmentId", false)}
>
โผ
</button>
</div>
DepartmentId
</th>
<th>
<div className="d-flex flex-row">
<input
className="form-control m-2"
onChange={handleFilterChange}
name="departmentNameFilter"
placeholder="Filter"
/>
<button
type="button"
className="btn btn-light"
onClick={() => sortResult("DepartmentName", true)}
>
โฒ
</button>
<button
type="button"
className="btn btn-light"
onClick={() => sortResult("DepartmentName", false)}
>
โผ
</button>
</div>
DepartmentName
</th>
<th>Options</th>
</tr>
</thead>
<tbody>
{departments.map((dep, index) => (
<DraggableRow
key={dep.DepartmentId}
id={dep.DepartmentId}
index={index}
moveRow={moveRow}
>
<td>{dep.DepartmentId}</td>
<td>{dep.DepartmentName}</td>
<td>
<button
type="button"
className="btn btn-light mr-1"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={() => handleEditClick(dep)}
>
<FontAwesomeIcon icon={faEdit} />
</button>
<button
type="button"
className="btn btn-light mr-1"
onClick={() => handleDeleteClick(dep.DepartmentId)}
>
<FontAwesomeIcon icon={faTrash} />
</button>
</td>
</DraggableRow>
))}
</tbody>
</table>
</div>
</DndProvider>
<div
className="modal fade"
id="exampleModal"
tabIndex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div className="modal-dialog modal-lg modal-dialog-centered">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLabel">
{modalTitle}
</h5>
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
id="modalCloseButton"
></button>
</div>
<div className="modal-body">
<div className="input-group mb-3">
<span className="input-group-text">Department Name</span>
<input
type="text"
className="form-control"
value={departmentName}
onChange={(e) => setDepartmentName(e.target.value)}
/>
</div>
{departmentId === 0 ? (
<button
type="button"
className="btn btn-primary float-start"
onClick={handleCreateClick}
>
Create
</button>
) : (
<button
type="button"
className="btn btn-primary float-start"
onClick={handleUpdateClick}
>
Update
</button>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default Department;
The Employee component has similar functionality to the Department component but is focused on managing employee data.
import React, { useState, useEffect, useCallback } from "react";
import { variables } from "./Variables.js";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import "./index.css";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEdit, faTrash } from '@fortawesome/free-solid-svg-icons';
import DraggableRow from "./DraggableRow"; // Import the DraggableRow component
const ItemType = "EMPLOYEE";
const Employee = () => {
const [employees, setEmployees] = useState([]);
const [departments, setDepartments] = useState([]);
const [modalTitle, setModalTitle] = useState("");
const [employeeId, setEmployeeId] = useState(0);
const [employeeName, setEmployeeName] = useState("");
const [department, setDepartment] = useState("");
const [dateOfJoining, setDateOfJoining] = useState("");
const [photoFileName, setPhotoFileName] = useState("anonymous.png");
const [photoPath, setPhotoPath] = useState(variables.PHOTO_URL);
useEffect(() => {
refreshList();
}, []);
const refreshList = async () => {
try {
const empResponse = await fetch(variables.API_URL + "employee");
const empData = await empResponse.json();
setEmployees(empData);
const depResponse = await fetch(variables.API_URL + "department");
const depData = await depResponse.json();
setDepartments(depData);
} catch (error) {
console.error("Failed to fetch data:", error);
}
};
const handleAddClick = () => {
setModalTitle("Add Employee");
setEmployeeId(0);
setEmployeeName("");
setDepartment("");
setDateOfJoining("");
setPhotoFileName("anonymous.png");
};
const handleEditClick = (emp) => {
setModalTitle("Edit Employee");
setEmployeeId(emp.EmployeeId);
setEmployeeName(emp.EmployeeName);
setDepartment(emp.Department);
setDateOfJoining(emp.DateOfJoining);
setPhotoFileName(emp.PhotoFileName);
};
const handleCreateClick = async () => {
try {
const response = await fetch(variables.API_URL + "employee", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
EmployeeName: employeeName,
Department: department,
DateOfJoining: dateOfJoining,
PhotoFileName: photoFileName,
}),
});
const result = await response.json();
alert(result);
refreshList();
} catch (error) {
alert("Failed");
}
};
const handleUpdateClick = async () => {
try {
const response = await fetch(variables.API_URL + "employee", {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
EmployeeId: employeeId,
EmployeeName: employeeName,
Department: department,
DateOfJoining: dateOfJoining,
PhotoFileName: photoFileName,
}),
});
const result = await response.json();
document.getElementById('modalCloseButton').click();
refreshList();
} catch (error) {
alert("Failed");
}
};
const handleDeleteClick = async (id) => {
if (window.confirm("Are you sure?")) {
try {
const response = await fetch(variables.API_URL + "employee/" + id, {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
if (response.status === 204) {
alert("Employee deleted successfully");
} else {
const result = await response.json();
alert(result);
}
refreshList();
} catch (error) {
alert("Failed");
}
}
};
const handleImageUpload = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("file", e.target.files[0]);
try {
const response = await fetch(variables.API_URL + "employee/savefile", {
method: "POST",
body: formData,
});
const data = await response.json();
setPhotoFileName(data);
} catch (error) {
alert("Failed to upload image");
}
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = employees[dragIndex];
const updatedEmployees = [...employees];
updatedEmployees.splice(dragIndex, 1);
updatedEmployees.splice(hoverIndex, 0, dragRow);
setEmployees(updatedEmployees);
},
[employees]
);
return (
<div>
<DndProvider backend={HTML5Backend}>
<button
type="button"
className="btn btn-primary m-2 float-end"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={handleAddClick}
>
Add Employee
</button>
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>
<th>EmployeeId</th>
<th>EmployeeName</th>
<th>Department</th>
<th>DateOfJoining</th>
<th>PhotoFileName</th>
<th>Options</th>
</tr>
</thead>
<tbody>
{employees.map((emp, index) => (
<DraggableRow
key={emp.EmployeeId}
id={emp.EmployeeId}
index={index}
moveRow={moveRow}
>
<td>{emp.EmployeeId}</td>
<td>{emp.EmployeeName}</td>
<td>{emp.Department}</td>
<td>{emp.DateOfJoining}</td>
<td>
<img
width="50px"
height="50px"
src={photoPath + emp.PhotoFileName}
alt={emp.EmployeeName}
/>
</td>
<td>
<button
type="button"
className="btn btn-light mr-1"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
onClick={() => handleEditClick(emp)}
>
<FontAwesomeIcon icon={faEdit} />
</button>
<button
type="button"
className="btn btn-light mr-1"
onClick={() => handleDeleteClick(emp.EmployeeId)}
>
<FontAwesomeIcon icon={faTrash} />
</button>
</td>
</DraggableRow>
))}
</tbody>
</table>
</div>
</DndProvider>
<div
className="modal fade"
id="exampleModal"
tabIndex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div className="modal-dialog modal-lg modal-dialog-centered">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLabel">
{modalTitle}
</h5>
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
id="modalCloseButton"
></button>
</div>
<div className="modal-body">
<div className="input-group mb-3">
<span className="input-group-text">Employee Name</span>
<input
type="text"
className="form-control"
value={employeeName}
onChange={(e) => setEmployeeName(e.target.value)}
/>
</div>
<div className="input-group mb-3">
<span className="input-group-text">Department</span>
<select
className="form-control"
value={department}
onChange={(e) => setDepartment(e.target.value)}
>
{departments.map((dep) => (
<option key={dep.DepartmentId} value={dep.DepartmentName}>
{dep.DepartmentName}
</option>
))}
</select>
</div>
<div className="input-group mb-3">
<span className="input-group-text">Date Of Joining</span>
<input
type="date"
className="form-control"
value={dateOfJoining}
onChange={(e) => setDateOfJoining(e.target.value)}
/>
</div>
<div className="input-group mb-3">
<span className="input-group-text">Photo</span>
<input
type="file"
className="form-control"
onChange={handleImageUpload}
/>
</div>
<button
type="button"
className="btn btn-primary float-start"
onClick={employeeId === 0 ? handleCreateClick : handleUpdateClick}
>
{employeeId === 0 ? "Create" : "Update"}
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default Employee;
These components are designed to be modular and reusable, focusing on managing data with CRUD operations and enhancing user experience with sorting, filtering, and drag-and-drop features.
Remember, adaptability and clarity in your codebase not only benefit your current development efforts but also pave the way for smoother collaboration and growth in your software projects.
Happy coding!
Meet Dennis, a seasoned software engineer with 10 years of experience transforming ideas into digital reality. He has successfully guided countless projects from concept to deployment, bringing innovative solutions to life. With a passion for crafting exceptional software, Dennis has helped countless clients achieve their goals.
Click here to learn more
No popular posts available.
No related posts found.