A
Anonymous

Staff Page - Copy this React, Tailwind Component to your project

Import React, { useEffect, useState, useMemo } from "react"; import axios from "axios"; import DateRangePicker from "../toold/DateRangePicker.jsx"; import SalesmanModal from "./SalesmanModal.jsx"; import { FiSearch } from "react icons/fi"; import { ToastContainer, toast } from "react toastify"; import "react toastify/dist/ReactToastify.css"; const StaffPage = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage] = useState(10); // Number of items per page const [selectedRows, setSelectedRows] = useState([]); const [isModalOpen, setModalOpen] = useState(false); const fetchData = async () => { if (!startDate || !endDate) { console.log("Start date or end date is missing."); return; } setLoading(true); try { console.log("Fetching data with:", { startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0], }); const response = await axios.get( `https://ranka.nuke.co.in/backend/api/visitors/by date range`, { params: { startDate: startDate.toISOString().split("T")[0], endDate: endDate.toISOString().split("T")[0], }, } ); console.log("Data fetched successfully:", response.data); setData(response.data); setCurrentPage(1); // Reset to first page after fetching new data } catch (error) { console.error("Error fetching data:", error); toast.error("Failed to load data."); } finally { setLoading(false); } }; useEffect(() => { if (startDate && endDate) { fetchData(); } }, [startDate, endDate]); // Pagination logic const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem itemsPerPage; const currentItems = useMemo(() => { return data.slice(indexOfFirstItem, indexOfLastItem); }, [data, currentPage]); const totalPages = Math.ceil(data.length / itemsPerPage); const handlePageChange = (pageNumber) => { setCurrentPage(pageNumber); }; const handleSearchChange = (event) => { setSearchTerm(event.target.value); }; const toggleRowSelection = (id) => { setSelectedRows((prev) => prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id] ); }; const toggleSelectAll = () => { if (selectedRows.length === currentItems.length) { setSelectedRows([]); } else { setSelectedRows(currentItems.map((row) => row.id)); } }; const filteredData = useMemo(() => { return currentItems.filter((item) => { return Object.values(item) .join(" ") .toLowerCase() .includes(searchTerm.toLowerCase()); }); }, [currentItems, searchTerm]); return ( <div className="p 6 bg gray 100 min h screen"> <h1 className="text 4xl font bold text center text gray 900 mb 6 shadow lg p 4"> Visitors </h1> <button className="bg blue 500 text white px 4 py 2 rounded mb 4" onClick={() => setModalOpen(true)} > Manage Salesmen </button> <SalesmanModal isOpen={isModalOpen} onClose={() => setModalOpen(false)} /> <DateRangePicker onApply={({ startDate, endDate }) => { setStartDate(startDate); setEndDate(endDate); }} /> {/* Search and Filter Header */} <div className="flex justify between items center mt 4 mb 4"> <div className="relative flex grow min w [200px]"> <input type="text" value={searchTerm} onChange={handleSearchChange} placeholder="Search..." className="w full px 4 py 2 border rounded lg focus:ring 2 focus:ring blue 300" /> <FiSearch className="absolute left 3 top 3 text gray 400" /> </div> </div> {loading ? ( <p className="text blue 500 mt 4">Loading...</p> ) : ( <div className="overflow x auto mt 6"> <table className="table auto w full bg white shadow lg rounded lg"> <thead> <tr className="bg gray 200"> <th className="px 4 py 2"> <input type="checkbox" onChange={toggleSelectAll} checked={selectedRows.length === filteredData.length} /> </th> <th className="px 4 py 2">ID</th> <th className="px 4 py 2">Name</th> <th className="px 4 py 2">Mobile No</th> <th className="px 4 py 2">Salesman</th> <th className="px 4 py 2">Company Name</th> <th className="px 4 py 2">District</th> <th className="px 4 py 2">Visit Count</th> </tr> </thead> <tbody> {filteredData.length > 0 ? ( filteredData.map((item) => ( <tr key={item.id} className="text center hover:bg gray 100"> <td className="border px 4 py 2"> <input type="checkbox" onChange={() => toggleRowSelection(item.id)} checked={selectedRows.includes(item.id)} /> </td> <td className="border px 4 py 2">{item.id}</td> <td className="border px 4 py 2">{item.name}</td> <td className="border px 4 py 2">{item.primary_mobile_no || "N/A"}</td> <td className="border px 4 py 2">{item.salesman || "N/A"}</td> <td className="border px 4 py 2">{item.company_name}</td> <td className="border px 4 py 2">{item.dist}</td> <td className="border px 4 py 2">{item.visit_count || 0}</td> </tr> )) ) : ( <tr> <td colSpan="8" className="text center py 4 text gray 500"> No data available for the selected date range. </td> </tr> )} </tbody> </table> {/* Pagination Controls */} <div className="flex justify center mt 4"> {Array.from({ length: totalPages }, (_, index) => ( <button key={index + 1} onClick={() => handlePageChange(index + 1)} className={`mx 1 px 4 py 2 border rounded lg ${currentPage === index + 1 ? "bg blue 500 text white" : "bg gray 200" }`} > {index + 1} </button> ))} </div> </div> )} {/* Toast Notifications */} <ToastContainer position="top right" autoClose={5000} /> </div> ); }; export default StaffPage;

Prompt
Component Preview

About

StaffPage - A dynamic staff management tool with search, date filtering, pagination, and modal features, built with React and Tailwind. Get code instantly!

Share

Last updated 1 month ago