Wednesday, February 19, 2025

React - Search and Pagination

To implement pagination similar to ngx-pagination in Angular. Here's how you can modify your EmployeesList component in React using react-paginate for efficient pagination:

Steps:

  1. Install react-paginate:

    npm install react-paginate

  2.  Install lodash (for efficient filtering)

          npm install lodash

Update your component to include pagination.

What’s Added?

✅ Pagination using react-paginate.
✅ Configurable items per page (change employeesPerPage).
✅ Page navigation (next/previous buttons).
✅ Search + Pagination (works together).
✅ State Management using useState for pagination.
✅ Lodash debounce for search optimization.

Uses lodash.debounce (Prevents excessive re-renders).


Updated EmployeesList.js with Pagination:

import { useEffect, useState,useCallback  } from "react";
import axios from "axios";
import { useNavigate, Link } from "react-router-dom";
import { debounce } from 'lodash';
import ReactPaginate from "react-paginate"; // Pagination component

const API_URL = "http://localhost:9090/api/employees";

const EmployeesList = () => {
    const [employees, setEmployees] = useState([]);
    const [filteredEmployees, setFilteredEmployees] = useState([]); // Filtered results
    const [search, setSearch] = useState("");
    const [page, setPage] = useState(1);
    const [pageNumber, setPageNumber] = useState(0);
    const employeesPerPage = 5; // Items per page
    const navigate = useNavigate();

    // Fetch employees on page load & when search/page changes
    useEffect(() => {
        fetchEmployees();
    }, []);

    const fetchEmployees = async () => {
        try {
            const response = await axios.get(API_URL);
            console.log("API Response:", response.data);
            if (Array.isArray(response.data)) {
                setEmployees(response.data);
                setFilteredEmployees(response.data); // Default to all employees
            }
        } catch (error) {
            console.error("Error fetching employees:", error);
            setEmployees([]);
            setFilteredEmployees([]);
        }
    };

     // Debounced Search Handler
     const handleSearchChange = useCallback(
        debounce((value) => {
            setSearch(value);
            if (!value) {
                setFilteredEmployees(employees);
            } else {
                setFilteredEmployees(
                    employees.filter((emp) =>
                        emp.empName.toLowerCase().includes(value.toLowerCase()) ||
                        emp.empPhoneNumber.includes(value) // Check phone number
                    )
                );
            }
        }, 300),
        [employees]
    );

    // Handle disabling an employee
    const handleDisable = async (empId) => {
        if (window.confirm("Are you sure you want to disable this employee?")) {
            try {
                await axios.put(`${API_URL}/disable/${empId}`, { empId });
                fetchEmployees(); // Refresh the list
            } catch (error) {
                console.error("Error disabling employee:", error);
            }
        }
    };

    const handleToggle = async (empId, currentStatus) => {
        const newStatus = !currentStatus; // Toggle active/inactive

        //if (window.confirm(`Are you sure you want to ${newStatus ? "enable" : "disable"} this employee?`)) {
        try {
            await axios.put(`${API_URL}/disable/${empId}/${newStatus}`);
            fetchEmployees(); // Refresh the list after update
        } catch (error) {
            console.error("Error updating employee status:", error);
        }
        //}
    };

    // Pagination logic
    const pagesVisited = pageNumber * employeesPerPage;
    const displayEmployees = filteredEmployees.slice(pagesVisited, pagesVisited + employeesPerPage);
    const pageCount = Math.ceil(filteredEmployees.length / employeesPerPage);

    const changePage = ({ selected }) => {
        setPageNumber(selected);
    };

    return (
        <div className="container-fluid mt-4">
            <div className="card shadow p-4">
                <h2 className="text-center mb-4 text-primary">Employee List</h2>

                {/* Search & Add Button */}
                <div className="d-flex justify-content-between align-items-center mb-3">
                    <div className="input-group w-50">
                        <span className="input-group-text">
                            <i className="fas fa-search"></i>
                        </span>
                        <input
                            type="text"
                            className="form-control"
                            placeholder="Search by name..."
                            defaultValue={search}
                            onChange={(e) => handleSearchChange(e.target.value)}
                        />
                    </div>

                    <Link to="/add" className="btn btn-success">
                        <i className="fas fa-user-plus"></i> Add Employee
                    </Link>
                </div>

                {/* Employee Table */}
                <table className="table table-striped table-hover">
                    <thead className="table-dark">
                        <tr>
                            <th>Name</th>
                            <th>Designation</th>
                            <th>Date of Join</th>
                            <th>Contact</th>
                            <th>Department</th>
                            <th>In Service</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {displayEmployees.length > 0 ? (
                            displayEmployees.map((emp) => (
                                <tr key={emp.empId}>
                                    <td>{emp.empName}</td>
                                    <td>{emp.designation}</td>
                                    <td>
                                        {new Date(emp.dateOfJoining).toLocaleDateString('en-GB', {
                                            day: '2-digit',
                                            month: 'short',
                                            year: 'numeric',
                                        })}
                                    </td>
                                    <td>{emp.empPhoneNumber}</td>
                                    <td>{emp.department?.deptName}</td>
                                    <td>
                                        {/* <button
                                            className={`btn btn-sm ${emp.isActive ? "btn-success" : "btn-danger"}`}
                                            onClick={() => handleToggle(emp.empId, emp.isActive)} // Ensure correct property name
                                        >
                                            {emp.isActive ? "Active" : "Inactive"}
                                        </button> */}
                                        <div className="form-check form-switch">
                                            <input
                                                className="form-check-input"
                                                type="checkbox"
                                                id={`toggle-${emp.empId}`}
                                                checked={emp.isActive}
                                                onChange={() => handleToggle(emp.empId, emp.isActive)}
                                                style={{
                                                    backgroundColor: emp.isActive ? "#28a745" : "#dc3545", // Green when Active, Red when Inactive
                                                    borderColor: emp.isActive ? "#28a745" : "#dc3545",
                                                    appearance: "none", // Remove default styles
                                                    width: "2.5rem", // Custom width
                                                    height: "1.4rem", // Custom height
                                                    borderRadius: "50px", // Make it rounded
                                                    position: "relative",
                                                    cursor: "pointer",
                                                    transition: "background-color 0.3s ease-in-out",
                                                }}
                                            />
                                            {/* <label className="form-check-label" htmlFor={`toggle-${emp.empId}`}>
                                                {emp.isActive ? "Active" : "Inactive"}
                                            </label> */}
                                        </div>
                                    </td>

                                    <td>
                                        {/* Edit Button */}
                                        <Link to={`/edit/${emp.empId}`} className="btn btn-primary btn-sm me-2">
                                            <i className="fas fa-edit"></i> Edit
                                        </Link>

                                        {/* Disable Button */}
                                        {/* <button className="btn btn-danger btn-sm" onClick={() => handleDisable(emp.empId)}>
                                            <i className="fas fa-user-slash"></i> Disable
                                        </button> */}

                                    </td>
                                </tr>
                            ))
                        ) : (
                            <tr>
                                <td colSpan="6" className="text-center text-muted">No employees found.</td>
                            </tr>
                        )}
                    </tbody>
                </table>

                {/* Pagination Controls
                <div className="d-flex justify-content-between mt-3">
                    <button className="btn btn-secondary" onClick={() => setPage(page - 1)} disabled={page === 1}>
                        <i className="fas fa-arrow-left"></i> Previous
                    </button>

                    <span className="align-self-center">Page {page} of {totalPages}</span>

                    <button className="btn btn-secondary" onClick={() => setPage(page + 1)} disabled={page >= totalPages}>
                        Next <i className="fas fa-arrow-right"></i>
                    </button>
                </div> */}

                 {/* Pagination Component */}
                 <ReactPaginate
                    previousLabel={"Previous"}
                    nextLabel={"Next"}
                    pageCount={pageCount}
                    onPageChange={changePage}
                    containerClassName={"pagination justify-content-end"}
                    previousLinkClassName={"page-link"}
                    nextLinkClassName={"page-link"}
                    pageClassName={"page-item"}
                    pageLinkClassName={"page-link"}
                    activeClassName={"active"}
                />
               
            </div>
        </div>
    );
};

export default EmployeesList;



No comments:

Post a Comment