diff --git a/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx b/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx index dfcac28..6ae8e3e 100644 --- a/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx +++ b/ipi-survey-platform/src/components/dashboard/SurveyStatus.jsx @@ -84,17 +84,29 @@ export const SurveyStatus = () => { }, []); const handleStartSurvey = (survey) => { - // Store the survey data in localStorage - localStorage.setItem('currentSurvey', JSON.stringify({ + // Prepare survey data with all required fields + const surveyData = { quarter: survey.quarter, year: survey.year, startDate: survey.startDate, endDate: survey.endDate, - title: survey.title - })); + title: survey.title, + // Include establishment data if available + establishment: survey.establishment || null + }; - // Navigate to the survey page - navigate('/survey'); + // Store the survey data in localStorage + localStorage.setItem('currentSurvey', JSON.stringify(surveyData)); + + // Navigate to the survey page with survey data in state + navigate('/survey', { + state: { + survey: surveyData, + isResubmit: survey.status === 'Submitted', // Mark as resubmit if status is 'Submitted' + // Include establishment data in the location state + establishment: survey.establishment || null + } + }); }; const nextDeadline = dashboardData?.next_deadline; diff --git a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx index c8490b1..3e3b750 100644 --- a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx +++ b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx @@ -157,24 +157,24 @@ const EstablishmentInfo = ({ }, [data]); const info = React.useMemo( - () => ({ - quarter: surveyData.quarter || '', - year: surveyData.year || '', - establishmentName: '', - permanentFactoryCode: '', - industryCode: '', - licenseNumber: '', - isicCode: '', - emirate: '', - establishment_contact_email: data?.establishment_contact_email || '', - employeeInfo: { - ...defaultEmployeeInfo, - ...(data.employeeInfo || {}), - }, - ...data, - }), - [data] - ); + () => ({ + quarter: surveyData.quarter || '', + year: surveyData.year || '', + establishmentName: data.establishmentName || '', + permanentFactoryCode: data.permanentFactoryCode || '', + industryCode: data.industryCode || '', + licenseNumber: data.licenseNumber || '', + isicCode: data.isicCode || '', + emirate: data.emirate || '', + establishment_contact_email: data.establishment_contact_email || '', + employeeInfo: { + ...defaultEmployeeInfo, + ...(data.employeeInfo || {}), + }, + ...data, + }), + [data, surveyData.quarter, surveyData.year] +); const handleFieldChange = (field) => (event) => { onChange({ diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx index 61bff1b..8d65ca6 100644 --- a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx +++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx @@ -314,22 +314,23 @@ const ProductData = ({ const [unitsError, setUnitsError] = React.useState(''); const [activeTab, setActiveTab] = React.useState('current'); const [isInitialLoad, setIsInitialLoad] = React.useState(true); + const [quarterPeriods, setQuarterPeriods] = React.useState(null); + const [isLoadingPeriods, setIsLoadingPeriods] = React.useState(false); + const [showOtherVariationReason, setShowOtherVariationReason] = React.useState({}); + const [showOtherZeroTargetReason, setShowOtherZeroTargetReason] = React.useState({}); + const [formErrors, setFormErrors] = React.useState({}); const [remarks, setRemarks] = React.useState(() => { if (typeof window !== 'undefined') { - const savedRemarks = localStorage.getItem('surveyRemarks'); - return savedRemarks || ''; + return localStorage.getItem('surveyRemarks') || ''; } return ''; }); - const [formErrors, setFormErrors] = React.useState({}); - const [quarterPeriods, setQuarterPeriods] = useState(null); - const [isLoadingPeriods, setIsLoadingPeriods] = useState(false); const handleRemarksChange = (e) => { - const newRemarks = e.target.value; - setRemarks(newRemarks); + const value = e.target.value; + setRemarks(value); if (typeof window !== 'undefined') { - localStorage.setItem('surveyRemarks', newRemarks); + localStorage.setItem('surveyRemarks', value); } }; @@ -362,12 +363,13 @@ const ProductData = ({ setReasonsError(''); try { const reasons = await fetchVariationReasons({ signal: variationController.signal }); - setVariationReasons(reasons); + console.log('Loaded variation reasons:', reasons); + setVariationReasons(reasons || []); setReasonsError(''); } catch (error) { + console.error('Failed to fetch variation reasons:', error); if (error.name !== 'AbortError') { - // console.error('Failed to fetch variation reasons', error); - // setReasonsError('Failed to load variation reasons.'); + setReasonsError('Failed to load variation reasons.'); } } finally { setIsLoadingReasons(false); @@ -464,12 +466,12 @@ const ProductData = ({ const formattedProducts = response.data.establishment_products.map(item => { const product = item.product || {}; return { - value: product.hs_code || '', + value: item.product_id, // Use product_id as the value label: product.product_name ? `${product.product_name} (${product.hs_code || 'N/A'})` : 'Unknown Product', - productId: item.product_id, // Include the actual product ID - productData: product // Include the full product data + originalData: product, // Store the full product data + hsCode: product.hs_code // Store HS code separately for display }; }); @@ -674,86 +676,96 @@ const location = useLocation(); } }; - // 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 + // Check if 'Others' is selected for variation reason + if (field === 'variationReason' || field === 'otherVariationReason') { + if (field === 'variationReason') { + const selectedReason = options?.find(r => r.value === value || r.id === value); + // Check both name and label for 'other' to be more flexible + const isOtherSelected = selectedReason && ( + (selectedReason.name && selectedReason.name.toLowerCase().includes('other')) || + (selectedReason.label && selectedReason.label.toLowerCase().includes('other')) + ); + + console.log('Selected reason:', selectedReason, 'Is other:', isOtherSelected); + + // Update the showOtherVariationReason state for this product + setShowOtherVariationReason(prev => ({ + ...prev, + [id]: isOtherSelected + })); + + // If not 'Others', clear any existing other reason + if (!isOtherSelected) { + return { + ...product, + variationReason: value, + variationReasonName: selectedReason?.label || selectedReason?.name || '', + otherVariationReason: '', + [field]: value + }; + } + + return { + ...product, + variationReason: value, + variationReasonName: selectedReason?.label || selectedReason?.name || '', + [field]: value + }; + } else { + // Handle otherVariationReason update + return { + ...product, + [field]: value + }; + } + + return { + ...product, + variationReason: value, + variationReasonName: selectedReason?.label || selectedReason?.name || '', + [field]: value + }; + } + + // Handle product selection if (field === 'product') { const selectedProduct = options?.find(opt => opt.value === value); const updatedProduct = { ...product, - product: selectedProduct?.value || value, + product: selectedProduct?.value || value, productName: selectedProduct?.label || '', - productId: selectedProduct?.value || '' + hsCode: selectedProduct?.hsCode || '', + productData: selectedProduct?.originalData || null }; // 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'); - + const establishmentId = sessionStorage.getItem('establishment_id') || + localStorage.getItem('establishmentId') || + localStorage.getItem('establishment_id'); if (establishmentId && quarter && year) { - // Get the actual product ID from the selected product object - const selectedProductId = selectedProduct.originalData?.id || selectedProduct.value; + // Use the product_id directly as the value + const productId = selectedProduct.value; + console.log('Fetching forecast data for:', { - productId: selectedProductId, + productId, establishmentId, quarter, year, storageSource: sessionStorage.getItem('establishment_id') ? 'sessionStorage' : 'localStorage' }); - handleProductSelect(selectedProductId, establishmentId, id); + + // Call handleProductSelect with the product ID + handleProductSelect(productId, 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' - } + hasYear: !!year }); } } @@ -773,6 +785,29 @@ const location = useLocation(); // When variation reason is selected, store both ID and name if (field === 'variationReason') { const selectedReason = options.find(r => r.value === value || r.id === value); + // Check both name and label for 'other' to be more flexible + const isOtherSelected = selectedReason && ( + (selectedReason.name && selectedReason.name.toLowerCase().includes('other')) || + (selectedReason.label && selectedReason.label.toLowerCase().includes('other')) + ); + + // Update the showOtherVariationReason state for this product + setShowOtherVariationReason(prev => ({ + ...prev, + [id]: isOtherSelected + })); + + // If not 'Others', clear any existing other reason + if (!isOtherSelected) { + return { + ...product, + variationReason: value, + variationReasonName: selectedReason?.label || selectedReason?.name || '', + otherVariationReason: '', + [field]: value + }; + } + return { ...product, variationReason: value, @@ -780,10 +815,35 @@ const location = useLocation(); [field]: value }; } - - // When zero target reason is selected, store both ID and name + + // Handle zero target reason selection if (field === 'zeroTargetReason') { - const selectedReason = options.find(r => r.value === value || r.id === value); + const selectedReason = options?.find(r => r.value === value || r.id === value); + // Check both name and label for 'other' to be more flexible + const isOtherSelected = selectedReason && ( + (selectedReason.name && selectedReason.name.toLowerCase().includes('other')) || + (selectedReason.label && selectedReason.label.toLowerCase().includes('other')) + ); + + console.log('Selected zero target reason:', selectedReason, 'Is other:', isOtherSelected); + + // Update the showOtherZeroTargetReason state for this product + setShowOtherZeroTargetReason(prev => ({ + ...prev, + [id]: isOtherSelected + })); + + // If not 'Others', clear any existing other reason + if (!isOtherSelected) { + return { + ...product, + zeroTargetReason: value, + zeroTargetReasonName: selectedReason?.label || selectedReason?.name || '', + otherZeroTargetReason: '', + [field]: value + }; + } + return { ...product, zeroTargetReason: value, @@ -791,76 +851,72 @@ const location = useLocation(); [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); - } + + return { ...product, [field]: value }; } - - onProductsChange(updatedProducts); - }; + 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); + } + } - const handleProductSelect = async (productId, establishmentId, id) => { - if (!productId || !establishmentId) { - console.warn('Missing required parameters in handleProductSelect:', { - hasProductId: !!productId, - hasEstablishmentId: !!establishmentId - }); + 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; } - 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; + console.log('Processing forecast data:', data); + + 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 || '' + }; } - - // 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 - }); - } - }; + 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; @@ -1323,6 +1379,18 @@ const location = useLocation(); value={p.variationReason || ''} onChange={(e) => updateProductField(p.id, 'variationReason', e.target.value, variationReasons)} /> + {(showOtherVariationReason[p.id] || (p.variationReason && (p.variationReason.toString().toLowerCase().includes('other') || (variationReasons.find(r => r.value === p.variationReason)?.name?.toLowerCase().includes('other') || variationReasons.find(r => r.value === p.variationReason)?.label?.toLowerCase().includes('other'))))) && ( +
Please provide details about the variation reason
+{reasonsError}
)} @@ -1458,6 +1526,18 @@ const location = useLocation(); value={p.zeroTargetReason || ''} onChange={(e) => updateProductField(p.id, 'zeroTargetReason', e.target.value, zeroTargetReasons)} /> + {(showOtherZeroTargetReason[p.id] || (p.zeroTargetReason && (p.zeroTargetReason.toString().toLowerCase().includes('other') || (zeroTargetReasons.find(r => r.value === p.zeroTargetReason)?.name?.toLowerCase().includes('other') || zeroTargetReasons.find(r => r.value === p.zeroTargetReason)?.label?.toLowerCase().includes('other'))))) && ( +Please provide details about the zero target reason
+{zeroReasonsError}
)} diff --git a/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx index ee4bb25..acb9d69 100644 --- a/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx +++ b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx @@ -222,9 +222,11 @@ const buildProductRows = (products = [], options = {}) => { q4_nov_cost: formatNumber(product.novCost) || '—', q4_dec_cost: formatNumber(product.decCost) || '—', - // Reasons and remarks - use display names + // Reasons and remarks - use display names and include other reasons variationReason: variationReasonName, + otherVariationReason: product.otherVariationReason || '', zeroTargetReason: zeroTargetReasonName, + otherZeroTargetReason: product.otherZeroTargetReason || '', remarks: product.remarks || '', // Keep the raw product data for debugging @@ -243,6 +245,80 @@ const buildProductRows = (products = [], options = {}) => { return rows.filter(Boolean); }; +const formatSubmissionData = (establishment, products, remarks = '') => { + if (!establishment) { + throw new Error('Establishment data is required'); + } + + // Get employee info with proper fallbacks + const employeeInfo = establishment.employeeInfo || {}; + const emiratiMale = parseInt(employeeInfo.emiratiMale) || 0; + const emiratiFemale = parseInt(employeeInfo.emiratiFemale) || 0; + const nonEmiratiMale = parseInt(employeeInfo.nonEmiratiMale) || 0; + const nonEmiratiFemale = parseInt(employeeInfo.nonEmiratiFemale) || 0; + const totalEmirati = emiratiMale + emiratiFemale; + const totalEmployees = totalEmirati + nonEmiratiMale + nonEmiratiFemale; + + // Format products data + const formattedProducts = products.map(product => ({ + product_id: parseInt(product.productId) || 0, + unit_id: parseInt(product.unit) || 0, + annual_installed_capacity: product.capacity?.toString() || '', + + // Previous quarter (Oct-Dec) + previous_quantity_period_one: product.octQuantity?.toString() || '0', + previous_quantity_period_two: product.novQuantity?.toString() || '0', + previous_quantity_period_three: product.decQuantity?.toString() || '0', + previous_cost_period_one: product.octCost?.toString() || '0', + previous_cost_period_two: product.novCost?.toString() || '0', + previous_cost_period_three: product.decCost?.toString() || '0', + + // Current quarter (Jan-Mar) + current_quantity_period_one: product.janQuantity?.toString() || '0', + current_quantity_period_two: product.febQuantity?.toString() || '0', + current_quantity_period_three: product.marQuantity?.toString() || '0', + current_cost_period_one: product.janCost?.toString() || '0', + current_cost_period_two: product.febCost?.toString() || '0', + current_cost_period_three: product.marCost?.toString() || '0', + + // Next quarter forecast (Apr-Jun) + forecast_quantity_period_one: product.aprQuantity?.toString() || '0', + forecast_quantity_period_two: product.mayQuantity?.toString() || '0', + forecast_quantity_period_three: product.junQuantity?.toString() || '0', + forecast_cost_period_one: product.aprCost?.toString() || '0', + forecast_cost_period_two: product.mayCost?.toString() || '0', + forecast_cost_period_three: product.junCost?.toString() || '0', + + // Totals (calculated on backend if needed) + previous_quantity: '', + previous_cost: '', + current_quantity: '', + current_cost: '', + forecast_quantity: '', + forecast_cost: '', + + // Reasons and remarks + variation_reason_master_id: product.variationReason?.toString() || '', + other_variation_reason: product.otherVariationReason?.toString() || '', + zero_target_reason_master_id: product.zeroTargetReason?.toString() || '', + other_zero_target_reason: product.otherZeroTargetReason?.toString() || '', + remarks: product.remarks || remarks || '' + })); + + return { + establishment_id: parseInt(establishment.id) || 0, + quarter: establishment.quarter || 'Q1', // Should come from your app state + year: parseInt(establishment.year) || new Date().getFullYear(), + emirati_male: emiratiMale, + emirati_female: emiratiFemale, + non_emirati_male: nonEmiratiMale, + non_emirati_female: nonEmiratiFemale, + total_emirati: totalEmirati, + total_employees: totalEmployees, + products: formattedProducts + }; +}; + const ReviewSubmit = ({ establishment, products = [], @@ -253,15 +329,37 @@ const ReviewSubmit = ({ isSubmitting = false, hasSubmitted = false, submitButtonText = 'Submit', + quarter = 'Q1', + year = new Date().getFullYear() }) => { const [confirm, setConfirm] = React.useState(false); const [remarks, setRemarks] = React.useState(() => { - // Load remarks from localStorage on component mount if (typeof window !== 'undefined') { return localStorage.getItem('surveyRemarks') || ''; } return ''; }); + + const handleSubmit = () => { + try { + const submissionData = formatSubmissionData( + { ...establishment, quarter, year }, + products, + remarks + ); + console.log('Submitting data:', submissionData); + onSubmit(submissionData); + setConfirm(false); + } catch (error) { + console.error('Error formatting submission data:', error); + if (onToastClose) { + onToastClose({ + type: 'error', + message: 'Failed to prepare data for submission. Please check your input.' + }); + } + } + }; const establishmentDetails = React.useMemo(() => buildEstablishmentDetails(establishment), [establishment]); const employmentDetails = React.useMemo(() => buildEmploymentDetails(establishment), [establishment]); const productRows = React.useMemo(() => buildProductRows(products), [products]); @@ -689,7 +787,14 @@ const ReviewSubmit = ({ -- --