I am having a problem with the dropdown menu, which is not visible or hides behind the div when the table is finished. Here is the snippet of code where I think the error is:
import React, { useState, useRef, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Link, useNavigate } from 'react-router-dom'; import service from '../server/entry'; import { completeEntrySuccess,deleteEntrySuccess } from '../store/slice/entrySlice';
function Entries() {
const entries = useSelector(state => state.entry.entries);
const customers = useSelector(state => state.customer.customers);
const navigate = useNavigate();
const dispatch = useDispatch();
const [searchQuery, setSearchQuery] = useState('');
const [sortCriteria, setSortCriteria] = useState('');
const [selectedEntry, setSelectedEntry] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [statusFilter, setStatusFilter] = useState('');
const entriesPerPage = 10;
const dropdownRefs = useRef({});
const getCustomerName = (customerId) => {
const customer = customers.find(customer => customer.id === customerId);
return customer ? customer.name : 'Unknown Customer';
};
const formatDate = (dateString) => {
if (!dateString) return 'N/A';
const options = { year: 'numeric', month: 'long', day: 'numeric' };
return new Date(dateString).toLocaleDateString(undefined, options);
};
const navigateToUpdate = (entryId) => {
navigate(`/entry/edit/${entryId}`);
};
const handleDelete = async (entryId) => {
try {
await service.deleteEntry(entryId);
console.log(`Successfully deleted entry with ID ${entryId}`);
dispatch(deleteEntrySuccess(entryId));
} catch (error) {
console.error(`Error deleting entry with ID ${entryId}:`, error);
}
};
const handleComplete = async (entryId) => {
const updatedEntry = await service.completeEntry(entryId);
dispatch(completeEntrySuccess({ entryId, updatedEntry }));
};
const handleSearchChange = (event) => {
setSearchQuery(event.target.value);
setCurrentPage(1);
};
const handleSortChange = (event) => {
setSortCriteria(event.target.value);
setCurrentPage(1);
};
const toggleDropdown = (entryId, event) => {
setSelectedEntry(selectedEntry === entryId ? null : entryId);
if (dropdownRefs.current[entryId]) {
const dropdownMenu = dropdownRefs.current[entryId];
const rect = dropdownMenu.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
// Calculate whether to show the dropdown above or below
const shouldDropdownBeAbove = spaceBelow < rect.height && spaceAbove > rect.height;
// Toggle Tailwind classes based on the above calculation
dropdownMenu.classList.toggle('origin-top', shouldDropdownBeAbove);
dropdownMenu.classList.toggle('origin-bottom', !shouldDropdownBeAbove);
dropdownMenu.classList.toggle('transform', true);
// Ensure the menu is visible
dropdownMenu.classList.toggle('hidden', selectedEntry !== entryId);
}
};
const handleOutsideClick = (event) => {
if (!event.target.closest('.relative')) {
setSelectedEntry(null);
}
};
useEffect(() => {
document.addEventListener('mousedown', handleOutsideClick);
return () => {
document.removeEventListener('mousedown', handleOutsideClick);
};
}, []);
const handleStatusFilterChange = (event) => {
setStatusFilter(event.target.value);
setCurrentPage(1);
};
const filteredEntries = entries.filter(entry =>
(statusFilter === '' || entry.status === statusFilter) &&
(
getCustomerName(entry.customer_id)?.toLowerCase().includes(searchQuery.toLowerCase()) ||
entry.document_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
entry.submitors_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
entry.collector_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
entry.status?.toLowerCase().includes(searchQuery.toLowerCase())
)
);
const sortedEntries = [...filteredEntries].sort((a, b) => {
switch (sortCriteria) {
case 'date':
return new Date(a.reception_date) - new Date(b.reception_date);
case 'document_name':
return getCustomerName(a.customer_id).localeCompare(getCustomerName(b.customer_id));
case 'customer':
return getCustomerName(a.customer_id).localeCompare(getCustomerName(b.customer_id));
default:
return 0;
}
});
const startIndex = (currentPage - 1) * entriesPerPage;
const paginatedEntries = sortedEntries.slice(startIndex, startIndex + entriesPerPage);
const totalPages = Math.ceil(sortedEntries.length / entriesPerPage);
const handleNextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const handlePrevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
return (
<div className='flex flex-col items-center p-4'>
<div className='mb-4 flex flex-col sm:flex-row sm:justify-between w-full max-w-6xl'>
<Link className='bg-slate-600 text-white py-2 px-4 rounded hover:bg-slate-700 transition duration-200' to='/entry/add'>
Add Entry
</Link>
<div className='flex mt-4 sm:mt-0'>
<select value={statusFilter} onChange={handleStatusFilterChange} className='p-2 border rounded'>
<option value=''>All Statuses</option>
<option value='active'>Active</option>
<option value='completed'>Completed</option>
<option value='pending'>Pending</option>
</select>
<input
type='text'
placeholder='Search...'
value={searchQuery}
onChange={handleSearchChange}
className='p-2 border rounded mx-2'
/>
<select value={sortCriteria} onChange={handleSortChange} className='p-2 border rounded'>
<option value=''>Sort By</option>
<option value='date'>Date</option>
<option value='document_name'>Name</option>
<option value='customer'>Customer</option>
</select>
</div>
</div>
<div className="w-full max-w-6xl">
{filteredEntries.length === 0 ? (
<div className="text-center text-gray-500">No entries available</div>
) : (
<div className="overflow-x-auto z-10">
<table className="min-w-full bg-white shadow-md rounded-lg overflow-hidden">
<thead className="bg-gray-200">
<tr>
<th onClick={() => handleSortChange('customer')} className="py-2 px-4 text-left cursor-pointer">Customer Name</th>
<th onClick={() => handleSortChange('document_name')} className="py-2 px-4 text-left cursor-pointer">Document Name</th>
<th onClick={() => handleSortChange('date')} className="py-2 px-4 text-left cursor-pointer">Reception Date</th>
<th className="py-2 px-4 text-left">Submitter's Name</th>
<th className="py-2 px-4 text-left">Restitution Date</th>
<th className="py-2 px-4 text-left">Collector's Name</th>
<th className="py-2 px-4 text-left">Status</th>
<th className="py-2 px-4 text-left">Actions</th>
</tr>
</thead>
<tbody>
{paginatedEntries.map((entry, index) => (
<tr key={entry.id} className={`${index % 2 === 0 ? 'bg-gray-100' : 'bg-white'}`}>
<td className="py-2 px-4">{getCustomerName(entry.customer_id)}</td>
<td className="py-2 px-4">{entry.document_name}</td>
<td className="py-2 px-4">{formatDate(entry.reception_date)}</td>
<td className="py-2 px-4">{entry.submitors_name}</td>
<td className="py-2 px-4">{formatDate(entry.restitution_date)}</td>
<td className="py-2 px-4">{entry.collector_name}</td>
<td className="py-2 px-4">{entry.status}</td>
<td className="py-2 px-4 relative">
{entry.status === 'active' ? (
<button
className="bg-blue-500 hover:bg-blue-700 text-white py-1 px-3 rounded transition duration-200 mr-2"
onClick={() => handleComplete(entry.id)}
>
Complete
</button>
) : (
<button className="bg-green-500 text-white py-1 px-3 rounded transition duration-200 mr-2">
Completed
</button>
)}
<div className="relative inline-block text-left z-200">
<button
onClick={(event) => toggleDropdown(entry.id, event)}
type="button"
className="bg-gray-300 rounded-full h-6 w-6 z-0 flex items-center justify-center hover:bg-gray-400 transition duration-200"
id={`options-menu-${entry.id}`}
aria-haspopup="true"
aria-expanded={selectedEntry === entry.id ? 'true' : 'false'}
>
<svg className="h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M3 9a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9zm14-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zM7 13a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm10 0a2 2 0 1 1-4 0 2 2 0 0 1 4 0z" clipRule="evenodd" />
</svg>
</button>
<div
ref={el => dropdownRefs.current[entry.id] = el}
className={`origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-20 ${selectedEntry === entry.id ? '' : 'hidden'}`}
role="menu"
aria-orientation="vertical"
aria-labelledby={`options-menu-${entry.id}`}
>
<div className="py-1" role="none">
<button
onClick={() => navigateToUpdate(entry.id)}
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
role="menuitem"
>
Update
</button>
<button
onClick={() => handleDelete(entry.id)}
className="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100 w-full text-left"
role="menuitem"
>
Delete
</button>
</div>
</div>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<div className="flex justify-between mt-4">
<div className="text-sm text-gray-700">
Showing page {currentPage} of {totalPages}
</div>
<div>
<button
onClick={handlePrevPage}
disabled={currentPage === 1}
className={`bg-gray-500 text-white py-2 px-4 rounded ${currentPage === 1 ? 'disabled:bg-gray-300 cursor-not-allowed' : 'hover:bg-gray-700 transition duration-200'}`}
>
Previous
</button>
<button
onClick={handleNextPage}
disabled={currentPage >= totalPages}
className={`ml-2 bg-gray-500 text-white py-2 px-4 rounded ${currentPage >= totalPages ? 'disabled:bg-gray-300 cursor-not-allowed' : 'hover:bg-gray-700 transition duration-200'}`}
>
Next
</button>
</div>
</div>
</div> </div>
); }
export default Entries;
This is image of first row where dropdown is visible
This is image of second row where dropdown is not visible
if you can look at the 2nd image at end of table where dropdown is supposed to be, there is a dark background indication is opening but being hidden behind the background