validation added
This commit is contained in:
parent
54aab3740c
commit
11abb7d77c
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user