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 [products, setProducts] = 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 [productSearchTerm, setProductSearchTerm] = React.useState('');
const [isLoadingProducts, setIsLoadingProducts] = React.useState(false);
@ -547,6 +550,30 @@ const CompanyProfile = () => {
const filteredAvailableProducts = availableProducts.filter(
(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
React.useEffect(() => {
try {
@ -1213,7 +1240,17 @@ const CompanyProfile = () => {
{productError}
</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">
{/* Available Products Panel */}
<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>
) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{filteredAvailableProducts.map((product) => (
<ul
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">
<div className="text-sm font-medium text-[#111827]">
{product.hsCode || product.hs_code || ''}
@ -1262,6 +1303,11 @@ const CompanyProfile = () => {
</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>
) : (

View File

@ -99,7 +99,10 @@ const EditCompanyProfile = () => {
const [isLoadingProducts, setIsLoadingProducts] = useState(false);
const [productError, setProductError] = useState("");
const [isLoadingCities, setIsLoadingCities] = useState(false);
const [visibleProductCount, setVisibleProductCount] = useState(14);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const initialSnapshotRef = useRef(null);
const productListRef = useRef(null);
const formSteps = React.useMemo(
() => [
@ -268,21 +271,35 @@ const EditCompanyProfile = () => {
const handleProductSearch = (e) => {
const searchTerm = e.target.value.toLowerCase();
setProductSearchTerm(searchTerm);
if (!searchTerm) {
setAvailableProducts(prev => prev.filter(p =>
!selectedProducts.some(sp => sp.value === p.value)
));
return;
}
const filtered = availableProducts.filter(product =>
(product.label?.toLowerCase().includes(searchTerm) ||
product.hs_code?.toLowerCase().includes(searchTerm)) &&
!selectedProducts.some(sp => sp.value === product.value)
);
setAvailableProducts(filtered);
// Get the full list of products from the API
const loadAndFilterProducts = async () => {
try {
setIsLoadingProducts(true);
const allProducts = await fetchProducts();
if (!searchTerm) {
// If search is cleared, show all products except selected ones
setAvailableProducts(allProducts.filter(p =>
!selectedProducts.some(sp => sp.value === p.value)
));
} else {
// Filter products based on search term
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
@ -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
useEffect(() => {
let cancelled = false;
@ -809,21 +851,31 @@ const EditCompanyProfile = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
{/* Left side: Products Header */}
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4>
{/* Right side: Icons */}
<div className="flex items-center gap-4 text-sm text-medium-600">
<div className="flex items-center gap-1">
<MdAdminPanelSettings className="text-[#92722A] text-lg" />
<span>Admin User</span>
{/* Left side: Products Header */}
<div className="flex items-baseline">
<h4 className="text-[16px] font-semibold text-[#232528] whitespace-nowrap">Products</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="flex items-center gap-1">
<FaBuilding className="text-[#92722A]" />
<span>Establishment User</span>
{/* Right side: Icons */}
<div className="flex items-center gap-4 text-sm text-medium-600">
<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> */}
{productError && (
@ -860,8 +912,12 @@ const EditCompanyProfile = () => {
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div>
</div>
) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{availableProducts.map((product) => (
<ul
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">
<div>
<div className="text-sm font-medium text-[#111827]">
@ -877,6 +933,11 @@ const EditCompanyProfile = () => {
</button>
</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>
) : (
<div className="p-4 text-center text-sm text-[#6B7280]">

View File

@ -240,7 +240,19 @@ const Login = () => {
}
} catch (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 attempts = (storedAttempts[email]?.attempts || 0) + 1;
@ -290,14 +302,14 @@ const Login = () => {
{toast && (
<div className="fixed top-6 inset-x-0 z-50 flex justify-center px-4">
<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'
? 'bg-[#F3FAF4] border-[#C6E7D8] text-[#2F663C]'
: 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
}`}
>
<div className="flex items-start gap-2">
{toast.type === 'error' && <AlertCircle className="h-4 w-4 mt-0.5 flex-shrink-0" />}
<div className="flex items-center gap-2">
{toast.type === 'error' && <AlertCircle className="h-4 w-4 flex-shrink-0" />}
<span>{toast.message}</span>
</div>
<button