validation added

This commit is contained in:
Malini 2025-11-17 16:54:25 +05:30
parent 54aab3740c
commit 11abb7d77c

View File

@ -20,8 +20,10 @@ const uae_currency = '/assets/images/UAE_Dirham_Symbol.svg';
const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {}, required = false, error = false }) => {
const [isFocused, setIsFocused] = React.useState(false);
let borderColor = 'border-[#CBA344]';
let bgColor = 'bg-white';
// Simple red background when there's an error
let borderColor = error ? 'border-red-500' : 'border-[#CBA344]';
let bgColor = error ? 'bg-red-50' : 'bg-white';
if (error) {
borderColor = 'border-red-500';
@ -46,8 +48,6 @@ const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {
);
};
const SearchableSelect = ({
placeholder = '',
options = [],
@ -112,9 +112,10 @@ const SearchableSelect = ({
setIsOpen(false);
};
let borderColor = 'border-[#CBA344]';
let bgColor = 'bg-white';
// Simple red background when there's an error
let borderColor = error ? 'border-red-500' : 'border-[#CBA344]';
let bgColor = error ? 'bg-red-50' : 'bg-white';
if (error) {
borderColor = 'border-red-500';
bgColor = 'bg-red-50';
@ -192,8 +193,9 @@ const Select = ({
}) => {
const [isFocused, setIsFocused] = React.useState(false);
let borderColor = 'border-[#CBA344]';
let bgColor = 'bg-white';
// Simple red background when there's an error
let borderColor = error ? 'border-red-500' : 'border-[#CBA344]';
let bgColor = error ? 'bg-red-50' : 'bg-white';
if (error) {
borderColor = 'border-red-500';
@ -571,53 +573,82 @@ const location = useLocation();
);
};
const validateProduct = (product, index) => {
const errors = {};
let hasError = false;
// Simple validation function
const validateField = (fieldName, value, productIndex) => {
const errors = { ...formErrors };
const fieldKey = `${fieldName}_${productIndex}`;
// Check for duplicate products
const duplicateProductIndex = products.findIndex(
(p, i) => p.product === product.product && i !== index
);
if (duplicateProductIndex !== -1) {
errors[`product-${product.id}`] = 'This product has already been selected';
hasError = true;
if (!value || value.toString().trim() === '') {
errors[fieldKey] = 'This field is required';
} else {
delete errors[fieldKey];
}
// Validate product selection
if (!product.product) {
errors[`product-${product.id}`] = 'Product is required';
hasError = true;
}
// Validate unit selection
if (!product.unit) {
errors[`unit-${product.id}`] = 'Unit is required';
hasError = true;
}
// Validate capacity
if (!product.capacity) {
errors[`capacity-${product.id}`] = 'Capacity is required';
hasError = true;
}
return { errors, hasError };
setFormErrors(errors);
return Object.keys(errors).length === 0;
};
// Simple validation for all required fields
const validateForm = () => {
let isValid = true;
const errors = {};
let isValid = true;
products.forEach((product, index) => {
const { errors: productErrors, hasError } = validateProduct(product, index);
if (hasError) {
Object.assign(errors, productErrors);
// Validate product selection
if (!product.product) {
errors[`product_${index}`] = 'Product is required';
isValid = false;
}
// Validate unit selection
if (!product.unit) {
errors[`unit_${index}`] = 'Unit is required';
isValid = false;
}
// Validate capacity
if (!product.capacity) {
errors[`capacity_${index}`] = 'Capacity is required';
isValid = false;
}
// Validate current quarter quantities
const currentQuantityFields = ['janQuantity', 'febQuantity', 'marQuantity'];
currentQuantityFields.forEach(field => {
if (!product[field] && product[field] !== 0) {
errors[`${field}_${index}`] = 'Required';
isValid = false;
}
});
// Validate current quarter costs
const currentCostFields = ['janCost', 'febCost', 'marCost'];
currentCostFields.forEach(field => {
if (!product[field] && product[field] !== 0) {
errors[`${field}_${index}`] = 'Required';
isValid = false;
}
});
// Validate forecast quantities
const forecastQuantityFields = ['aprQuantity', 'mayQuantity', 'junQuantity'];
forecastQuantityFields.forEach(field => {
if (!product[field] && product[field] !== 0) {
errors[`${field}_${index}`] = 'Required';
isValid = false;
}
});
// Validate forecast costs
const forecastCostFields = ['aprCost', 'mayCost', 'junCost'];
forecastCostFields.forEach(field => {
if (!product[field] && product[field] !== 0) {
errors[`${field}_${index}`] = 'Required';
isValid = false;
}
});
});
setFormErrors(errors);
return isValid;
};
@ -625,6 +656,15 @@ const location = useLocation();
const handleNext = () => {
if (validateForm()) {
onNext();
} else {
// Scroll to first error
const firstErrorKey = Object.keys(formErrors)[0];
if (firstErrorKey) {
const element = document.querySelector(`[data-field="${firstErrorKey}"]`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
};
@ -825,13 +865,6 @@ const location = useLocation();
[field]: value
};
}
return {
...product,
variationReason: value,
variationReasonName: selectedReason?.label || selectedReason?.name || '',
[field]: value
};
}
// Handle product selection
@ -953,67 +986,68 @@ const location = useLocation();
};
}
return { ...product, [field]: value };
}
return product;
});
// Clear error for the field being updated
const productIndex = products.findIndex(p => p.id === id);
if (productIndex !== -1) {
const errorKey = `${field}_${productIndex}`;
if (formErrors[errorKey] && value) {
const newErrors = { ...formErrors };
delete newErrors[errorKey];
setFormErrors(newErrors);
}
}
onProductsChange(updatedProducts);
};
const handleProductSelect = async (productId, establishmentId, id) => {
if (!productId || !establishmentId) {
console.warn('Missing required parameters in handleProductSelect:', {
hasProductId: !!productId,
hasEstablishmentId: !!establishmentId
});
return;
}
try {
const forecastData = await fetchPreviousForecastData(productId, establishmentId);
if (!forecastData) {
console.warn('No forecast data received for product:', productId);
return;
}
// Check if the response has a data property (nested response) or is the data itself
const data = forecastData.data || forecastData;
const updatedProducts = products.map(product => {
if (product.id === id) {
return {
...product,
octQuantity: data.previous_quantity_period_one || '',
novQuantity: data.previous_quantity_period_two || '',
decQuantity: data.previous_quantity_period_three || '',
octCost: data.previous_cost_period_one || '',
novCost: data.previous_cost_period_two || '',
decCost: data.previous_cost_period_three || '',
capacity: data.annual_installed_capacity || ''
};
return { ...product, [field]: value };
}
return product;
});
// Clear error for the field being updated
const productIndex = products.findIndex(p => p.id === id);
if (productIndex !== -1) {
const errorKey = `${field}_${productIndex}`;
if (formErrors[errorKey] && value) {
const newErrors = { ...formErrors };
delete newErrors[errorKey];
setFormErrors(newErrors);
}
}
onProductsChange(updatedProducts);
} catch (error) {
console.error('Error in handleProductSelect:', {
message: error.message,
error: error,
stack: error.stack
});
}
};
};
const handleProductSelect = async (productId, establishmentId, id) => {
if (!productId || !establishmentId) {
console.warn('Missing required parameters in handleProductSelect:', {
hasProductId: !!productId,
hasEstablishmentId: !!establishmentId
});
return;
}
try {
const forecastData = await fetchPreviousForecastData(productId, establishmentId);
if (!forecastData) {
console.warn('No forecast data received for product:', productId);
return;
}
// Check if the response has a data property (nested response) or is the data itself
const data = forecastData.data || forecastData;
const updatedProducts = products.map(product => {
if (product.id === id) {
return {
...product,
octQuantity: data.previous_quantity_period_one || '',
novQuantity: data.previous_quantity_period_two || '',
decQuantity: data.previous_quantity_period_three || '',
octCost: data.previous_cost_period_one || '',
novCost: data.previous_cost_period_two || '',
decCost: data.previous_cost_period_three || '',
capacity: data.annual_installed_capacity || ''
};
}
return product;
});
onProductsChange(updatedProducts);
} catch (error) {
console.error('Error in handleProductSelect:', {
message: error.message,
error: error,
stack: error.stack
});
}
};
const canAddMoreProducts = products.length < MAX_PRODUCTS;
const isLoading = loading || isLoadingReasons || isLoadingZeroReasons || isLoadingProducts || isLoadingUnits;
@ -1142,7 +1176,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<h3 className="font-semibold mb-4 text-base">Data definitions</h3>
<p className="mb-3"><strong>Product (HS Code Name):</strong> Standard trade classification. Start typing to search by code or name.</p>
<p className="mb-3"><strong>Quantity (Qty):</strong> Physical output produced in the month, measured in the selected unit.</p>
<p className="mb-3"><strong>Cost (AED):</strong> Total production cost for that month, excluding VAT (e.g., materials, energy, direct labour, and manufacturing expenses). Do not include sales margins.</p>
<p className="mb-3"><strong>Cost (AED <img src={uae_currency} alt="AED" className="inline-block w-2 h-2 -mt-0.5" />):</strong> Total production cost for that month, excluding VAT (e.g., materials, energy, direct labour, and manufacturing expenses). Do not include sales margins.</p>
<p className="mb-0"><strong>Annual Installed Capacity:</strong> Maximum achievable annual output under normal operating conditions with existing equipment; do not include extraordinary overtime.</p>
</div>
)}
@ -1199,7 +1233,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<div data-field={`product_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">Product (HS Code - Name) <span className="text-red-500">*</span></label>
<div>
<SearchableSelect
@ -1336,7 +1370,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{productsError}</p>
)}
</div>
<div>
<div data-field={`unit_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">Unit <span className="text-red-500">*</span></label>
<SearchableSelect
placeholder={isLoadingUnits ? 'Loading units...' : 'Select unit'}
@ -1363,7 +1397,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
)}
</div>
</div>
<div>
<div data-field={`capacity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">Annual installed capacity (in unit) <span className="text-red-500">*</span></label>
<Input
placeholder="Enter capacity"
@ -1417,7 +1451,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<div className="flex items-end space-x-4 mt-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_one } Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)
{quarterPeriods?.previous_month?.previous_period_one } Cost <img src={uae_currency} alt="AED" className="inline-block w-4 h-4 -mt-0.5 opacity-70" />
</label>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.octCost || '0'}
@ -1425,7 +1459,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_two } Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)
{quarterPeriods?.previous_month?.previous_period_two } Cost (AED)
</label>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.novCost || '0'}
@ -1450,7 +1484,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
>
<div className="space-y-4">
<div className="flex items-end space-x-4">
<div>
<div data-field={`janQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_one} Qty<span className="text-red-600">*</span>
</label>
@ -1466,7 +1500,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`janQuantity_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`febQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_two } Qty<span className="text-red-600">*</span>
</label>
@ -1482,7 +1516,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`febQuantity_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`marQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_three } Qty<span className="text-red-600">*</span>
</label>
@ -1500,7 +1534,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
</div>
</div>
<div className="flex items-end space-x-4">
<div>
<div data-field={`janCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_one } Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1516,7 +1550,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`janCost_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`febCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_two} Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1532,7 +1566,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`febCost_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`marCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.current_month?.current_period_three} Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1592,7 +1626,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
>
<div className="space-y-4">
<div className="flex items-end space-x-3">
<div>
<div data-field={`aprQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_one } Qty<span className="text-red-600">*</span>
</label>
@ -1609,7 +1643,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`aprQuantity_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`mayQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_two } Qty<span className="text-red-600">*</span>
</label>
@ -1626,7 +1660,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`mayQuantity_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`junQuantity_${idx}`}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_three } Qty<span className="text-red-600">*</span>
</label>
@ -1645,7 +1679,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
</div>
</div>
<div className="flex items-end space-x-3">
<div>
<div data-field={`aprCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 w-26 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_one} Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1662,7 +1696,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`aprCost_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`mayCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_two } Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1679,7 +1713,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<p className="mt-1 text-xs text-red-600">{formErrors[`mayCost_${idx}`]}</p>
)}
</div>
<div>
<div data-field={`junCost_${idx}`}>
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">
{quarterPeriods?.forecast_month?.forecast_period_three } Cost (<img src={uae_currency} alt="AED" className="inline-block w-3.5 h-3 -mt-0.5 opacity-80" />)<span className="text-red-600">*</span>
</label>
@ -1786,4 +1820,4 @@ const handleProductSelect = async (productId, establishmentId, id) => {
);
};
export default ProductData;
export default ProductData;