added pdf link for products

This commit is contained in:
Malini 2025-11-27 13:28:30 +05:30
parent 49b14861aa
commit d3d9167de2
3 changed files with 155 additions and 36 deletions

View File

@ -524,6 +524,9 @@ const CompanyProfile = () => {
const [currentUser, setCurrentUser] = React.useState(null); const [currentUser, setCurrentUser] = React.useState(null);
const [products, setProducts] = React.useState([]); const [products, setProducts] = React.useState([]);
const [availableProducts, setAvailableProducts] = React.useState([]); const [availableProducts, setAvailableProducts] = React.useState([]);
const [visibleProductCount, setVisibleProductCount] = React.useState(14);
const [isLoadingMore, setIsLoadingMore] = React.useState(false);
const productListRef = React.useRef(null);
const [selectedProducts, setSelectedProducts] = React.useState([]); const [selectedProducts, setSelectedProducts] = React.useState([]);
const [productSearchTerm, setProductSearchTerm] = React.useState(''); const [productSearchTerm, setProductSearchTerm] = React.useState('');
const [isLoadingProducts, setIsLoadingProducts] = React.useState(false); const [isLoadingProducts, setIsLoadingProducts] = React.useState(false);
@ -547,6 +550,30 @@ const CompanyProfile = () => {
const filteredAvailableProducts = availableProducts.filter( const filteredAvailableProducts = availableProducts.filter(
(product) => !selectedProducts.some((sp) => sp.id === product.id) (product) => !selectedProducts.some((sp) => sp.id === product.id)
); );
// Reset visible count when products or search term changes
React.useEffect(() => {
setVisibleProductCount(14);
}, [availableProducts.length, productSearchTerm]);
// Handle scroll for infinite loading
const handleScroll = React.useCallback((e) => {
if (isLoadingMore || visibleProductCount >= availableProducts.length) return;
const list = e.target;
const { scrollTop, scrollHeight, clientHeight } = list;
const isNearBottom = scrollTop + clientHeight >= scrollHeight - 100; // 100px from bottom
if (isNearBottom) {
setIsLoadingMore(true);
// Simulate loading delay for better UX
setTimeout(() => {
setVisibleProductCount(prev => Math.min(prev + 10, availableProducts.length));
setIsLoadingMore(false);
}, 300);
}
}, [isLoadingMore, visibleProductCount, availableProducts.length]);
// Load current user from session storage // Load current user from session storage
React.useEffect(() => { React.useEffect(() => {
try { try {
@ -1213,7 +1240,17 @@ const CompanyProfile = () => {
{productError} {productError}
</div> </div>
)} )}
<h4 className="text-[16px] font-semibold text-[#232528]">Products <span className="text-red-500">*</span></h4> <div className="flex items-baseline">
<h4 className="text-[16px] font-semibold text-[#232528] whitespace-nowrap">Products <span className="text-red-500">*</span></h4>
<a
href="/assets/images/HS-Code UAE (002).pdf"
target="_blank"
rel="noopener noreferrer"
className="ml-3 text-sm text-black-600 hover:underline"
>
Please click here to view the list of HS codes
</a>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Available Products Panel */} {/* Available Products Panel */}
<div className="bg-white rounded-lg border border-[#E5E7EB] overflow-hidden"> <div className="bg-white rounded-lg border border-[#E5E7EB] overflow-hidden">
@ -1241,8 +1278,12 @@ const CompanyProfile = () => {
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div> <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div>
</div> </div>
) : availableProducts.length > 0 ? ( ) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto"> <ul
{filteredAvailableProducts.map((product) => ( ref={productListRef}
onScroll={handleScroll}
className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto"
>
{filteredAvailableProducts.slice(0, visibleProductCount).map((product) => (
<li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center"> <li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div className="text-sm font-medium text-[#111827]"> <div className="text-sm font-medium text-[#111827]">
{product.hsCode || product.hs_code || ''} {product.hsCode || product.hs_code || ''}
@ -1262,6 +1303,11 @@ const CompanyProfile = () => {
</li> </li>
))} ))}
{isLoadingMore && (
<div className="flex justify-center p-2">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-[#92722A]"></div>
</div>
)}
</ul> </ul>
) : ( ) : (

View File

@ -99,7 +99,10 @@ const EditCompanyProfile = () => {
const [isLoadingProducts, setIsLoadingProducts] = useState(false); const [isLoadingProducts, setIsLoadingProducts] = useState(false);
const [productError, setProductError] = useState(""); const [productError, setProductError] = useState("");
const [isLoadingCities, setIsLoadingCities] = useState(false); const [isLoadingCities, setIsLoadingCities] = useState(false);
const [visibleProductCount, setVisibleProductCount] = useState(14);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const initialSnapshotRef = useRef(null); const initialSnapshotRef = useRef(null);
const productListRef = useRef(null);
const formSteps = React.useMemo( const formSteps = React.useMemo(
() => [ () => [
@ -268,21 +271,35 @@ const EditCompanyProfile = () => {
const handleProductSearch = (e) => { const handleProductSearch = (e) => {
const searchTerm = e.target.value.toLowerCase(); const searchTerm = e.target.value.toLowerCase();
setProductSearchTerm(searchTerm); setProductSearchTerm(searchTerm);
if (!searchTerm) { // Get the full list of products from the API
setAvailableProducts(prev => prev.filter(p => const loadAndFilterProducts = async () => {
!selectedProducts.some(sp => sp.value === p.value) try {
)); setIsLoadingProducts(true);
return; const allProducts = await fetchProducts();
}
if (!searchTerm) {
const filtered = availableProducts.filter(product => // If search is cleared, show all products except selected ones
(product.label?.toLowerCase().includes(searchTerm) || setAvailableProducts(allProducts.filter(p =>
product.hs_code?.toLowerCase().includes(searchTerm)) && !selectedProducts.some(sp => sp.value === p.value)
!selectedProducts.some(sp => sp.value === product.value) ));
); } else {
// Filter products based on search term
setAvailableProducts(filtered); const filtered = allProducts.filter(product =>
(product.label?.toLowerCase().includes(searchTerm) ||
product.hs_code?.toLowerCase().includes(searchTerm)) &&
!selectedProducts.some(sp => sp.value === product.value)
);
setAvailableProducts(filtered);
}
} catch (error) {
console.error('Error searching products:', error);
} finally {
setIsLoadingProducts(false);
}
};
loadAndFilterProducts();
}; };
// Add product handler // Add product handler
@ -323,6 +340,31 @@ const EditCompanyProfile = () => {
} }
}; };
// Reset visible count when products change or search is performed
useEffect(() => {
setVisibleProductCount(14);
}, [availableProducts.length, productSearchTerm]);
// Handle scroll event for infinite loading
const handleScroll = useCallback(() => {
if (isLoadingMore || visibleProductCount >= availableProducts.length) return;
const list = productListRef.current;
if (!list) return;
const { scrollTop, scrollHeight, clientHeight } = list;
const isNearBottom = scrollTop + clientHeight >= scrollHeight - 100; // 100px from bottom
if (isNearBottom) {
setIsLoadingMore(true);
// Simulate loading delay for better UX
setTimeout(() => {
setVisibleProductCount(prev => Math.min(prev + 10, availableProducts.length));
setIsLoadingMore(false);
}, 300);
}
}, [isLoadingMore, visibleProductCount, availableProducts.length]);
// Load emirates on component mount using your service function // Load emirates on component mount using your service function
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@ -809,21 +851,31 @@ const EditCompanyProfile = () => {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Left side: Products Header */} {/* Left side: Products Header */}
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4> <div className="flex items-baseline">
<h4 className="text-[16px] font-semibold text-[#232528] whitespace-nowrap">Products</h4>
{/* Right side: Icons */} <a
<div className="flex items-center gap-4 text-sm text-medium-600"> href="/assets/images/HS-Code UAE (002).pdf"
<div className="flex items-center gap-1"> target="_blank"
<MdAdminPanelSettings className="text-[#92722A] text-lg" /> rel="noopener noreferrer"
<span>Admin User</span> className="ml-3 text-sm text-black-600 hover:underline"
>
Please click here to view the list of HS codes
</a>
</div> </div>
<div className="flex items-center gap-1"> {/* Right side: Icons */}
<FaBuilding className="text-[#92722A]" /> <div className="flex items-center gap-4 text-sm text-medium-600">
<span>Establishment User</span> <div className="flex items-center gap-1">
<MdAdminPanelSettings className="text-[#92722A] text-lg" />
<span>Admin User</span>
</div>
<div className="flex items-center gap-1">
<FaBuilding className="text-[#92722A]" />
<span>Establishment User</span>
</div>
</div> </div>
</div>
{/* </div> */} {/* </div> */}
{productError && ( {productError && (
@ -860,8 +912,12 @@ const EditCompanyProfile = () => {
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div> <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div>
</div> </div>
) : availableProducts.length > 0 ? ( ) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto"> <ul
{availableProducts.map((product) => ( ref={productListRef}
onScroll={handleScroll}
className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto"
>
{availableProducts.slice(0, visibleProductCount).map((product) => (
<li key={product.value} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center"> <li key={product.value} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div> <div>
<div className="text-sm font-medium text-[#111827]"> <div className="text-sm font-medium text-[#111827]">
@ -877,6 +933,11 @@ const EditCompanyProfile = () => {
</button> </button>
</li> </li>
))} ))}
{isLoadingMore && (
<div className="flex justify-center p-2">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-[#92722A]"></div>
</div>
)}
</ul> </ul>
) : ( ) : (
<div className="p-4 text-center text-sm text-[#6B7280]"> <div className="p-4 text-center text-sm text-[#6B7280]">

View File

@ -240,7 +240,19 @@ const Login = () => {
} }
} catch (err) { } catch (err) {
console.error('Login failed', err); console.error('Login failed', err);
// Get the error message from the API response
const errorMessage = err.response?.data?.message ||
err.message ||
'An error occurred during login. Please try again.';
// Show the exact error message for inactive users
if (errorMessage.toLowerCase().includes('inactive')) {
showToast('error', errorMessage);
return;
}
// For other errors, show the generic message and update login attempts
const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}'); const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}');
const attempts = (storedAttempts[email]?.attempts || 0) + 1; const attempts = (storedAttempts[email]?.attempts || 0) + 1;
@ -290,14 +302,14 @@ const Login = () => {
{toast && ( {toast && (
<div className="fixed top-6 inset-x-0 z-50 flex justify-center px-4"> <div className="fixed top-6 inset-x-0 z-50 flex justify-center px-4">
<div <div
className={`text-sm rounded-lg px-4 py-3 border flex items-start justify-between gap-3 shadow-lg max-w-md ${ className={`text-sm rounded-lg px-4 py-3 border flex items-center justify-between gap-3 shadow-lg max-w-md ${
toast.type === 'success' toast.type === 'success'
? 'bg-[#F3FAF4] border-[#C6E7D8] text-[#2F663C]' ? 'bg-[#F3FAF4] border-[#C6E7D8] text-[#2F663C]'
: 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]' : 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
}`} }`}
> >
<div className="flex items-start gap-2"> <div className="flex items-center gap-2">
{toast.type === 'error' && <AlertCircle className="h-4 w-4 mt-0.5 flex-shrink-0" />} {toast.type === 'error' && <AlertCircle className="h-4 w-4 flex-shrink-0" />}
<span>{toast.message}</span> <span>{toast.message}</span>
</div> </div>
<button <button