bug fixed
This commit is contained in:
parent
f9c3447704
commit
01df11d1fe
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { getQuarterPeriods, getPreviousForecastData } from '@/services/submissions/submissionService';
|
||||||
import {
|
import {
|
||||||
fetchVariationReasons,
|
fetchVariationReasons,
|
||||||
fetchZeroTargetReasons,
|
fetchZeroTargetReasons,
|
||||||
@ -12,9 +13,17 @@ const calendarIcon = '/assets/images/Duedate.svg';
|
|||||||
const nextIcon = '/assets/images/mdi_page-next-outline.svg';
|
const nextIcon = '/assets/images/mdi_page-next-outline.svg';
|
||||||
const trashActiveSrc = '/assets/images/Trash - active.svg';
|
const trashActiveSrc = '/assets/images/Trash - active.svg';
|
||||||
|
|
||||||
const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {}, required = false }) => {
|
const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {}, required = false, error = false }) => {
|
||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
const borderColor = value || isFocused ? 'border-[#92722A]' : 'border-[#CBA344]';
|
let borderColor = 'border-[#CBA344]';
|
||||||
|
let bgColor = 'bg-white';
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
borderColor = 'border-red-500';
|
||||||
|
bgColor = 'bg-red-50';
|
||||||
|
} else if (value || isFocused) {
|
||||||
|
borderColor = 'border-[#92722A]';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@ -26,7 +35,7 @@ const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
required={required}
|
required={required}
|
||||||
className={`block w-full h-10 rounded-md border-2 ${borderColor} focus:ring-0 px-4 text-sm bg-white transition-colors [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none`}
|
className={`block w-full h-10 rounded-md border-2 ${borderColor} focus:ring-0 px-4 text-sm ${bgColor} transition-colors [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -38,24 +47,32 @@ const Select = ({
|
|||||||
value = '',
|
value = '',
|
||||||
onChange,
|
onChange,
|
||||||
loading = false,
|
loading = false,
|
||||||
disabled = false
|
disabled = false,
|
||||||
|
error = false,
|
||||||
|
className = ''
|
||||||
}) => {
|
}) => {
|
||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
|
|
||||||
|
let borderColor = 'border-[#CBA344]';
|
||||||
|
let bgColor = 'bg-white';
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
borderColor = 'border-red-500';
|
||||||
|
bgColor = 'bg-red-50';
|
||||||
|
} else if (value || isFocused) {
|
||||||
|
borderColor = 'border-[#92722A]';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select
|
<select
|
||||||
className={`block w-full h-10 rounded-md border-2 ${
|
className={`block w-full h-10 rounded-md border-2 px-4 pr-10 text-sm appearance-none focus:ring-0 ${
|
||||||
disabled ? 'bg-gray-50 cursor-not-allowed' : 'bg-white'
|
disabled ? 'bg-gray-50 cursor-not-allowed' : bgColor
|
||||||
} ${
|
} ${borderColor} ${className}`}
|
||||||
value ? 'border-[#92722A]' : 'border-[#CBA344]'
|
|
||||||
} ${
|
|
||||||
isFocused && !disabled ? 'border-[#92722A]' : ''
|
|
||||||
} focus:ring-0 px-4 pr-10 text-sm appearance-none`}
|
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onFocus={() => !disabled && setIsFocused(true)}
|
onFocus={() => !disabled && setIsFocused(true)}
|
||||||
onBlur={() => !disabled && setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
>
|
>
|
||||||
<option value="" disabled>{loading ? 'Loading...' : placeholder}</option>
|
<option value="" disabled>{loading ? 'Loading...' : placeholder}</option>
|
||||||
@ -139,6 +156,8 @@ const ProductData = ({
|
|||||||
onBack = () => {},
|
onBack = () => {},
|
||||||
loading = false,
|
loading = false,
|
||||||
error = '',
|
error = '',
|
||||||
|
quarter = '',
|
||||||
|
year = '',
|
||||||
}) => {
|
}) => {
|
||||||
const [showDataDefinitions, setShowDataDefinitions] = React.useState(true);
|
const [showDataDefinitions, setShowDataDefinitions] = React.useState(true);
|
||||||
const [showEntryRules, setShowEntryRules] = React.useState(true);
|
const [showEntryRules, setShowEntryRules] = React.useState(true);
|
||||||
@ -157,8 +176,32 @@ const ProductData = ({
|
|||||||
const [unitOptions, setUnitOptions] = React.useState([]);
|
const [unitOptions, setUnitOptions] = React.useState([]);
|
||||||
const [isLoadingUnits, setIsLoadingUnits] = React.useState(false);
|
const [isLoadingUnits, setIsLoadingUnits] = React.useState(false);
|
||||||
const [unitsError, setUnitsError] = React.useState('');
|
const [unitsError, setUnitsError] = React.useState('');
|
||||||
|
const [activeTab, setActiveTab] = React.useState('current');
|
||||||
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
|
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
|
||||||
const [remarks, setRemarks] = React.useState('');
|
const [remarks, setRemarks] = React.useState('');
|
||||||
|
const [formErrors, setFormErrors] = React.useState({});
|
||||||
|
const [quarterPeriods, setQuarterPeriods] = useState(null);
|
||||||
|
const [isLoadingPeriods, setIsLoadingPeriods] = useState(false);
|
||||||
|
|
||||||
|
// Fetch quarter periods when component mounts or quarter/year changes
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchQuarterPeriods = async () => {
|
||||||
|
if (!quarter || !year) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoadingPeriods(true);
|
||||||
|
const quarterNumber = quarter.replace('Q', ''); // Convert 'Q3' to '3'
|
||||||
|
const response = await getQuarterPeriods(parseInt(year), `Q${quarterNumber}`);
|
||||||
|
setQuarterPeriods(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch quarter periods:', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingPeriods(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchQuarterPeriods();
|
||||||
|
}, [quarter, year]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const variationController = new AbortController();
|
const variationController = new AbortController();
|
||||||
@ -265,6 +308,65 @@ const ProductData = ({
|
|||||||
}
|
}
|
||||||
}, [products]);
|
}, [products]);
|
||||||
|
|
||||||
|
const requiredMessage = 'Required';
|
||||||
|
const validateForm = () => {
|
||||||
|
const errors = {};
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
products.forEach((product, index) => {
|
||||||
|
// Product validation
|
||||||
|
if (!product.product) {
|
||||||
|
// errors[`product_${index}`] = 'Product (HS Code - Name) is required';
|
||||||
|
errors[`product_${index}`] = requiredMessage;
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit validation
|
||||||
|
if (!product.unit) {
|
||||||
|
errors[`unit_${index}`] = requiredMessage;
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity validation
|
||||||
|
if (!product.capacity) {
|
||||||
|
errors[`capacity_${index}`] = requiredMessage;
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monthly quantity validations
|
||||||
|
const requiredFields = [
|
||||||
|
{ key: 'janQuantity', label: 'Jan Qty' },
|
||||||
|
{ key: 'febQuantity', label: 'Feb Qty' },
|
||||||
|
{ key: 'marQuantity', label: 'Mar Qty' },
|
||||||
|
{ key: 'aprQuantity', label: 'Apr Qty' },
|
||||||
|
{ key: 'mayQuantity', label: 'May Qty' },
|
||||||
|
{ key: 'junQuantity', label: 'Jun Qty' },
|
||||||
|
{ key: 'janCost', label: 'Jan Cost' },
|
||||||
|
{ key: 'febCost', label: 'Feb Cost' },
|
||||||
|
{ key: 'marCost', label: 'Mar Cost' },
|
||||||
|
{ key: 'aprCost', label: 'Apr Cost' },
|
||||||
|
{ key: 'mayCost', label: 'May Cost' },
|
||||||
|
{ key: 'junCost', label: 'Jun Cost' }
|
||||||
|
];
|
||||||
|
|
||||||
|
requiredFields.forEach(field => {
|
||||||
|
if (product[field.key] === undefined || product[field.key] === '') {
|
||||||
|
errors[`${field.key}_${index}`] = requiredMessage;
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setFormErrors(errors);
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (validateForm()) {
|
||||||
|
onNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addProduct = () => {
|
const addProduct = () => {
|
||||||
if (products.length >= MAX_PRODUCTS) return;
|
if (products.length >= MAX_PRODUCTS) return;
|
||||||
const newProduct = {
|
const newProduct = {
|
||||||
@ -292,25 +394,156 @@ const ProductData = ({
|
|||||||
onProductsChange(products.filter((p) => p.id !== id));
|
onProductsChange(products.filter((p) => p.id !== id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProductField = (id, field, value, options = []) => {
|
const fetchPreviousForecastData = async (productId, establishmentId) => {
|
||||||
onProductsChange(
|
console.log('fetchPreviousForecastData called with:', {
|
||||||
products.map((product) => {
|
productId,
|
||||||
if (product.id !== id) return product;
|
establishmentId,
|
||||||
|
quarter,
|
||||||
// When product is selected, store both ID and name
|
year
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!productId || !establishmentId || !quarter || !year) {
|
||||||
|
console.warn('Missing required parameters for fetchPreviousForecastData:', {
|
||||||
|
hasProductId: !!productId,
|
||||||
|
hasEstablishmentId: !!establishmentId,
|
||||||
|
hasQuarter: !!quarter,
|
||||||
|
hasYear: !!year
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure quarter is in the format 'Q1', 'Q2', etc.
|
||||||
|
const formattedQuarter = quarter.startsWith('Q') ? quarter : `Q${quarter}`;
|
||||||
|
const params = {
|
||||||
|
establishment_id: establishmentId,
|
||||||
|
quarter: formattedQuarter,
|
||||||
|
year: year,
|
||||||
|
product_id: productId
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Calling getPreviousForecastData with params:', params);
|
||||||
|
|
||||||
|
const response = await getPreviousForecastData(
|
||||||
|
establishmentId,
|
||||||
|
formattedQuarter,
|
||||||
|
year,
|
||||||
|
productId
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('API Response:', response);
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
console.warn('Empty response from getPreviousForecastData');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the response data or the response itself if data doesn't exist
|
||||||
|
const result = response.data || response;
|
||||||
|
console.log('Processed forecast data:', result);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchPreviousForecastData:', {
|
||||||
|
message: error.message,
|
||||||
|
response: error.response?.data,
|
||||||
|
status: error.response?.status,
|
||||||
|
config: error.config
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleProductSelect = async (productId, establishmentId, id) => {
|
||||||
|
// if (!productId || !establishmentId) return;
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const forecastData = await fetchPreviousForecastData(productId, establishmentId);
|
||||||
|
// console.log('Forecast data received:', forecastData);
|
||||||
|
|
||||||
|
// 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:', error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const updateProductField = (id, field, value, options = null) => {
|
||||||
|
const updatedProducts = products.map(product => {
|
||||||
|
if (product.id === id) {
|
||||||
|
// Clear error for the field being updated
|
||||||
if (field === 'product') {
|
if (field === 'product') {
|
||||||
const selectedProduct = options.find(p => p.value === value || p.id === value);
|
const selectedProduct = options?.find(opt => opt.value === value);
|
||||||
return {
|
const updatedProduct = {
|
||||||
...product,
|
...product,
|
||||||
product: value,
|
product: value,
|
||||||
productName: selectedProduct?.label || selectedProduct?.name || '',
|
productName: selectedProduct?.label || '',
|
||||||
|
productId: selectedProduct?.value || '',
|
||||||
[field]: value
|
[field]: value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch previous forecast data when product is selected
|
||||||
|
if (selectedProduct?.value) {
|
||||||
|
// Try to get establishmentId from sessionStorage first, then fallback to localStorage
|
||||||
|
let establishmentId = sessionStorage.getItem('establishment_id') ||
|
||||||
|
localStorage.getItem('establishmentId') ||
|
||||||
|
localStorage.getItem('establishment_id');
|
||||||
|
|
||||||
|
console.log('Retrieved establishment ID:', establishmentId);
|
||||||
|
|
||||||
|
if (establishmentId && quarter && year) {
|
||||||
|
// Get the actual product ID from the selected product object
|
||||||
|
const selectedProductId = selectedProduct.originalData?.id || selectedProduct.value;
|
||||||
|
console.log('Fetching forecast data for:', {
|
||||||
|
productId: selectedProductId,
|
||||||
|
establishmentId,
|
||||||
|
quarter,
|
||||||
|
year,
|
||||||
|
storageSource: sessionStorage.getItem('establishment_id') ? 'sessionStorage' : 'localStorage'
|
||||||
|
});
|
||||||
|
handleProductSelect(selectedProductId, establishmentId, id);
|
||||||
|
} else {
|
||||||
|
console.warn('Missing required parameters for forecast data fetch:', {
|
||||||
|
hasEstablishmentId: !!establishmentId,
|
||||||
|
establishmentIdValue: establishmentId, // Log the actual value for debugging
|
||||||
|
hasQuarter: !!quarter,
|
||||||
|
hasYear: !!year,
|
||||||
|
availableStorage: {
|
||||||
|
sessionStorage: sessionStorage.getItem('establishment_id') ? 'exists' : 'not found',
|
||||||
|
localStorage: localStorage.getItem('establishmentId') ? 'exists' : 'not found',
|
||||||
|
localStorageAlt: localStorage.getItem('establishment_id') ? 'exists' : 'not found'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedProduct;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When unit is selected, store both ID and name
|
|
||||||
if (field === 'unit') {
|
if (field === 'unit') {
|
||||||
const selectedUnit = options.find(u => u.value === value || u.id === value);
|
const selectedUnit = options?.find(u => u.value === value || u.id === value);
|
||||||
return {
|
return {
|
||||||
...product,
|
...product,
|
||||||
unit: value,
|
unit: value,
|
||||||
@ -342,10 +575,79 @@ const ProductData = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { ...product, [field]: value };
|
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) => {
|
||||||
|
console.log('handleProductSelect called with:', { productId, establishmentId, id });
|
||||||
|
|
||||||
|
if (!productId || !establishmentId) {
|
||||||
|
console.warn('Missing required parameters in handleProductSelect:', {
|
||||||
|
hasProductId: !!productId,
|
||||||
|
hasEstablishmentId: !!establishmentId
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Fetching forecast data...');
|
||||||
|
const forecastData = await fetchPreviousForecastData(productId, establishmentId);
|
||||||
|
console.log('Forecast data received:', forecastData);
|
||||||
|
|
||||||
|
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;
|
||||||
|
console.log('Processing forecast data:', data);
|
||||||
|
|
||||||
|
const updatedProducts = products.map(product => {
|
||||||
|
if (product.id === id) {
|
||||||
|
const updatedProduct = {
|
||||||
|
...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 || ''
|
||||||
|
};
|
||||||
|
console.log('Updated product data:', updatedProduct);
|
||||||
|
return updatedProduct;
|
||||||
|
}
|
||||||
|
return product;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Updating products with new data');
|
||||||
|
onProductsChange(updatedProducts);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleProductSelect:', {
|
||||||
|
message: error.message,
|
||||||
|
error: error,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
const canAddMoreProducts = products.length < MAX_PRODUCTS;
|
const canAddMoreProducts = products.length < MAX_PRODUCTS;
|
||||||
|
|
||||||
const isLoading = loading || isLoadingReasons || isLoadingZeroReasons || isLoadingProducts || isLoadingUnits;
|
const isLoading = loading || isLoadingReasons || isLoadingZeroReasons || isLoadingProducts || isLoadingUnits;
|
||||||
@ -358,7 +660,14 @@ const ProductData = ({
|
|||||||
<div className="h-12 w-12 animate-spin rounded-full border-[3px] border-[#92722A] border-t-transparent" />
|
<div className="h-12 w-12 animate-spin rounded-full border-[3px] border-[#92722A] border-t-transparent" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h2 className="text-xl font-semibold text-[#232528] mb-2">Step 2: Product Data — Monthly Output & Cost</h2>
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<h2 className="text-xl font-semibold text-[#232528]">Step 2: Product Data — Monthly Output & Cost</h2>
|
||||||
|
{(quarter || year) && (
|
||||||
|
<div className="text-sm font-medium text-gray-600">
|
||||||
|
Current Quarter: <span className="text-[#92722A]">Q{quarter}-{year}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!showInfoCard && (
|
{!showInfoCard && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowInfoCard(true)}
|
onClick={() => setShowInfoCard(true)}
|
||||||
@ -494,12 +803,28 @@ const ProductData = ({
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Product (HS Code - Name) <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Product (HS Code - Name) <span className="text-red-500">*</span></label>
|
||||||
<Select
|
<div>
|
||||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
|
<Select
|
||||||
options={productOptions}
|
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
|
||||||
value={p.product || ''}
|
options={productOptions}
|
||||||
onChange={(e) => updateProductField(p.id, 'product', e.target.value, productOptions)}
|
value={p.product || ''}
|
||||||
/>
|
onChange={(e) => {
|
||||||
|
// Clear error when user selects a product
|
||||||
|
if (formErrors[`product_${idx}`]) {
|
||||||
|
const newErrors = { ...formErrors };
|
||||||
|
delete newErrors[`product_${idx}`];
|
||||||
|
setFormErrors(newErrors);
|
||||||
|
}
|
||||||
|
updateProductField(p.id, 'product', e.target.value, productOptions);
|
||||||
|
}}
|
||||||
|
loading={isLoadingProducts}
|
||||||
|
disabled={isLoadingProducts}
|
||||||
|
error={!!formErrors[`product_${idx}`]}
|
||||||
|
/>
|
||||||
|
{formErrors[`product_${idx}`] && (
|
||||||
|
<p className="mt-1 text-sm text-red-600">{formErrors[`product_${idx}`]}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{productsError && productOptions.length === 0 && (
|
{productsError && productOptions.length === 0 && (
|
||||||
<p className="mt-1 text-xs text-red-600">{productsError}</p>
|
<p className="mt-1 text-xs text-red-600">{productsError}</p>
|
||||||
)}
|
)}
|
||||||
@ -511,8 +836,12 @@ const ProductData = ({
|
|||||||
options={unitOptions}
|
options={unitOptions}
|
||||||
value={p.unit || ''}
|
value={p.unit || ''}
|
||||||
onChange={(e) => updateProductField(p.id, 'unit', e.target.value, unitOptions)}
|
onChange={(e) => updateProductField(p.id, 'unit', e.target.value, unitOptions)}
|
||||||
|
error={!!formErrors[`unit_${idx}`]}
|
||||||
/>
|
/>
|
||||||
{unitsError && unitOptions.length === 0 && (
|
{formErrors[`unit_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`unit_${idx}`]}</p>
|
||||||
|
)}
|
||||||
|
{!formErrors[`unit_${idx}`] && unitsError && unitOptions.length === 0 && (
|
||||||
<p className="mt-1 text-xs text-red-600">{unitsError}</p>
|
<p className="mt-1 text-xs text-red-600">{unitsError}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -524,26 +853,38 @@ const ProductData = ({
|
|||||||
value={p.capacity || ''}
|
value={p.capacity || ''}
|
||||||
onChange={(e) => updateProductField(p.id, 'capacity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'capacity', e.target.value)}
|
||||||
required
|
required
|
||||||
|
error={!!formErrors[`capacity_${idx}`]}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`capacity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`capacity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<SectionBox title="PREVIOUS QUARTER (Q4-2024)" badgeBg="#E7F5FF" badgeText="#003CFF">
|
<SectionBox
|
||||||
|
title={quarterPeriods ? `PREVIOUS QUARTER (${quarterPeriods.previous_quarter}-${quarterPeriods.previous_year})` : 'PREVIOUS QUARTER (Q-YYYY)'}
|
||||||
|
badgeBg="#E7F5FF"
|
||||||
|
badgeText="#003CFF"
|
||||||
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-end space-x-4">
|
<div className="flex items-end space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Oct Qty</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_one } Qty
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.previousQuantity || ''}
|
value={p.octQuantity || ''}
|
||||||
onChange={(e) => updateProductField(p.id, 'previousQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'octQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Nov Qty</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_two } Qty
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
@ -553,7 +894,9 @@ const ProductData = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Dec Qty</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_three} Qty
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
@ -563,10 +906,11 @@ const ProductData = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-gray-200 my-2"></div>
|
<div className="flex items-end space-x-4 mt-4">
|
||||||
<div className="flex items-end space-x-4">
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Oct Cost (AED)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_one } Cost (AED)
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
@ -576,7 +920,9 @@ const ProductData = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Nov Cost (AED)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_two } Cost (AED)
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
@ -586,7 +932,9 @@ const ProductData = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Dec Cost (AED)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.previous_month?.previous_period_three } Cost (AED)
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
@ -599,76 +947,111 @@ const ProductData = ({
|
|||||||
</div>
|
</div>
|
||||||
</SectionBox>
|
</SectionBox>
|
||||||
|
|
||||||
<SectionBox title="CURRENT QUARTER (Q1-2025)" badgeBg="#F3FAF4" badgeText="#2F663C">
|
<SectionBox
|
||||||
|
title={quarterPeriods ? `CURRENT QUARTER (${quarterPeriods.current_quarter} ${quarterPeriods.current_year})` : `CURRENT QUARTER (${quarter}-${year})`}
|
||||||
|
badgeBg="#F3FAF4"
|
||||||
|
badgeText="#2F663C"
|
||||||
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-end space-x-4">
|
<div className="flex items-end space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Jan Qty<span className="text-red-600">*</span></label>
|
<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>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.janQuantity || ''}
|
value={p.janQuantity || ''}
|
||||||
required
|
error={!!formErrors[`janQuantity_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'janQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'janQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`janQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`janQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Feb Qty<span className="text-red-600">*</span></label>
|
<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>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.febQuantity || ''}
|
value={p.febQuantity || ''}
|
||||||
required
|
error={!!formErrors[`febQuantity_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'febQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'febQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`febQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`febQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Mar Qty<span className="text-red-600">*</span></label>
|
<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>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.marQuantity || ''}
|
value={p.marQuantity || ''}
|
||||||
required
|
error={!!formErrors[`marQuantity_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'marQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'marQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`marQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`marQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end space-x-4">
|
<div className="flex items-end space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Jan Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.current_month?.current_period_one } Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.janCost || ''}
|
value={p.janCost || ''}
|
||||||
required
|
error={!!formErrors[`janCost_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'janCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'janCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`janCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`janCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Feb Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.current_month?.current_period_two} Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.febCost || ''}
|
value={p.febCost || ''}
|
||||||
required
|
error={!!formErrors[`febCost_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'febCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'febCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`febCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`febCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Mar Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{quarterPeriods?.current_month?.current_period_three} Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
value={p.marCost || ''}
|
value={p.marCost || ''}
|
||||||
required
|
error={!!formErrors[`marCost_${idx}`]}
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
onChange={(e) => updateProductField(p.id, 'marCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'marCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`marCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`marCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -686,77 +1069,116 @@ const ProductData = ({
|
|||||||
</div>
|
</div>
|
||||||
</SectionBox>
|
</SectionBox>
|
||||||
|
|
||||||
<SectionBox title="NEXT QUARTER FORECAST (Q2-2025)" badgeBg="#FFF7E9" badgeText="#F29F0E">
|
<SectionBox
|
||||||
|
title={`NEXT QUARTER FORECAST (${quarterPeriods?.forecast_quarter || 'Q'}-${quarterPeriods?.forecast_year || '2025'})`}
|
||||||
|
badgeBg="#FFF7E9"
|
||||||
|
badgeText="#F29F0E"
|
||||||
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-end space-x-3">
|
<div className="flex items-end space-x-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Apr Qty<span className="text-red-600">*</span></label>
|
<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>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.aprQuantity || ''}
|
value={p.aprQuantity || ''}
|
||||||
|
error={!!formErrors[`aprQuantity_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'aprQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'aprQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`aprQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`aprQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">May Qty<span className="text-red-600">*</span></label>
|
<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>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.mayQuantity || ''}
|
value={p.mayQuantity || ''}
|
||||||
|
error={!!formErrors[`mayQuantity_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'mayQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'mayQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`mayQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`mayQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<div>
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Jun Qty<span className="text-red-600">*</span></label>
|
{quarterPeriods?.forecast_month?.forecast_period_three } Qty<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.junQuantity || ''}
|
value={p.junQuantity || ''}
|
||||||
|
error={!!formErrors[`junQuantity_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'junQuantity', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'junQuantity', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`junQuantity_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`junQuantity_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end space-x-3">
|
<div className="flex items-end space-x-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 w-26 mb-1">Apr Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 w-26 mb-1">
|
||||||
|
{quarterPeriods?.forecast_month?.forecast_period_one} Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.aprCost || ''}
|
value={p.aprCost || ''}
|
||||||
|
error={!!formErrors[`aprCost_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'aprCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'aprCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`aprCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`aprCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">May Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">
|
||||||
|
{quarterPeriods?.forecast_month?.forecast_period_two } Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.mayCost || ''}
|
value={p.mayCost || ''}
|
||||||
|
error={!!formErrors[`mayCost_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'mayCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'mayCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`mayCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`mayCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">Jun Cost (AED)<span className="text-red-600">*</span></label>
|
<label className="block text-sm font-medium text-gray-700 w-27 mb-1">
|
||||||
|
{quarterPeriods?.forecast_month?.forecast_period_three } Cost (AED)<span className="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
required
|
required
|
||||||
placeholder="Enter"
|
placeholder="Enter"
|
||||||
value={p.junCost || ''}
|
value={p.junCost || ''}
|
||||||
|
error={!!formErrors[`junCost_${idx}`]}
|
||||||
onChange={(e) => updateProductField(p.id, 'junCost', e.target.value)}
|
onChange={(e) => updateProductField(p.id, 'junCost', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
{formErrors[`junCost_${idx}`] && (
|
||||||
|
<p className="mt-1 text-xs text-red-600">{formErrors[`junCost_${idx}`]}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -806,7 +1228,7 @@ const ProductData = ({
|
|||||||
<span>Back</span>
|
<span>Back</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onNext}
|
onClick={handleNext}
|
||||||
className="inline-flex items-center gap-2 h-9 px-5 rounded-md bg-[#92722A] text-white hover:bg-[#7b5c1f] cursor-pointer"
|
className="inline-flex items-center gap-2 h-9 px-5 rounded-md bg-[#92722A] text-white hover:bg-[#7b5c1f] cursor-pointer"
|
||||||
>
|
>
|
||||||
<span>Next</span>
|
<span>Next</span>
|
||||||
|
|||||||
@ -763,10 +763,10 @@ const ReviewSubmit = ({
|
|||||||
<span>Back</span>
|
<span>Back</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
disabled={!confirm || isSubmitting || hasSubmitted}
|
disabled={!confirm || isSubmitting}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
className={`inline-flex items-center justify-center h-9 px-5 rounded-md transition-colors ${
|
className={`inline-flex items-center justify-center h-9 px-5 rounded-md transition-colors ${
|
||||||
!confirm || isSubmitting || hasSubmitted
|
!confirm || isSubmitting
|
||||||
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
||||||
: 'bg-[#92722A] text-white hover:bg-[#7b5c1f]'
|
: 'bg-[#92722A] text-white hover:bg-[#7b5c1f]'
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@ -62,7 +62,9 @@ const Survey = () => {
|
|||||||
const [productsData, setProductsData] = React.useState([createEmptyProduct(1)]);
|
const [productsData, setProductsData] = React.useState([createEmptyProduct(1)]);
|
||||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||||
const [hasSubmitted, setHasSubmitted] = React.useState(false);
|
const [hasSubmitted, setHasSubmitted] = React.useState(false);
|
||||||
|
const [showSuccessPopup, setShowSuccessPopup] = React.useState(false);
|
||||||
const [toast, setToast] = React.useState(null);
|
const [toast, setToast] = React.useState(null);
|
||||||
|
const [error, setError] = React.useState('');
|
||||||
const [submissionDetail, setSubmissionDetail] = React.useState(null);
|
const [submissionDetail, setSubmissionDetail] = React.useState(null);
|
||||||
const [viewLoading, setViewLoading] = React.useState(false);
|
const [viewLoading, setViewLoading] = React.useState(false);
|
||||||
const [viewError, setViewError] = React.useState('');
|
const [viewError, setViewError] = React.useState('');
|
||||||
@ -395,6 +397,21 @@ const Survey = () => {
|
|||||||
};
|
};
|
||||||
}, [establishmentData, parseNumber, productsData, stringify]);
|
}, [establishmentData, parseNumber, productsData, stringify]);
|
||||||
console.log("productsData",productsData)
|
console.log("productsData",productsData)
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (step < 3) {
|
||||||
|
setStep(step + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (step > 1) {
|
||||||
|
setStep(step - 1);
|
||||||
|
} else {
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = React.useCallback(async () => {
|
const handleSubmit = React.useCallback(async () => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
@ -402,9 +419,10 @@ const Survey = () => {
|
|||||||
const response = await submitSurvey(payload);
|
const response = await submitSurvey(payload);
|
||||||
|
|
||||||
// Navigate to overview page after successful submission
|
// Navigate to overview page after successful submission
|
||||||
navigate('/overview');
|
// navigate('/overview');
|
||||||
console.log('Survey submission response:', response);
|
console.log('Survey submission response:', response);
|
||||||
showToast('success', response?.message || 'You have successfully submitted the survey.');
|
showToast('success', response?.message || 'You have successfully submitted the survey.');
|
||||||
|
setShowSuccessPopup(true);
|
||||||
setHasSubmitted(true);
|
setHasSubmitted(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to submit survey', error);
|
console.error('Failed to submit survey', error);
|
||||||
@ -604,8 +622,12 @@ const Survey = () => {
|
|||||||
<ProductData
|
<ProductData
|
||||||
products={productsData}
|
products={productsData}
|
||||||
onProductsChange={setProductsData}
|
onProductsChange={setProductsData}
|
||||||
onBack={() => setStep(1)}
|
onNext={handleNext}
|
||||||
onNext={() => setStep(3)}
|
onBack={handleBack}
|
||||||
|
loading={isSubmitting}
|
||||||
|
error={error}
|
||||||
|
quarter={establishmentData.quarter}
|
||||||
|
year={establishmentData.year}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{step === 3 && (
|
{step === 3 && (
|
||||||
@ -628,6 +650,54 @@ const Survey = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Success Popup - Moved to root level */}
|
||||||
|
{showSuccessPopup && (
|
||||||
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/40 p-4">
|
||||||
|
<div className="bg-white w-[343px] rounded-lg shadow-2xl border border-gray-200">
|
||||||
|
<div className="p-6 text-center flex flex-col gap-6">
|
||||||
|
{/* Green Check Icon */}
|
||||||
|
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||||
|
<svg
|
||||||
|
className="h-6 w-6 text-green-600"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M5 13l4 4L19 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
|
Submission successful
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
|
<p className="text-sm text-gray-700">
|
||||||
|
Thank you. Your IP Quarterly Survey for <span className="font-semibold text-gray-900">{establishmentData?.quarter} {establishmentData?.year}</span> has been submitted.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="px-8 py-2.5 bg-[#92722A] text-white font-medium rounded-md hover:bg-[#7b5c1f] transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]"
|
||||||
|
onClick={() => {
|
||||||
|
setShowSuccessPopup(false);
|
||||||
|
navigate('/overview');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { getRequest, postRequest } from '@/services/api/CommonService';
|
|||||||
import resolveEstablishmentId from '@/services/utils/establishment';
|
import resolveEstablishmentId from '@/services/utils/establishment';
|
||||||
|
|
||||||
const endpoint = '/submissions';
|
const endpoint = '/submissions';
|
||||||
|
const QUARTER_PERIODS_ENDPOINT = 'https://ipi.venbait.in/api/getQuarterPeriods';
|
||||||
|
|
||||||
// Add this new function to fetch submissions with pagination
|
// Add this new function to fetch submissions with pagination
|
||||||
export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
|
export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
|
||||||
@ -51,9 +52,46 @@ export const fetchSubmissionDetail = async (submissionId, config = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add the new function to the default export
|
// Add the new function to the default export
|
||||||
|
export const getQuarterPeriods = async (currentYear, currentQuarter) => {
|
||||||
|
try {
|
||||||
|
const response = await postRequest(QUARTER_PERIODS_ENDPOINT, {
|
||||||
|
current_year: currentYear,
|
||||||
|
current_quarter: currentQuarter
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching quarter periods:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPreviousForecastData = async (establishmentId, quarter, year, productId) => {
|
||||||
|
try {
|
||||||
|
const response = await getRequest(
|
||||||
|
`https://ipi.venbait.in/api/submissions/getPreviousForecastData`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
establishment_id: establishmentId,
|
||||||
|
quarter: quarter,
|
||||||
|
year: year,
|
||||||
|
product_id: productId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// Check if response has data property and return it, otherwise return the response as is
|
||||||
|
return response.data || response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching previous forecast data:', error);
|
||||||
|
// Instead of throwing, return null to handle the error gracefully
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getSubmissions, // Add this line
|
getSubmissions,
|
||||||
submitSurvey,
|
submitSurvey,
|
||||||
fetchSubmissionHistory,
|
fetchSubmissionHistory,
|
||||||
fetchSubmissionDetail,
|
fetchSubmissionDetail,
|
||||||
|
getQuarterPeriods,
|
||||||
|
getPreviousForecastData
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user