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 {
|
||||
fetchVariationReasons,
|
||||
fetchZeroTargetReasons,
|
||||
@ -12,9 +13,17 @@ const calendarIcon = '/assets/images/Duedate.svg';
|
||||
const nextIcon = '/assets/images/mdi_page-next-outline.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 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 (
|
||||
<div className="relative">
|
||||
@ -26,7 +35,7 @@ const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
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>
|
||||
);
|
||||
@ -38,24 +47,32 @@ const Select = ({
|
||||
value = '',
|
||||
onChange,
|
||||
loading = false,
|
||||
disabled = false
|
||||
disabled = false,
|
||||
error = false,
|
||||
className = ''
|
||||
}) => {
|
||||
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 (
|
||||
<div className="relative">
|
||||
<select
|
||||
className={`block w-full h-10 rounded-md border-2 ${
|
||||
disabled ? 'bg-gray-50 cursor-not-allowed' : 'bg-white'
|
||||
} ${
|
||||
value ? 'border-[#92722A]' : 'border-[#CBA344]'
|
||||
} ${
|
||||
isFocused && !disabled ? 'border-[#92722A]' : ''
|
||||
} focus:ring-0 px-4 pr-10 text-sm appearance-none`}
|
||||
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' : bgColor
|
||||
} ${borderColor} ${className}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={() => !disabled && setIsFocused(true)}
|
||||
onBlur={() => !disabled && setIsFocused(false)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
disabled={disabled || loading}
|
||||
>
|
||||
<option value="" disabled>{loading ? 'Loading...' : placeholder}</option>
|
||||
@ -139,6 +156,8 @@ const ProductData = ({
|
||||
onBack = () => {},
|
||||
loading = false,
|
||||
error = '',
|
||||
quarter = '',
|
||||
year = '',
|
||||
}) => {
|
||||
const [showDataDefinitions, setShowDataDefinitions] = React.useState(true);
|
||||
const [showEntryRules, setShowEntryRules] = React.useState(true);
|
||||
@ -157,8 +176,32 @@ const ProductData = ({
|
||||
const [unitOptions, setUnitOptions] = React.useState([]);
|
||||
const [isLoadingUnits, setIsLoadingUnits] = React.useState(false);
|
||||
const [unitsError, setUnitsError] = React.useState('');
|
||||
const [activeTab, setActiveTab] = React.useState('current');
|
||||
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
|
||||
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(() => {
|
||||
const variationController = new AbortController();
|
||||
@ -265,6 +308,65 @@ const ProductData = ({
|
||||
}
|
||||
}, [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 = () => {
|
||||
if (products.length >= MAX_PRODUCTS) return;
|
||||
const newProduct = {
|
||||
@ -292,25 +394,156 @@ const ProductData = ({
|
||||
onProductsChange(products.filter((p) => p.id !== id));
|
||||
};
|
||||
|
||||
const updateProductField = (id, field, value, options = []) => {
|
||||
onProductsChange(
|
||||
products.map((product) => {
|
||||
if (product.id !== id) return product;
|
||||
|
||||
// When product is selected, store both ID and name
|
||||
const fetchPreviousForecastData = async (productId, establishmentId) => {
|
||||
console.log('fetchPreviousForecastData called with:', {
|
||||
productId,
|
||||
establishmentId,
|
||||
quarter,
|
||||
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') {
|
||||
const selectedProduct = options.find(p => p.value === value || p.id === value);
|
||||
return {
|
||||
const selectedProduct = options?.find(opt => opt.value === value);
|
||||
const updatedProduct = {
|
||||
...product,
|
||||
product: value,
|
||||
productName: selectedProduct?.label || selectedProduct?.name || '',
|
||||
productName: selectedProduct?.label || '',
|
||||
productId: selectedProduct?.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') {
|
||||
const selectedUnit = options.find(u => u.value === value || u.id === value);
|
||||
const selectedUnit = options?.find(u => u.value === value || u.id === value);
|
||||
return {
|
||||
...product,
|
||||
unit: value,
|
||||
@ -342,10 +575,79 @@ const ProductData = ({
|
||||
}
|
||||
|
||||
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 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>
|
||||
)}
|
||||
<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 && (
|
||||
<button
|
||||
onClick={() => setShowInfoCard(true)}
|
||||
@ -494,12 +803,28 @@ const ProductData = ({
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Product (HS Code - Name) <span className="text-red-500">*</span></label>
|
||||
<Select
|
||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
|
||||
options={productOptions}
|
||||
value={p.product || ''}
|
||||
onChange={(e) => updateProductField(p.id, 'product', e.target.value, productOptions)}
|
||||
/>
|
||||
<div>
|
||||
<Select
|
||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
|
||||
options={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 && (
|
||||
<p className="mt-1 text-xs text-red-600">{productsError}</p>
|
||||
)}
|
||||
@ -511,8 +836,12 @@ const ProductData = ({
|
||||
options={unitOptions}
|
||||
value={p.unit || ''}
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
@ -524,26 +853,38 @@ const ProductData = ({
|
||||
value={p.capacity || ''}
|
||||
onChange={(e) => updateProductField(p.id, 'capacity', e.target.value)}
|
||||
required
|
||||
error={!!formErrors[`capacity_${idx}`]}
|
||||
/>
|
||||
{formErrors[`capacity_${idx}`] && (
|
||||
<p className="mt-1 text-xs text-red-600">{formErrors[`capacity_${idx}`]}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="flex items-end space-x-4">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
placeholder="Enter"
|
||||
value={p.previousQuantity || ''}
|
||||
onChange={(e) => updateProductField(p.id, 'previousQuantity', e.target.value)}
|
||||
value={p.octQuantity || ''}
|
||||
onChange={(e) => updateProductField(p.id, 'octQuantity', e.target.value)}
|
||||
/>
|
||||
</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
|
||||
className="w-24"
|
||||
type="number"
|
||||
@ -553,7 +894,9 @@ const ProductData = ({
|
||||
/>
|
||||
</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
|
||||
className="w-24"
|
||||
type="number"
|
||||
@ -563,10 +906,11 @@ const ProductData = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-gray-200 my-2"></div>
|
||||
<div className="flex items-end space-x-4">
|
||||
<div className="flex items-end space-x-4 mt-4">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
@ -576,7 +920,9 @@ const ProductData = ({
|
||||
/>
|
||||
</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
|
||||
className="w-24"
|
||||
type="number"
|
||||
@ -586,7 +932,9 @@ const ProductData = ({
|
||||
/>
|
||||
</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
|
||||
className="w-24"
|
||||
placeholder="Enter"
|
||||
@ -599,76 +947,111 @@ const ProductData = ({
|
||||
</div>
|
||||
</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="flex items-end space-x-4">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.janQuantity || ''}
|
||||
required
|
||||
error={!!formErrors[`janQuantity_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.febQuantity || ''}
|
||||
required
|
||||
error={!!formErrors[`febQuantity_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.marQuantity || ''}
|
||||
required
|
||||
error={!!formErrors[`marQuantity_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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 className="flex items-end space-x-4">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.janCost || ''}
|
||||
required
|
||||
error={!!formErrors[`janCost_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.febCost || ''}
|
||||
required
|
||||
error={!!formErrors[`febCost_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
value={p.marCost || ''}
|
||||
required
|
||||
error={!!formErrors[`marCost_${idx}`]}
|
||||
placeholder="Enter"
|
||||
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>
|
||||
@ -686,77 +1069,116 @@ const ProductData = ({
|
||||
</div>
|
||||
</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="flex items-end space-x-3">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
value={p.aprQuantity || ''}
|
||||
error={!!formErrors[`aprQuantity_${idx}`]}
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
value={p.mayQuantity || ''}
|
||||
error={!!formErrors[`mayQuantity_${idx}`]}
|
||||
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>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Jun Qty<span className="text-red-600">*</span></label>
|
||||
<div>
|
||||
<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>
|
||||
<Input
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
value={p.junQuantity || ''}
|
||||
value={p.junQuantity || ''}
|
||||
error={!!formErrors[`junQuantity_${idx}`]}
|
||||
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 className="flex items-end space-x-3">
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
value={p.aprCost || ''}
|
||||
error={!!formErrors[`aprCost_${idx}`]}
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
value={p.mayCost || ''}
|
||||
error={!!formErrors[`mayCost_${idx}`]}
|
||||
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>
|
||||
<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
|
||||
className="w-24"
|
||||
type="number"
|
||||
required
|
||||
placeholder="Enter"
|
||||
placeholder="Enter"
|
||||
value={p.junCost || ''}
|
||||
error={!!formErrors[`junCost_${idx}`]}
|
||||
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>
|
||||
@ -806,7 +1228,7 @@ const ProductData = ({
|
||||
<span>Back</span>
|
||||
</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"
|
||||
>
|
||||
<span>Next</span>
|
||||
|
||||
@ -763,10 +763,10 @@ const ReviewSubmit = ({
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<button
|
||||
disabled={!confirm || isSubmitting || hasSubmitted}
|
||||
disabled={!confirm || isSubmitting}
|
||||
onClick={onSubmit}
|
||||
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-[#92722A] text-white hover:bg-[#7b5c1f]'
|
||||
}`}
|
||||
|
||||
@ -62,7 +62,9 @@ const Survey = () => {
|
||||
const [productsData, setProductsData] = React.useState([createEmptyProduct(1)]);
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
const [hasSubmitted, setHasSubmitted] = React.useState(false);
|
||||
const [showSuccessPopup, setShowSuccessPopup] = React.useState(false);
|
||||
const [toast, setToast] = React.useState(null);
|
||||
const [error, setError] = React.useState('');
|
||||
const [submissionDetail, setSubmissionDetail] = React.useState(null);
|
||||
const [viewLoading, setViewLoading] = React.useState(false);
|
||||
const [viewError, setViewError] = React.useState('');
|
||||
@ -395,6 +397,21 @@ const Survey = () => {
|
||||
};
|
||||
}, [establishmentData, parseNumber, productsData, stringify]);
|
||||
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 () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
@ -402,9 +419,10 @@ const Survey = () => {
|
||||
const response = await submitSurvey(payload);
|
||||
|
||||
// Navigate to overview page after successful submission
|
||||
navigate('/overview');
|
||||
// navigate('/overview');
|
||||
console.log('Survey submission response:', response);
|
||||
showToast('success', response?.message || 'You have successfully submitted the survey.');
|
||||
setShowSuccessPopup(true);
|
||||
setHasSubmitted(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to submit survey', error);
|
||||
@ -604,8 +622,12 @@ const Survey = () => {
|
||||
<ProductData
|
||||
products={productsData}
|
||||
onProductsChange={setProductsData}
|
||||
onBack={() => setStep(1)}
|
||||
onNext={() => setStep(3)}
|
||||
onNext={handleNext}
|
||||
onBack={handleBack}
|
||||
loading={isSubmitting}
|
||||
error={error}
|
||||
quarter={establishmentData.quarter}
|
||||
year={establishmentData.year}
|
||||
/>
|
||||
)}
|
||||
{step === 3 && (
|
||||
@ -628,6 +650,54 @@ const Survey = () => {
|
||||
)}
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { getRequest, postRequest } from '@/services/api/CommonService';
|
||||
import resolveEstablishmentId from '@/services/utils/establishment';
|
||||
|
||||
const endpoint = '/submissions';
|
||||
const QUARTER_PERIODS_ENDPOINT = 'https://ipi.venbait.in/api/getQuarterPeriods';
|
||||
|
||||
// Add this new function to fetch submissions with pagination
|
||||
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
|
||||
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 {
|
||||
getSubmissions, // Add this line
|
||||
getSubmissions,
|
||||
submitSurvey,
|
||||
fetchSubmissionHistory,
|
||||
fetchSubmissionDetail,
|
||||
getQuarterPeriods,
|
||||
getPreviousForecastData
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user