code merged

This commit is contained in:
Malini 2025-11-13 20:18:11 +05:30
parent fde8142acb
commit 7c16dec47a
2 changed files with 175 additions and 169 deletions

View File

@ -401,16 +401,18 @@ const EstablishmentInfo = ({
</a>
. Changes saved there will appear here.
</p> */}
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<span className="font-medium">Need to update details?</span> Go to Profile {' '}
<a
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
</p>
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
. Changes saved there will appear here.
</p>
</div>
<div className="flex items-center gap-2 mb-2">
<h2 className="text-lg font-semibold text-[#232528]">Step 1: Review Establishment Information</h2>

View File

@ -98,6 +98,8 @@ const EditCompanyProfile = () => {
const [selectedProducts, setSelectedProducts] = useState([]);
const [productSearchTerm, setProductSearchTerm] = useState('');
const [isLoadingProducts, setIsLoadingProducts] = useState(false);
const [productError, setProductError] = useState("");
const [isLoadingCities, setIsLoadingCities] = useState(false);
const initialSnapshotRef = useRef(null);
const formSteps = React.useMemo(
@ -126,15 +128,12 @@ const EditCompanyProfile = () => {
2: [
{ field: 'contactName', label: 'Name' },
{ field: 'contactAddress', label: 'Address' },
{ field: 'contactCityTown', label: 'City/Town' },
{ field: 'contactEmirate', label: 'Emirate' },
{ field: 'contactMakaniNumber', label: 'Makani Number' },
{ field: 'contactPersonName', label: 'Contact Person Name' },
{ field: 'contactMobileNumber', label: 'Mobile Number' },
{ field: 'contactEmirateId', label: 'Emirate' },
{ field: 'contactCityTownId', label: 'City/Town' },
{ field: 'contactEmail', label: 'Email' },
],
3: [
// Products are optional, no required fields
// Products validation handled separately
],
4: [
{ field: 'employmentEmiratiMale', label: 'Number of Emirati Male' },
@ -148,6 +147,18 @@ const EditCompanyProfile = () => {
const validateStepFields = useCallback((stepIndex) => {
setPasswordReuseError("");
// Special validation for Products step
if (stepIndex === 3) {
if (selectedProducts.length === 0) {
setProductError("At least one product is required.");
return false;
} else {
setProductError("");
return true;
}
}
const requiredFields = requiredFieldsByStep[stepIndex] || [];
if (!requiredFields.length) return true;
@ -163,15 +174,15 @@ const EditCompanyProfile = () => {
return nextErrors;
});
return valid;
}, [form, requiredFieldsByStep]);
}, [form, requiredFieldsByStep, selectedProducts]);
const handleFormChange = useCallback((field, value) => {
if (field === 'contactCityTown') {
const foundCity = contactCityOptions.find(option => option.value === value || option.label === value);
if (field === 'contactCityTownId') {
const foundCity = contactCityOptions.find(option => option.value === value);
setForm(prev => ({
...prev,
contactCityTown: foundCity?.name || '',
contactCityTownId: foundCity?.id || ''
contactCityTown: foundCity?.label || '',
contactCityTownId: value
}));
return;
}
@ -200,63 +211,47 @@ const EditCompanyProfile = () => {
if (activeStep > 0) setActiveStep(activeStep - 1);
}, [activeStep]);
// Load cities based on selected emirate
const loadCities = useCallback(async (emirateId, setCityOptions) => {
// Load cities based on selected emirate using your service function
const loadCities = useCallback(async (emirateId) => {
if (!emirateId) {
setCityOptions([]);
setContactCityOptions([]);
return [];
}
try {
const response = await fetchCityTowns({ emirateId: Number(emirateId) });
const options = Array.isArray(response) ? response : (response.data || []);
setIsLoadingCities(true);
const cities = await fetchCityTowns({ emirateId });
const formatted = options.map((option) => ({
label: option.name || option.label,
value: String(option.id || option.value),
name: option.name || option.label,
id: String(option.id || option.value)
}));
console.log('Loaded cities for emirate', emirateId, ':', cities);
setCityOptions(formatted);
return formatted;
setContactCityOptions(cities);
return cities;
} catch (error) {
console.error('Failed to load cities:', error);
setCityOptions([]);
setContactCityOptions([]);
return [];
} finally {
setIsLoadingCities(false);
}
}, []);
// Load products
// Load products using your service function
const loadProducts = useCallback(async () => {
try {
setIsLoadingProducts(true);
const response = await fetchProducts();
const products = await fetchProducts();
let productsData = [];
console.log('Processed products data:', products);
if (Array.isArray(response)) {
productsData = response;
} else if (response?.data) {
productsData = Array.isArray(response.data) ? response.data : response.data.products || [];
}
console.log('Processed products data:', productsData);
// Format products data
const formattedProducts = productsData.map(p => ({
id: p.id || p.value,
product_id: p.id || p.value,
hs_code: p.hs_code || p.hsCode || '',
hsCode: p.hs_code || p.hsCode || '',
product_name: p.product_name || p.productName || p.label || '',
productName: p.product_name || p.productName || p.label || '',
label: p.product_name || p.productName || p.label || '',
value: p.id || p.value
}));
setAvailableProducts(formattedProducts);
return formattedProducts;
setAvailableProducts(prevProducts => {
// Only update if products have actually changed to prevent unnecessary re-renders
if (JSON.stringify(prevProducts) !== JSON.stringify(products)) {
return products;
}
return prevProducts;
});
return products;
} catch (error) {
console.error('Error loading products:', error);
return [];
@ -272,7 +267,7 @@ const EditCompanyProfile = () => {
if (!searchTerm) {
setAvailableProducts(prev => prev.filter(p =>
!selectedProducts.some(sp => sp.id === p.id)
!selectedProducts.some(sp => sp.value === p.value)
));
return;
}
@ -280,7 +275,7 @@ const EditCompanyProfile = () => {
const filtered = availableProducts.filter(product =>
(product.label?.toLowerCase().includes(searchTerm) ||
product.hs_code?.toLowerCase().includes(searchTerm)) &&
!selectedProducts.some(sp => sp.id === product.id)
!selectedProducts.some(sp => sp.value === product.value)
);
setAvailableProducts(filtered);
@ -294,19 +289,22 @@ const EditCompanyProfile = () => {
});
// Remove from available products
setAvailableProducts(prev => prev.filter(p => p.id !== product.id));
setAvailableProducts(prev => prev.filter(p => p.value !== product.value));
// Clear product error when a product is added
setProductError("");
};
// Remove product handler
const handleRemoveProduct = (product) => {
setSelectedProducts(prev => prev.filter(p => p.id !== product.id));
setSelectedProducts(prev => prev.filter(p => p.value !== product.value));
// Add back to available products if not already there and matches search
if (!productSearchTerm ||
(product.hs_code && product.hs_code.toLowerCase().includes(productSearchTerm)) ||
(product.product_name && product.product_name.toLowerCase().includes(productSearchTerm))) {
(product.label && product.label.toLowerCase().includes(productSearchTerm))) {
setAvailableProducts(prev => {
if (!prev.some(p => p.id === product.id)) {
if (!prev.some(p => p.value === product.value)) {
return [...prev, product];
}
return prev;
@ -314,9 +312,44 @@ const EditCompanyProfile = () => {
}
};
// Load emirates on component mount using your service function
useEffect(() => {
let cancelled = false;
const controller = new AbortController();
const loadEmirates = async () => {
try {
const emirates = await fetchEmirates({ signal: controller.signal });
if (cancelled) return;
console.log('Loaded emirates:', emirates);
const lookup = {};
emirates.forEach((emirate) => {
lookup[emirate.value] = emirate.label;
});
setEmirateOptions(emirates);
setEmirateLookup(lookup);
} catch (error) {
if (cancelled) return;
console.error('Failed to load emirates:', error);
}
};
loadEmirates();
return () => {
cancelled = true;
controller.abort();
};
}, []);
// Load establishment data and set form with API data
useEffect(() => {
console.log('useEffect triggered, establishmentId:', establishmentId);
// Create an async function inside the effect
const fetchData = async () => {
if (!establishmentId) {
console.log('No establishmentId found, skipping fetch');
@ -349,98 +382,85 @@ const EditCompanyProfile = () => {
console.log('Final Form Data:', formData);
// First set the form data
setForm(formData);
initialSnapshotRef.current = formData;
// Load products and set selected products
const allProducts = await loadProducts();
// Then load cities and products in parallel
const [cities, allProducts] = await Promise.all([
formData.contactEmirateId ? loadCities(formData.contactEmirateId) : Promise.resolve([]),
loadProducts()
]);
console.log('Loaded cities:', cities);
console.log('All products loaded:', allProducts);
// Set selected products from API response
const establishmentProducts = response.data?.establishment_products || response.establishment_products || [];
if (Array.isArray(establishmentProducts) && establishmentProducts.length > 0) {
const formattedSelectedProducts = establishmentProducts.map(ep => {
const productData = ep.product || {};
return {
id: ep.id || 0,
product_id: ep.product_id || 0,
hs_code: productData.hs_code || ep.hs_code || '',
hsCode: productData.hs_code || ep.hs_code || '',
product_name: productData.product_name || ep.product_name || 'Unnamed Product',
productName: productData.product_name || ep.product_name || 'Unnamed Product',
label: `${productData.product_name || ep.product_name || 'Unnamed Product'}${productData.hs_code ? ` (${productData.hs_code})` : ''}`,
value: ep.product_id ? String(ep.product_id) : '',
...productData
const productId = productData.id || ep.product_id;
// Find the product in the loaded products to get the proper format
const foundProduct = allProducts.find(p => p.value === String(productId));
return foundProduct || {
value: String(productId),
label: productData.product_name || productData.name || 'Unnamed Product',
hs_code: productData.hs_code || '',
product_name: productData.product_name || productData.name || 'Unnamed Product'
};
});
setSelectedProducts(formattedSelectedProducts);
// Remove selected products from available products
setAvailableProducts(prev =>
prev.filter(p => !formattedSelectedProducts.some(sp => sp.id === p.id))
);
setSelectedProducts(formattedSelectedProducts);
}
} catch (error) {
console.error('Failed to load establishment details:', error);
alert('Failed to load company details. Please try again.');
console.error('Error loading establishment:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, [establishmentId, loadProducts]);
// Load emirates on component mount
useEffect(() => {
let cancelled = false;
const controller = new AbortController();
const loadEmirates = async () => {
try {
const options = await fetchEmirates({ signal: controller.signal });
if (cancelled) return;
const formatted = options.map((option) => ({
label: option.name || option.label,
value: String(option.id || option.value),
name: option.name || option.label,
id: String(option.id || option.value)
}));
const lookup = {};
formatted.forEach((option) => {
lookup[option.value] = option.label;
});
setEmirateOptions(formatted);
setEmirateLookup(lookup);
} catch (error) {
if (cancelled) return;
console.error('Failed to load emirates:', error);
}
};
loadEmirates();
// Cleanup function to prevent state updates after unmount
return () => {
cancelled = true;
controller.abort();
// Any cleanup if needed
};
}, []);
}, [establishmentId]); // Remove loadCities and loadProducts from dependencies
// Load contact cities when emirate ID changes
useEffect(() => {
const loadContactCities = async () => {
let isMounted = true;
const loadCitiesForEmirate = async () => {
if (form.contactEmirateId) {
await loadCities(form.contactEmirateId, setContactCityOptions);
} else {
try {
const cities = await fetchCityTowns({ emirateId: form.contactEmirateId });
if (isMounted) {
setContactCityOptions(cities);
}
} catch (error) {
console.error('Failed to load cities:', error);
if (isMounted) {
setContactCityOptions([]);
}
}
} else if (isMounted) {
setContactCityOptions([]);
// Clear city selection when emirate is cleared
handleFormChange('contactCityTownId', '');
}
};
loadContactCities();
}, [form.contactEmirateId, loadCities]);
loadCitiesForEmirate();
return () => {
isMounted = false;
};
}, [form.contactEmirateId]);
// Calculate totals when employment numbers change
useEffect(() => {
@ -497,7 +517,7 @@ const EditCompanyProfile = () => {
updated_by: 1,
establishment_products: Array.isArray(selectedProducts) && selectedProducts.length > 0
? selectedProducts.map(p => ({
product_id: p.product_id || p.id || 0
product_id: Number(p.value) || 0
}))
: [],
};
@ -650,23 +670,16 @@ const EditCompanyProfile = () => {
/>
<SelectField
label="Emirate"
name="contactEmirate"
name="contactEmirateId"
value={form.contactEmirateId || ''}
onChange={async (e) => {
onChange={(e) => {
const selectedValue = e.target.value;
const selectedEmirate = emirateOptions.find(opt => opt.value === selectedValue);
handleFormChange("contactEmirate", selectedEmirate?.name || '');
handleFormChange("contactEmirateId", selectedEmirate?.id || '');
handleFormChange("contactEmirate", selectedEmirate?.label || '');
handleFormChange("contactEmirateId", selectedValue);
handleFormChange("contactCityTown", '');
handleFormChange("contactCityTownId", '');
if (selectedEmirate?.id) {
const cities = await loadCities(selectedEmirate.id, setContactCityOptions);
setContactCityOptions(cities);
} else {
setContactCityOptions([]);
}
}}
options={emirateOptions.map(opt => ({
value: opt.value,
@ -675,28 +688,25 @@ const EditCompanyProfile = () => {
placeholder="Select Emirate"
width="100%"
required
error={fieldErrors.contactEmirate}
error={fieldErrors.contactEmirateId}
/>
<SelectField
label="City/Town"
name="contactCityTown"
name="contactCityTownId"
value={form.contactCityTownId || ''}
onChange={(e) => {
const selectedValue = e.target.value;
const selectedCity = contactCityOptions.find(opt => opt.value === selectedValue);
if (selectedCity) {
handleFormChange("contactCityTown", selectedCity.value);
}
handleFormChange("contactCityTownId", selectedValue);
}}
options={contactCityOptions.map(opt => ({
value: opt.value,
label: opt.label
}))}
placeholder={form.contactEmirate ? "Select City/Town" : "Select emirate first"}
placeholder={form.contactEmirateId ? (isLoadingCities ? "Loading cities..." : "Select City/Town") : "Select emirate first"}
width="100%"
required
disabled={!form.contactEmirate}
error={fieldErrors.contactCityTown}
disabled={!form.contactEmirateId || isLoadingCities}
error={fieldErrors.contactCityTownId}
/>
<TextField
label="Postal Code"
@ -718,8 +728,6 @@ const EditCompanyProfile = () => {
onChange={(e) => handleFormChange("contactMakaniNumber", e.target.value)}
placeholder="Enter Makani Number"
width="100%"
required
error={fieldErrors.contactMakaniNumber}
/>
<TextField
label="Contact Person Name"
@ -727,8 +735,6 @@ const EditCompanyProfile = () => {
onChange={(e) => handleFormChange("contactPersonName", e.target.value)}
placeholder="Enter Contact Person Name"
width="100%"
required
error={fieldErrors.contactPersonName}
/>
<TextField
label="Contact Person Designation"
@ -743,8 +749,6 @@ const EditCompanyProfile = () => {
onChange={(value) => handleFormChange("contactMobileNumber", value)}
placeholder="Enter Mobile Number"
width="100%"
required
error={fieldErrors.contactMobileNumber}
/>
<TextField
label="Email"
@ -769,7 +773,12 @@ const EditCompanyProfile = () => {
case 3:
return (
<div className="space-y-6">
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4>
<div>
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4>
{productError && (
<p className="text-xs text-[#B91C1C] mt-1">{productError}</p>
)}
</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">
@ -802,12 +811,10 @@ const EditCompanyProfile = () => {
) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{availableProducts.map((product) => (
<li key={product.id} 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 className="text-sm font-medium text-[#111827]">
{product.hsCode || ''}
{product.hsCode && (product.productName || product.label || product.product_name) ? ' - ' : ''}
{product.productName || product.label || product.product_name || ''}
{product.label}
</div>
</div>
<button
@ -843,12 +850,10 @@ const EditCompanyProfile = () => {
{selectedProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{selectedProducts.map((product) => (
<li key={product.id} 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 className="text-sm font-medium text-[#111827]">
{product.hs_code || product.hsCode || ''}
{product.hs_code || product.hsCode ? ' - ' : ''}
{product.product_name || product.productName || product.label || 'Unnamed Product'}
{product.label}
</div>
</div>
<button
@ -943,17 +948,17 @@ const EditCompanyProfile = () => {
};
return (
<div className="min-h-screen bg-[#F8FAFC]">
<div className="min-h-screen">
<HeaderBar />
<div className="container mx-auto px-4 py-8">
<div className="bg-white rounded-xl shadow-lg overflow-hidden">
<div className="p-10 mx-auto ">
<div className="bg-white overflow-hidden">
{loading && <Loader />}
{/* Header */}
<div className="flex justify-between items-center border-b border-[#F1F2F4] px-8 py-5 bg-white">
<div className="flex justify-between items-center border-b border-[#F1F2F4] ">
<div>
<h2 className="text-xl font-bold text-gray-900">Edit Company Profile</h2>
<h2 className="text-xl font-bold pb-6 text-gray-900">Edit Company Profile</h2>
</div>
</div>
@ -1072,5 +1077,4 @@ const EditCompanyProfile = () => {
);
};
export default EditCompanyProfile;
export default EditCompanyProfile;