added pdf link for products
This commit is contained in:
parent
49b14861aa
commit
d3d9167de2
@ -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>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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]">
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user