diff --git a/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx b/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx index d55f30c..45a41ab 100644 --- a/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx +++ b/ipi-survey-platform/src/components/dashboard/SubmissionHistory.jsx @@ -214,6 +214,7 @@ const handleResubmit = async (submission) => { } }; const handleEdit = async (submission) => { + localStorage.removeItem('autoSaveData'); if (submission.status.toLowerCase() === 'draft') { try { const submissionData = await fetchSubmissionDetail(submission.id); diff --git a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx index ea9f236..e8447be 100644 --- a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx +++ b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx @@ -653,7 +653,7 @@ React.useEffect(() => { -

Productss

+

Products

{loadingProducts ? (
diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx index ba443d8..ec6cbf0 100644 --- a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx +++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx @@ -563,6 +563,26 @@ const ProductData = ({ persistLocalDraft(latestDraftRef.current); }, [persistLocalDraft]); + // Function to synchronize draft products with current establishment products + const synchronizeDraftWithEstablishmentProducts = React.useCallback((draftProducts, currentEstablishmentProducts) => { + if (!Array.isArray(draftProducts) || !Array.isArray(currentEstablishmentProducts)) { + return draftProducts; + } + + // Create a Set of valid product IDs from current establishment products + const validProductIds = new Set( + currentEstablishmentProducts.map(p => p.value || p.id || p.product_id || p.productId) + ); + + // Filter draft products to only include those that are still valid + const synchronizedProducts = draftProducts.filter(draftProduct => { + const productId = draftProduct.product || draftProduct.productId || draftProduct.product_id; + return productId && validProductIds.has(productId.toString()); + }); + + return synchronizedProducts; + }, []); + const handleRemarksChange = (e) => { const value = e.target.value; setRemarks(value); @@ -621,16 +641,81 @@ const ProductData = ({ if (restoredDraftKeyRef.current === key && restoredDraftSignatureRef.current === parsedSignature) { return; } - onProductsChange(parsed.products); - if (Array.isArray(parsed.deletedProducts)) setDeletedProducts(parsed.deletedProducts); - if (typeof parsed.remarks === 'string') setRemarks(parsed.remarks); - restoredDraftKeyRef.current = key; - restoredDraftSignatureRef.current = parsedSignature; + + // Synchronize draft products with current establishment products + const synchronizedProducts = synchronizeDraftWithEstablishmentProducts(parsed.products, productOptions); + + // Only restore if we have valid products after synchronization + if (synchronizedProducts.length > 0) { + onProductsChange(synchronizedProducts); + if (Array.isArray(parsed.deletedProducts)) setDeletedProducts(parsed.deletedProducts); + if (typeof parsed.remarks === 'string') setRemarks(parsed.remarks); + restoredDraftKeyRef.current = key; + restoredDraftSignatureRef.current = JSON.stringify({ + products: synchronizedProducts, + deletedProducts: Array.isArray(parsed.deletedProducts) ? parsed.deletedProducts : [], + remarks: typeof parsed.remarks === 'string' ? parsed.remarks : '', + }); + + // Update the localStorage with synchronized data to prevent future issues + persistLocalDraft({ + products: synchronizedProducts, + deletedProducts: Array.isArray(parsed.deletedProducts) ? parsed.deletedProducts : [], + remarks: typeof parsed.remarks === 'string' ? parsed.remarks : '', + }); + } else { + // If no valid products remain, clear the draft and start fresh + onProductsChange([{ id: Date.now() }]); + // Clear the invalid draft from localStorage + if (typeof window !== 'undefined') { + localStorage.removeItem(key); + } + } } } catch (e) { // ignore parse errors } - }, [getLocalDraftKey, onProductsChange, products]); + }, [getLocalDraftKey, onProductsChange, products, synchronizeDraftWithEstablishmentProducts, productOptions]); + + // Synchronize existing products when establishment products change + React.useEffect(() => { + if (productOptions.length === 0) return; + + // Check if current products have any that are no longer valid + const validProductIds = new Set( + productOptions.map(p => p.value || p.id || p.product_id || p.productId) + ); + + const hasInvalidProducts = products.some(product => { + const productId = product.product || product.productId || product.product_id; + return productId && !validProductIds.has(productId.toString()); + }); + + if (hasInvalidProducts) { + // Filter out invalid products + const validProducts = products.filter(product => { + const productId = product.product || product.productId || product.product_id; + return !productId || validProductIds.has(productId.toString()); + }); + + // Update products state + onProductsChange(validProducts); + + // Update the draft with filtered products + persistLocalDraft({ + products: validProducts, + deletedProducts, + remarks, + }); + + // Update the latest draft ref + latestDraftRef.current = { + products: validProducts, + deletedProducts, + remarks, + }; + } + }, [productOptions, products, onProductsChange, deletedProducts, remarks, persistLocalDraft]); // Debounced autosave to localStorage whenever user changes anything React.useEffect(() => { @@ -959,11 +1044,69 @@ const existingProduct = currentSubmission.products?.find(p => other_variation_reason: product.otherVariationReason || '', zero_target_reason_master_id: product.zeroTargetReason === null ? null : (parseInt(product.zeroTargetReason) || 0), other_zero_target_reason: product.otherZeroTargetReason || '', - remarks: product.remarks || remarks || '' // Use product.remarks if available, otherwise use the component's remarks state + remarks: product.remarks || remarks || '', // Use product.remarks if available, otherwise use the component's remarks state + is_active: true // Mark current products as active }; }); - // Add deleted products with is_active: false if we're in draft edit mode + // Get all products from current submission to identify removed ones + const currentSubmissionProducts = currentSubmission.products || []; + const currentProductIds = new Set( + products.map(p => p.product || p.productId || p.product_id) + ); + + // Find products that are in current submission but not in current products (removed products) + const removedProducts = currentSubmissionProducts.filter(submissionProduct => { + const productId = submissionProduct.product_id || submissionProduct.product?.id; + return productId && !currentProductIds.has(productId.toString()); + }); + + // Add removed products with is_active: false + if (removedProducts.length > 0) { + const removedFormattedProducts = removedProducts.map(product => { + return { + id: product.id || 0, + product_id: product.product_id || product.product?.id || null, + unit_id: product.unit_id?.toString() || '0', + annual_installed_capacity: product.annual_installed_capacity || '0', + previous_quantity_period_one: product.previous_quantity_period_one || '0', + previous_quantity_period_two: product.previous_quantity_period_two || '0', + previous_quantity_period_three: product.previous_quantity_period_three || '0', + previous_cost_period_one: product.previous_cost_period_one || '0', + previous_cost_period_two: product.previous_cost_period_two || '0', + previous_cost_period_three: product.previous_cost_period_three || '0', + current_quantity_period_one: product.current_quantity_period_one || '0', + current_quantity_period_two: product.current_quantity_period_two || '0', + current_quantity_period_three: product.current_quantity_period_three || '0', + current_cost_period_one: product.current_cost_period_one || '0', + current_cost_period_two: product.current_cost_period_two || '0', + current_cost_period_three: product.current_cost_period_three || '0', + forecast_quantity_period_one: product.forecast_quantity_period_one || '0', + forecast_quantity_period_two: product.forecast_quantity_period_two || '0', + forecast_quantity_period_three: product.forecast_quantity_period_three || '0', + forecast_cost_period_one: product.forecast_cost_period_one || '0', + forecast_cost_period_two: product.forecast_cost_period_two || '0', + forecast_cost_period_three: product.forecast_cost_period_three || '0', + previous_quantity: product.previous_quantity || '0', + previous_cost: product.previous_cost || '0', + current_quantity: product.current_quantity || '0', + current_cost: product.current_cost || '0', + forecast_quantity: product.forecast_quantity || '0', + forecast_cost: product.forecast_cost || '0', + variation_reason_master_id: product.variation_reason_master_id || null, + other_variation_reason: product.other_variation_reason || '', + zero_target_reason_master_id: product.zero_target_reason_master_id || null, + other_zero_target_reason: product.other_zero_target_reason || '', + remarks: product.remarks || '', + is_active: false // Mark removed products as inactive + }; + }); + + // Combine active and removed products + formattedProducts.push(...removedFormattedProducts); + } + + // Also add deleted products from local state if we're in edit mode if (location.state?.isEdit && deletedProducts.length > 0) { const deletedFormattedProducts = deletedProducts.map(product => { const selectedProduct = productOptions.find(p => p.value === product.product); @@ -1345,17 +1488,17 @@ useEffect(() => { const [lastAutoSave, setLastAutoSave] = React.useState(null); const autoSaveInterval = React.useRef(null); - // Auto-save every 2 minutes + // Auto-save every 1 second React.useEffect(() => { // Clear any existing interval if (autoSaveInterval.current) { clearInterval(autoSaveInterval.current); } - // Set up new interval for auto-save every 2 minutes (120000 ms) + // Set up new interval for auto-save every 1 second (1000 ms) autoSaveInterval.current = setInterval(() => { autoSaveDraft(); - }, 120000); // 2 minutes = 120000 ms + }, 1000); // 1 second = 1000 ms // Cleanup interval on unmount return () => { diff --git a/ipi-survey-platform/src/pages/Overview/Overview.jsx b/ipi-survey-platform/src/pages/Overview/Overview.jsx index 3b2a6e4..2754a18 100644 --- a/ipi-survey-platform/src/pages/Overview/Overview.jsx +++ b/ipi-survey-platform/src/pages/Overview/Overview.jsx @@ -178,6 +178,7 @@ const showToast = (message, type = 'success') => { onClick={async (e) => { e.preventDefault(); e.stopPropagation(); + localStorage.removeItem('autoSaveData'); try { const submissionData = await fetchSubmissionDetail(record.id); navigate('/survey', {