bug fixed
This commit is contained in:
parent
b2a01bb935
commit
a381b5d30b
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
|
||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
||||
@ -40,6 +40,35 @@ const AdminHeader = () => {
|
||||
const [userName, setUserName] = React.useState('User');
|
||||
const [userEmail, setUserEmail] = React.useState('user@example.com');
|
||||
const navigate = useNavigate();
|
||||
const profileRef = useRef(null);
|
||||
const mobileMenuRef = useRef(null);
|
||||
const profileButtonRef = useRef(null);
|
||||
const mobileMenuButtonRef = useRef(null);
|
||||
|
||||
// Handle click outside for profile dropdown and mobile menu
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
// Close profile dropdown if clicked outside
|
||||
if (profileOpen && profileRef.current && !profileRef.current.contains(event.target) &&
|
||||
profileButtonRef.current && !profileButtonRef.current.contains(event.target)) {
|
||||
setProfileOpen(false);
|
||||
}
|
||||
|
||||
// Close mobile menu if clicked outside
|
||||
if (open && mobileMenuRef.current && !mobileMenuRef.current.contains(event.target) &&
|
||||
mobileMenuButtonRef.current && !mobileMenuButtonRef.current.contains(event.target)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener when the component mounts
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
// Clean up the event listener when the component unmounts
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [open, profileOpen]);
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
@ -90,8 +119,9 @@ const AdminHeader = () => {
|
||||
>
|
||||
<img src={bellIconSrc} alt="Notifications" className="h-[18px] w-[18px]" />
|
||||
</button> */}
|
||||
<div className="relative">
|
||||
<div className="relative" ref={profileRef}>
|
||||
<button
|
||||
ref={profileButtonRef}
|
||||
type="button"
|
||||
onClick={() => setProfileOpen((value) => !value)}
|
||||
className="h-9 w-9 rounded-full bg-[#F2F2F2] grid place-items-center text-[13px] font-medium text-[#232528]"
|
||||
@ -137,8 +167,9 @@ const AdminHeader = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto lg:hidden">
|
||||
<div className="ml-auto lg:hidden" ref={mobileMenuRef}>
|
||||
<button
|
||||
ref={mobileMenuButtonRef}
|
||||
type="button"
|
||||
aria-label="Open Menu"
|
||||
className="inline-flex items-center justify-center h-10 w-10 rounded-full border border-[#E5E7EB]"
|
||||
|
||||
@ -15,6 +15,7 @@ const clockIcon = '/assets/images/mingcute_time-line.svg';
|
||||
const calendarIcon = '/assets/images/Duedate.svg';
|
||||
const nextIcon = '/assets/images/mdi_page-next-outline.svg';
|
||||
const trashActiveSrc = '/assets/images/Trash - active.svg';
|
||||
const searchIcon = '/assets/images/material-symbols_search-rounded.svg';
|
||||
|
||||
const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {}, required = false, error = false }) => {
|
||||
const [isFocused, setIsFocused] = React.useState(false);
|
||||
@ -46,6 +47,135 @@ const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {
|
||||
|
||||
|
||||
|
||||
const SearchableSelect = ({
|
||||
placeholder = '',
|
||||
options = [],
|
||||
value = '',
|
||||
onChange,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
error = false,
|
||||
className = ''
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const selectRef = React.useRef(null);
|
||||
const inputRef = React.useRef(null);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
React.useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (selectRef.current && !selectRef.current.contains(event.target)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Focus input when dropdown opens
|
||||
React.useEffect(() => {
|
||||
if (isOpen && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// Filter options based on search term
|
||||
const filteredOptions = React.useMemo(() => {
|
||||
if (!searchTerm) return options;
|
||||
const term = searchTerm.toLowerCase();
|
||||
return options.filter(option => {
|
||||
const label = option.label?.toLowerCase() || '';
|
||||
const value = option.value?.toString().toLowerCase() || '';
|
||||
return label.includes(term) || value.includes(term);
|
||||
});
|
||||
}, [options, searchTerm]);
|
||||
|
||||
// Get selected option label
|
||||
const selectedOption = options.find(opt => opt.value === value);
|
||||
const displayValue = selectedOption ? selectedOption.label : '';
|
||||
|
||||
// Handle option selection
|
||||
const handleSelect = (option) => {
|
||||
if (onChange) {
|
||||
const event = { target: { value: option.value } };
|
||||
onChange(event);
|
||||
}
|
||||
setSearchTerm('');
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
let borderColor = 'border-[#CBA344]';
|
||||
let bgColor = 'bg-white';
|
||||
|
||||
if (error) {
|
||||
borderColor = 'border-red-500';
|
||||
bgColor = 'bg-red-50';
|
||||
} else if (value || isOpen) {
|
||||
borderColor = 'border-[#92722A]';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-full" ref={selectRef}>
|
||||
<div
|
||||
className={`flex items-center justify-between h-10 px-4 py-2 text-sm rounded-md border-2 ${borderColor} ${bgColor} ${disabled ? 'bg-gray-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
onClick={() => !disabled && !loading && setIsOpen(!isOpen)}
|
||||
>
|
||||
<span className="truncate">{displayValue || placeholder}</span>
|
||||
{loading ? (
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-[#92722A] border-t-transparent ml-2" />
|
||||
) : (
|
||||
<img src={caretDownSrc} alt="open" className="h-4 w-4 ml-2" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg border border-gray-200">
|
||||
<div className="p-2 border-b border-gray-200">
|
||||
<div className="relative">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search..."
|
||||
className="w-full h-9 pl-8 pr-3 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<img
|
||||
src={searchIcon}
|
||||
alt="Search"
|
||||
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-60 overflow-auto">
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={`px-4 py-2 text-sm cursor-pointer hover:bg-gray-100 ${
|
||||
value === option.value ? 'bg-[#F5F0E1]' : ''
|
||||
}`}
|
||||
onClick={() => handleSelect(option)}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="px-4 py-2 text-sm text-gray-500">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Keep the original Select component for other dropdowns
|
||||
const Select = ({
|
||||
placeholder = '',
|
||||
options = [],
|
||||
@ -847,8 +977,8 @@ const location = useLocation();
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Product (HS Code - Name) <span className="text-red-500">*</span></label>
|
||||
<div>
|
||||
<Select
|
||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
|
||||
<SearchableSelect
|
||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Search product HS code...'}
|
||||
options={productOptions}
|
||||
value={p.productId || p.product || ''}
|
||||
onChange={async (e) => {
|
||||
@ -927,11 +1057,20 @@ const location = useLocation();
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Unit <span className="text-red-500">*</span></label>
|
||||
<Select
|
||||
placeholder={isLoadingUnits ? 'Loading units...' : 'Select unit'}
|
||||
<SearchableSelect
|
||||
placeholder={isLoadingUnits ? 'Loading units...' : 'Search unit...'}
|
||||
options={unitOptions}
|
||||
value={p.unit || ''}
|
||||
onChange={(e) => updateProductField(p.id, 'unit', e.target.value, unitOptions)}
|
||||
value={p.unit}
|
||||
onChange={(e) => {
|
||||
// Clear error when user selects a unit
|
||||
if (formErrors[`unit_${idx}`]) {
|
||||
const newErrors = { ...formErrors };
|
||||
delete newErrors[`unit_${idx}`];
|
||||
setFormErrors(newErrors);
|
||||
}
|
||||
updateProductField(p.id, 'unit', e.target.value, unitOptions);
|
||||
}}
|
||||
loading={isLoadingUnits}
|
||||
error={!!formErrors[`unit_${idx}`]}
|
||||
/>
|
||||
<div className="h-5">
|
||||
|
||||
@ -133,14 +133,14 @@ const AdminDashboard = () => {
|
||||
onChange={(e) => setSelectedYear(e.target.value)}
|
||||
className="border border-[#D0D5DD] rounded-md h-8 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
||||
>
|
||||
{/* <option>All</option> */}
|
||||
<option>2028</option>
|
||||
<option>2027</option>
|
||||
<option>2026</option>
|
||||
<option>2025</option>
|
||||
<option>2024</option>
|
||||
<option>2023</option>
|
||||
<option>2022</option>
|
||||
{Array.from(
|
||||
{ length: new Date().getFullYear() - 2021 },
|
||||
(_, i) => new Date().getFullYear() - i
|
||||
).map((year) => (
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -752,7 +752,7 @@ const ManageSubmissions = () => {
|
||||
<p className="mt-1 text-sm text-red-600">Reason for rejection is required</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3">
|
||||
<div className="mt-4 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCloseRejectModal}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user