react-beautiful-grid-shahab
Version:
346 lines (325 loc) • 14.1 kB
JSX
/** @format */
import React, { useState } from "react";
import { BsThreeDotsVertical } from "react-icons/bs";
import { MdFilterListAlt } from "react-icons/md";
import { BsPaperclip } from "react-icons/bs";
import { IoMdArrowDropup } from "react-icons/io";
import { IoMdArrowDropdown } from "react-icons/io";
import { PiLessThan } from "react-icons/pi";
import { PiGreaterThan } from "react-icons/pi";
import { BsSliders } from "react-icons/bs";
import FilterModal from "../modals/FilterModal";
import SortedModal from "../modals/SortedModal";
import DeleteModal from "../modals/DeleteModal";
const PaginatedTable = ({
data,
columns: initialColumns,
headerBackgroundColor,
}) => {
const [currentPage, setCurrentPage] = useState(1);
const [selectedRows, setSelectedRows] = useState([]);
const [columns, setColumns] = useState([...new Set(initialColumns)]);
const [showModal, setShowModal] = useState(false);
const [filterSearch, setFilterSearch] = useState("");
const [sortedColumn, setSortedColumn] = useState(null);
const [sortedData, setSortedData] = useState(data);
const [modalData, setModalData] = useState([]);
const [showFilterModal, setShowFilterModal] = useState(false);
const [showSortedModal, setShowSortedModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [modalPosition, setModalPosition] = useState({ x: 0, y: 0 });
const [sortDirection, setSortDirection] = useState({});
const rowsPerPage = 10;
const totalPages = Math.ceil(sortedData.length / rowsPerPage);
const currentRows = sortedData.slice(
(currentPage - 1) * rowsPerPage,
currentPage * rowsPerPage
);
// <--------------------- Sorting logic ------------------------>
const handleSort = (columnName, direction) => {
const sorted = [...sortedData].sort((a, b) => {
if (direction === "asc") {
return a[columnName] > b[columnName] ? 1 : -1;
} else {
return a[columnName] < b[columnName] ? 1 : -1;
}
});
setSortedColumn(columnName);
setSortedData(sorted);
setSortDirection({ [columnName]: direction }); // Update the direction for the column
};
// <-------------------- OPEN AND CLOSE MODALS LOGIC -------------------------->
const handleFilterClick = () => {
setShowFilterModal(true);
};
const openSortModal = (columnName, index) => {
const uniqueValues = [...new Set(data.map((item) => item[columnName]))];
setModalData(uniqueValues);
setShowSortedModal(showSortedModal === index ? null : index); // Open the sorted modal
};
const closeModal = () => {
setShowFilterModal(false);
setShowSortedModal(false);
};
// <-------------------- OPEN AND CLOSE MODALS FILTER LOGIC -------------------------->
const openFilterModal = (columnName) => {
const uniqueValues = [...new Set(data.map((item) => item[columnName]))];
setModalData(uniqueValues);
setSortedColumn(columnName);
setShowSortedModal(true);
};
const applyFilter = (selectedValues) => {
if (sortedColumn) {
const filteredData = data.filter((row) =>
selectedValues.includes(row[sortedColumn])
);
setSortedData(filteredData);
}
closeModal();
};
const clearFilter = () => {
setSortedData(data);
setSortedColumn(null);
};
// <-------------------- ADD AND DELETE COLUMN ON CHECKBOX -------------------------->
const handleColumnToggle = (columnName, checked) => {
const columnIndex = initialColumns.indexOf(columnName);
if (checked) {
const updatedColumns = [...columns];
updatedColumns.splice(columnIndex, 0, columnName);
setColumns(updatedColumns);
} else {
setColumns((prev) => prev.filter((col) => col !== columnName));
}
};
// <-------------------------- FUNCTION FOR DRAG AND DROP --------------------------->
const handleDragStart = (e, index) => {
e.dataTransfer.setData("draggedIndex", index);
};
const handleDrop = (e, index) => {
const draggedIndex = e.dataTransfer.getData("draggedIndex");
const updatedColumns = [...columns];
const draggedColumn = updatedColumns.splice(draggedIndex, 1)[0];
updatedColumns.splice(index, 0, draggedColumn);
setColumns(updatedColumns);
};
const handleInputChange = (rowId, column, value) => {
setSortedData((prevData) =>
prevData.map((row) =>
row.id === rowId ? { ...row, [column]: value } : row
)
);
};
// <--------------------- For Selecting all checkbox on One ------------------------->
const handleSelectAll = (checked) => {
if (checked) {
const allCurrentRowIds = currentRows.map((row) => row.id);
setSelectedRows([...new Set([...selectedRows, ...allCurrentRowIds])]);
} else {
const remainingRows = selectedRows.filter(
(id) => !currentRows.map((row) => row.id).includes(id)
);
setSelectedRows(remainingRows);
}
};
// <--------------------- For Selecting all checkbox individually ------------------------->
const handleRowSelection = (id, checked) => {
if (checked) {
setSelectedRows((prev) => [...prev, id]);
} else {
setSelectedRows((prev) => prev.filter((rowId) => rowId !== id));
}
};
const handleOpenDeleteModal = (id) => {
setModalPosition({ x: event.clientX, y: event.clientY });
setOpenDeleteModal(true); // Set the index of the icon
};
const handleCloseDeleteModal = () => {
setOpenDeleteModal(false); // Close the modal
};
return (
<>
<section className='p-4 '>
<div className=' overflow-y-auto'>
<div className='overflow-auto max-h-[400px] border border-gray-300 rounded-lg'>
<table className='w-full border-collapse table-fixed'>
<thead>
<tr className={`${headerBackgroundColor} sticky top-0 z-20`}>
{/* First Header Cell - Sticky Horizontally and Vertically */}
<th className='bg-blue-700 border border-gray-300 px-4 py-2 text-left w-[50px] sticky top-0 left-0 z-30'>
<input
type='checkbox'
onChange={(e) => handleSelectAll(e.target.checked)}
checked={currentRows.every((row) =>
selectedRows.includes(row.id)
)}
/>
</th>
{/* Second Header Cell - Sticky Horizontally and Vertically */}
<th className='bg-blue-700 border border-gray-300 px-4 py-2 w-[80px] sticky top-0 left-[50px] z-30'>
<button onClick={handleFilterClick}>
<BsSliders className='w-6 h-6 text-white' />
</button>
</th>
{/* Other Headers */}
{columns.map((col, index) => (
<th
key={index}
className='border border-gray-300 px-4 py-2 text-white w-[150px] sticky top-0 z-10'
draggable
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => handleDrop(e, index)}
style={{ cursor: "move" }}>
<div className='flex items-center justify-between'>
<span onClick={() => handleSort(col)}>{col}</span>
<div className='flex items-center space-x-2'>
{(col === "Caller Name" ||
col === "Division" ||
col === "Reg No" ||
col === "Full Name" ||
col === "Duty") && (
<div>
<IoMdArrowDropup
className={`ml-2 font-bold cursor-pointer ${
sortDirection[col] === "asc"
? "text-gray-700"
: "text-white"
}`}
onClick={() => handleSort(col, "asc")}
/>
<IoMdArrowDropdown
className={`ml-2 font-bold cursor-pointer ${
sortDirection[col] === "desc"
? "text-gray-700"
: "text-white"
}`}
onClick={() => handleSort(col, "desc")}
/>
</div>
)}
{col === "Encounter Time" && (
<button
onClick={() => openFilterModal(col, index)}
className='ml-2'>
<MdFilterListAlt className='w-4 h-4 relative' />
</button>
)}
{col === "Division" && (
<button
onClick={() => handleSort(col)}
className='ml-2'>
<MdFilterListAlt className='w-4 h-4 relative' />
</button>
)}
</div>
</div>
</th>
))}
</tr>
</thead>
<tbody>
{currentRows.map((row, id) => (
<tr key={row.id}>
{/* First Sticky Cell - Body */}
<td className='left-0 bg-gray-100 border border-gray-300 px-4 py-2 w-[50px] sticky top-0 z-10'>
<input
type='checkbox'
checked={selectedRows.includes(row.id)}
onChange={(e) =>
handleRowSelection(row.id, e.target.checked)
}
/>
</td>
{/* Second Sticky Cell - Body */}
<td className='left-[50px] bg-gray-100 border border-gray-300 px-4 py-2 w-[80px] sticky top-0 z-10'>
<div className='flex'>
<BsPaperclip className='w-4 h-4 text-blue-700' />
<BsThreeDotsVertical
className='w-4 h-4 cursor-pointer text-gray-500 relative'
onClick={() => handleOpenDeleteModal(id)}
/>
</div>
</td>
{columns.map((col, colIndex) => (
<td
key={colIndex}
className={`border border-gray-300 px-4 py-2 w-[150px] ${
col === "Address"
? "truncate overflow-hidden whitespace-nowrap text-ellipsis"
: ""
}`}>
{col === "Encounter No" ? (
<span>
<input
type='text'
className='bg-transparent w-24 outline-none focus:outline-none'
value={row[col] || ""}
onChange={(e) =>
handleInputChange(row.id, col, e.target.value)
}
/>
</span>
) : (
row[col] || ""
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* <----------------------- PAGINATION --------------------------> */}
<div className='mt-4 flex justify-end space-x-2'>
<button
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}>
<PiLessThan className='w-4 h-4 font-bold' />
</button>
{Array.from({ length: totalPages }, (_, index) => (
<button
key={index}
onClick={() => setCurrentPage(index + 1)}
className={`px-2 rounded ${
currentPage === index + 1
? "border border-blue-500 text-gray-600"
: "border border-blue-500"
}`}>
{index + 1}
</button>
))}
<button
onClick={() =>
setCurrentPage((prev) => Math.min(prev + 1, totalPages))
}>
<PiGreaterThan className='w-4 h-4 font-bold' />
</button>
</div>
{/* <--------------------------- SORTED MODAL ------------------------> */}
<SortedModal
showModal={showSortedModal}
modalData={modalData}
applyFilter={applyFilter}
clearFilter={clearFilter}
closeModal={closeModal}
/>
<FilterModal
showModal={showFilterModal}
filterSearch={filterSearch}
setFilterSearch={setFilterSearch}
columns={columns}
initialColumns={initialColumns}
handleColumnToggle={handleColumnToggle}
closeModal={closeModal}
/>
<DeleteModal
isOpen={openDeleteModal}
handleClose={handleCloseDeleteModal}
modalPosition={modalPosition}
/>
</section>
</>
);
};
export default PaginatedTable;