chnages in product details

This commit is contained in:
Malini 2025-11-13 13:53:14 +05:30
parent 2edb38db14
commit 8eee109297
6 changed files with 646 additions and 207 deletions

View File

@ -1,6 +1,6 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchSubmissionDetail } from '@/services/submissions/submissionService';
import { fetchSubmissionDetail, getQuarterPeriods } from '@/services/submissions/submissionService';
import Table from '@/components/common/Table';
import ProductDetails from './ProductDetails';
@ -196,9 +196,31 @@ export const DetailedOverview = ({ submission, onBack }) => {
URL.revokeObjectURL(url);
}, [filteredProducts]);
const handleViewDetails = (product) => {
setSelectedProduct(product);
setShowToast(true);
const handleViewDetails = async (product) => {
try {
setLoadingProduct(true);
// Extract quarter and year from the submission data
const quarter = submissionData.quarter; // e.g., 'Q1'
const year = submissionData.year; // e.g., 2025
// Fetch quarter periods
const response = await getQuarterPeriods(year, quarter);
// Update the selected product with quarter periods data
setSelectedProduct({
...product,
quarterPeriods: response.data
});
setShowToast(true);
} catch (error) {
console.error('Error fetching quarter periods:', error);
// Still show the product details even if quarter periods fetch fails
setSelectedProduct(product);
setShowToast(true);
} finally {
setLoadingProduct(false);
}
};
const handleToastShown = () => {
@ -418,13 +440,15 @@ export const DetailedOverview = ({ submission, onBack }) => {
// If a product is selected, show the product details view
if (selectedProduct) {
return (
<ProductDetails
product={selectedProduct}
onBack={() => setSelectedProduct(null)}
<ProductDetails
product={selectedProduct}
onClose={() => setShowToast(false)}
onBack={() => setSelectedProduct(null)} // Add this line to handle back navigation
showToast={showToast}
onToastShown={handleToastShown}
submissionDate={selectedProduct?.created_at || submissionData?.created_at || submissionData?.updated_at || new Date().toISOString()}
submissionStatus={selectedProduct?.status || submissionData?.status || 'submitted'}
quarterPeriods={selectedProduct?.quarterPeriods || quarterPeriods}
/>
);
}

View File

@ -2,7 +2,21 @@ import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastShown, submissionDate, submissionStatus = 'approved' }) => {
// Default onBack function if not provided
const defaultOnBack = () => {
console.log('No onBack handler provided');
};
const ProductDetails = ({
product = {},
onClose = () => {},
showToast = false,
onToastShown = () => {},
submissionDate = new Date().toISOString(),
submissionStatus = 'submitted',
quarterPeriods = null,
onBack = defaultOnBack
}) => {
const [toast, setToast] = useState(null);
const toastShownRef = useRef(false);
@ -21,11 +35,12 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
};
useEffect(() => {
if (shouldShowToast && !toastShownRef.current) {
if (showToast && !toastShownRef.current) {
const formattedDate = formatDate(submissionDate);
if (submissionStatus) {
const status = String(submissionStatus).toLowerCase();
console.log("Status",status)
const toastConfig = {
approved: {
type: 'approved',
@ -76,7 +91,7 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
toastShownRef.current = true;
onToastShown?.();
}
}, [shouldShowToast, product?.is_active, onToastShown, submissionDate, submissionStatus]);
}, [showToast, product?.is_active, onToastShown, submissionDate, submissionStatus]);
const closeToast = () => {
setToast(null);
@ -86,6 +101,34 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
useEffect(() => {
}, [product?.status, product?.is_active]);
// Default quarter periods if not provided
const defaultQuarterPeriods = {
previous_quarter: 'Q4',
previous_year: new Date().getFullYear() - 1,
current_quarter: 'Q1',
current_year: new Date().getFullYear(),
forecast_quarter: 'Q2',
forecast_year: new Date().getFullYear(),
previous_month: {
previous_period_one: 'Oct',
previous_period_two: 'Nov',
previous_period_three: 'Dec'
},
current_month: {
current_period_one: 'Jan',
current_period_two: 'Feb',
current_period_three: 'Mar'
},
forecast_month: {
forecast_period_one: 'Apr',
forecast_period_two: 'May',
forecast_period_three: 'Jun'
}
};
// Use provided quarterPeriods or fall back to defaults
const periods = quarterPeriods || product?.quarterPeriods || defaultQuarterPeriods;
const navigate = useNavigate();
// Function to save resubmission data to localStorage
@ -369,13 +412,31 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
<div className="space-y-1">
<label className="block text-sm font-medium text-[#6B7280]">Status</label>
<div className="mt-1">
<span className={`inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium ${
formattedProduct.status === 'Active'
? 'bg-[#F3FAF4] text-[#2F663C] ring-1 ring-inset ring-green-600/20'
: 'bg-yellow-50 text-yellow-700 ring-1 ring-inset ring-yellow-600/20'
}`}>
{formattedProduct.status}
</span>
{(() => {
const status = String(submissionStatus).toLowerCase();
let statusClass = 'px-2 py-0.5 text-xs font-medium';
let displayText = status.charAt(0).toUpperCase() + status.slice(1);
if (status === 'approved') {
statusClass += ' bg-[#F3FAF4] text-[#2F663C]';
} else if (status === 'rejected') {
statusClass += ' bg-red-50 text-red-600';
} else if (status === 'submitted' || status === 'active') {
statusClass += ' bg-[#E7F5FF] text-[#043DFF]';
displayText = status === 'active' ? 'Submitted' : displayText;
} else if (status === 'resubmitted') {
statusClass += ' bg-[#E7F5FF] text-[#043DFF]';
} else {
// Default/pending state
statusClass += ' bg-amber-50 text-amber-700';
}
return (
<span className={`inline-flex items-center rounded ${statusClass}`}>
{displayText}
</span>
);
})()}
</div>
</div>
</div>
@ -389,28 +450,46 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
<th rowSpan="2" className="border border-gray-300 py-2 px-3 text-left bg-[#F2ECCF] w-[140px]">
Metric
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q4-2024 (Previous Quarter)
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#E9F6FF]">
{periods.previous_quarter}-{periods.previous_year} (Previous Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q1-2025 (Current Quarter)
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F3FAF4]">
{periods.current_quarter}-{periods.current_year} (Current Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q2-2025 (Next Quarter)
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#FFF7E9]">
{periods.forecast_quarter}-{periods.forecast_year} (Next Quarter)
</th>
</tr>
{/* Second header row */}
<tr className="bg-white text-[13px] font-medium">
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Oct</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Nov</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Dec</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Jan</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Feb</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Mar</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">Apr</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">May</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">Jun</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{periods.previous_month.previous_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{periods.previous_month.previous_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{periods.previous_month.previous_period_three}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{periods.current_month.current_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{periods.current_month.current_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{periods.current_month.current_period_three}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{periods.forecast_month.forecast_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{periods.forecast_month.forecast_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{periods.forecast_month.forecast_period_three}
</th>
</tr>
</thead>
@ -513,17 +592,32 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
</div>
</div> */}
</div>
<button
<div className="flex justify-between items-center mb-4">
{/* <button
onClick={onBack}
className="inline-flex items-center gap-2 h-9 px-4 mt-8 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
className="flex items-center text-gray-600 hover:text-gray-800"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clipRule="evenodd" />
</svg>
<span>Back</span>
</button>
Back to List
</button> */}
{/* <button
onClick={onClose}
className="text-gray-500 hover:text-gray-700"
aria-label="Close"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button> */}
</div>
</div>
);
};
ProductDetails.defaultProps = {
onBack: () => {}
};
export default ProductDetails;

View File

@ -104,6 +104,12 @@ const EstablishmentInfo = ({
// Initialize from location state if available, otherwise use defaults
if (location.state?.survey) {
const { quarter, year, endDate } = location.state.survey;
console.log('Initializing survey data from location.state:', { quarter, year, endDate });
// Save to localStorage
const surveyPeriod = { quarter, year, endDate };
localStorage.setItem('currentSurveyPeriod', JSON.stringify(surveyPeriod));
return {
quarter: quarter || '',
year: year || '',
@ -112,12 +118,27 @@ const EstablishmentInfo = ({
}
// Or use current quarter/year as fallback
const current = getCurrentQuarterAndYear();
console.log('Using current quarter/year as fallback:', current);
// Save to localStorage
const surveyPeriod = {
quarter: current.quarter,
year: current.year,
endDate: current.endDate
};
localStorage.setItem('currentSurveyPeriod', JSON.stringify(surveyPeriod));
return {
quarter: current.quarter,
year: current.year,
endDate: current.endDate
};
});
// Log when surveyData changes
React.useEffect(() => {
console.log('surveyData updated:', surveyData);
}, [surveyData]);
// Update the ref whenever surveyData changes
React.useEffect(() => {

View File

@ -56,7 +56,8 @@ const SearchableSelect = ({
loading = false,
disabled = false,
error = false,
className = ''
className = '',
displayValue = undefined
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState('');
@ -97,7 +98,9 @@ const SearchableSelect = ({
// Get selected option label
const selectedOption = options.find(opt => opt.value === value);
const displayValue = selectedOption ? selectedOption.label : '';
const displayValueToShow = displayValue !== undefined
? displayValue
: (selectedOption ? selectedOption.label : '');
// Handle option selection
const handleSelect = (option) => {
@ -125,7 +128,7 @@ const SearchableSelect = ({
className={`flex items-center justify-between h-10 px-4 py-2 text-sm rounded-md border-2 ${borderColor} ${bgColor} ${disabled ? 'bg-gray-50 cursor-not-allowed' : 'cursor-pointer'}`}
onClick={() => !disabled && !loading && setIsOpen(!isOpen)}
>
<span className="truncate">{displayValue || placeholder}</span>
<span className="truncate">{displayValueToShow || placeholder}</span>
{loading ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-[#92722A] border-t-transparent ml-2" />
) : (
@ -311,7 +314,13 @@ const ProductData = ({
const [productsError, setProductsError] = React.useState('');
const [unitOptions, setUnitOptions] = React.useState([]);
const [isLoadingUnits, setIsLoadingUnits] = React.useState(false);
const [unitsError, setUnitsError] = React.useState('');
const [unitsError, setUnitsError] = React.useState(null);
// Debug: Log products data when it changes
useEffect(() => {
console.log('Products data updated:', products);
}, [products]);
const [activeTab, setActiveTab] = React.useState('current');
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
const [quarterPeriods, setQuarterPeriods] = React.useState(null);
@ -336,12 +345,28 @@ const ProductData = ({
useEffect(() => {
const fetchQuarterPeriods = async () => {
if (!quarter || !year) return;
try {
// Try to get survey period from localStorage
const savedSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
let periodYear = year;
let periodQuarter = quarter;
if (savedSurveyPeriod) {
const { quarter: savedQuarter, year: savedYear } = JSON.parse(savedSurveyPeriod);
console.log('Using survey period from localStorage:', { savedQuarter, savedYear });
periodYear = savedYear || year;
periodQuarter = savedQuarter || quarter;
}
if (!periodQuarter || !periodYear) {
console.warn('No quarter or year available for fetching periods');
return;
}
setIsLoadingPeriods(true);
const quarterNumber = quarter.replace('Q', ''); // Convert 'Q3' to '3'
const response = await getQuarterPeriods(parseInt(year), `Q${quarterNumber}`);
const quarterNumber = periodQuarter.replace('Q', ''); // Convert 'Q3' to '3'
console.log('Fetching quarter periods with:', { year: periodYear, quarter: `Q${quarterNumber}` });
const response = await getQuarterPeriods(parseInt(periodYear), `Q${quarterNumber}`);
setQuarterPeriods(response.data);
} catch (error) {
console.error('Failed to fetch quarter periods:', error);
@ -526,6 +551,7 @@ const location = useLocation();
year: year || ''
}));
}, [quarter, year]);
console.log("surveyDatatest", surveyData);
const requiredMessage = 'Required';
const validateForm = () => {
@ -916,11 +942,24 @@ const handleProductSelect = async (productId, establishmentId, id) => {
)}
<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>
{(surveyData.quarter || surveyData.year) && (
<div className="text-sm font-medium text-gray-600">
Quarter: <span className="text-[#92722A]">{surveyData.quarter}-{surveyData.year}</span>
</div>
)}
{(() => {
// Try to get survey period from localStorage first
const savedSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
let displayQuarter = quarter;
let displayYear = year;
if (savedSurveyPeriod) {
const { quarter: savedQuarter, year: savedYear } = JSON.parse(savedSurveyPeriod);
displayQuarter = savedQuarter || quarter;
displayYear = savedYear || year;
}
return (displayQuarter || displayYear) ? (
<div className="text-sm font-medium text-gray-600">
Quarter: <span className="text-[#92722A]">{displayQuarter}-{displayYear}</span>
</div>
) : null;
})()}
</div>
{!showInfoCard && (
<button
@ -956,15 +995,26 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<div className="flex items-center space-x-1">
<img src={calendarIcon} alt="" className="h-4 w-4" />
<span className="font-medium">Due:</span>
{surveyData.endDate ? (
new Date(surveyData.endDate).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
) : (
<span className="text-amber-600">To be announced</span>
)}
{(() => {
// Try to get endDate from localStorage first
const savedSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
let endDateToUse = surveyData.endDate;
if (savedSurveyPeriod) {
const { endDate } = JSON.parse(savedSurveyPeriod);
endDateToUse = endDate || surveyData.endDate;
}
return endDateToUse ? (
new Date(endDateToUse).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
) : (
<span className="text-amber-600">To be announced</span>
);
})()}
</div>
<div className="flex items-center space-x-1">
@ -1069,7 +1119,8 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<SearchableSelect
placeholder={isLoadingProducts ? 'Loading products...' : 'Search product HS code...'}
options={productOptions}
value={p.productId?.toString() || p.product?.toString() || ''}
value={p.productId?.toString() || p.product_id?.toString() || p.product?.toString() || ''}
displayValue={p.name || (p.hs_code && p.productName ? `${p.hs_code} - ${p.productName}` : p.productName) || ''}
onChange={async (e) => {
const selectedValue = e.target.value;
// Clear error when user selects a product
@ -1080,26 +1131,63 @@ const handleProductSelect = async (productId, establishmentId, id) => {
}
// Find the selected product from options
const selectedProduct = productOptions.find(opt => opt.value === selectedValue);
const selectedProduct = productOptions.find(opt => opt.value === selectedValue || opt.productId?.toString() === selectedValue);
// Get unit information from the selected product
const unitId = selectedProduct?.originalData?.['product.unit.id'];
const unitId = selectedProduct?.originalData?.['product.unit.id'] ||
selectedProduct?.unitId ||
selectedProduct?.originalData?.unit_id;
const unitName = selectedProduct?.originalData?.['product.unit.uom'] ||
selectedProduct?.originalData?.unit?.uom ||
selectedProduct?.unitName ||
selectedProduct?.unit || '';
// Create updated product with all necessary fields including unit info
// Get the display name for the product - check multiple possible locations
let displayName = '';
if (selectedProduct) {
// Get HS code from various possible locations
const hsCode = selectedProduct.hsCode ||
selectedProduct.originalData?.['product.hs_code'] ||
selectedProduct.originalData?.hs_code ||
'';
// Get product name from various possible locations
const productName = selectedProduct.name ||
selectedProduct.originalData?.['product.name'] ||
selectedProduct.originalData?.product_name ||
selectedProduct.originalData?.name ||
'';
// Combine HS Code and Product Name for display
displayName = hsCode && productName ?
`${hsCode} - ${productName}` :
productName || selectedProduct.label ||
(selectedProduct.originalData ?
(selectedProduct.originalData['product.product_name'] ||
selectedProduct.originalData.product_name ||
selectedProduct.originalData.name) :
(selectedProduct.name || '')
);
}
// Create updated product with all necessary fields
const updatedProduct = {
...p,
product: selectedProduct?.value || selectedValue,
productId: selectedProduct?.value || selectedValue,
product_id: selectedProduct?.value || selectedValue,
hs_code: selectedProduct?.hsCode || p.hs_code,
name: selectedProduct?.name || p.name,
product: selectedValue, // Store the ID as string
productId: selectedValue, // Also store as productId for consistency
product_id: selectedValue, // For backward compatibility
hs_code: selectedProduct?.hsCode ||
(selectedProduct?.originalData ?
(selectedProduct.originalData['product.hs_code'] ||
selectedProduct.originalData.hs_code) :
'') || p.hs_code,
name: displayName || p.name, // Use the formatted display name
originalData: selectedProduct?.originalData || p.originalData,
// Set unit information
unit: unitId ? unitId.toString() : '',
unitName: unitName,
unit_id: unitId,
unit_id: unitId ? unitId.toString() : '',
// Clear any previous unit-related errors
...(formErrors[`unit_${idx}`] && { unitError: undefined })
};
@ -1165,28 +1253,23 @@ const handleProductSelect = async (productId, establishmentId, id) => {
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Unit <span className="text-red-500">*</span></label>
<SearchableSelect
placeholder={p.unitName || (isLoadingUnits ? 'Loading units...' : 'Search unit...')}
placeholder={isLoadingUnits ? 'Loading units...' : 'Select unit'}
options={unitOptions}
value={p.unit}
value={p.unit?.toString() || ''}
displayValue={p.unitName || ''}
onChange={(e) => {
// Clear error when user selects a unit
if (formErrors[`unit_${idx}`]) {
const newErrors = { ...formErrors };
delete newErrors[`unit_${idx}`];
setFormErrors(newErrors);
}
// Find the selected unit to get its name
const selectedUnit = unitOptions.find(unit => unit.value === e.target.value);
updateProductField(p.id, 'unit', e.target.value, unitOptions);
// Also update unit name if a valid unit is selected
if (selectedUnit) {
updateProductField(p.id, 'unitName', selectedUnit.label || selectedUnit.name);
updateProductField(p.id, 'unit_id', selectedUnit.id || e.target.value);
}
const unitId = e.target.value;
const selectedUnit = unitOptions.find(u => u.value === unitId);
const originalUnit = selectedUnit?.originalData || {};
const displayName = originalUnit.uom_short_name && originalUnit.uom
? `${originalUnit.uom_short_name.toUpperCase()} - ${originalUnit.uom}`
: selectedUnit?.label || '';
updateProductField(idx, 'unit', unitId);
updateProductField(idx, 'unitName', displayName);
}}
loading={isLoadingUnits}
error={!!formErrors[`unit_${idx}`]}
isDisabled={!!p.unitName} // Disable if unit is auto-filled from product
disabled={!!p.unitName} // Disable if unit is auto-filled from product
/>
<div className="h-5">
{formErrors[`unit_${idx}`] && (

View File

@ -1,5 +1,6 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Table from '@/components/common/Table';
import { getQuarterPeriods } from '@/services/submissions/submissionService';
const caretUpSrc = '/assets/images/caret-up.svg';
const caretDownSrc = '/assets/images/CaretDown-black.svg';
const establishmentIconSrc = '/assets/images/Establishment.svg';
@ -341,7 +342,41 @@ const ReviewSubmit = ({
quarter = 'Q1',
year = new Date().getFullYear()
}) => {
const [quarterPeriods, setQuarterPeriods] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [confirm, setConfirm] = React.useState(false);
useEffect(() => {
const fetchQuarterPeriods = async () => {
try {
setIsLoading(true);
const savedSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
if (!savedSurveyPeriod) {
throw new Error('No survey period found in localStorage');
}
const { quarter, year } = JSON.parse(savedSurveyPeriod);
if (!quarter || !year) {
throw new Error('Incomplete survey period data');
}
const response = await getQuarterPeriods(year, quarter);
if (response.status === 'success') {
setQuarterPeriods(response.data);
} else {
throw new Error('Failed to fetch quarter periods');
}
} catch (err) {
console.error('Error fetching quarter periods:', err);
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchQuarterPeriods();
}, []);
const [remarks, setRemarks] = React.useState(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem('surveyRemarks') || '';
@ -728,28 +763,71 @@ const ReviewSubmit = ({
<th rowSpan="2" className="border border-gray-300 py-2 px-3 text-left bg-[#F2ECCF] w-[140px]">
Metric
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q4-2024 (Previous Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q1-2025 (Current Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Q2-2025 (Next Quarter)
</th>
{isLoading ? (
<th colSpan="9" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
Loading quarters...
</th>
) : error ? (
<th colSpan="9" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF] text-red-500">
{error}
</th>
) : (
<>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
{quarterPeriods?.previous_quarter}-{quarterPeriods?.previous_year} (Previous Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
{quarterPeriods?.current_quarter}-{quarterPeriods?.current_year} (Current Quarter)
</th>
<th colSpan="3" className="border border-gray-300 py-2 px-3 bg-[#F2ECCF]">
{quarterPeriods?.forecast_quarter}-{quarterPeriods?.forecast_year} (Next Quarter)
</th>
</>
)}
</tr>
{/* Second header row */}
<tr className="bg-white text-[13px] font-medium">
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Oct</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Nov</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">Dec</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Jan</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Feb</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">Mar</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">Apr</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">May</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">Jun</th>
{isLoading ? (
<th colSpan="9" className="py-2">Loading months...</th>
) : error ? (
<th colSpan="9" className="py-2 text-red-500">{error}</th>
) : (
<>
{/* Previous Quarter Months */}
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{quarterPeriods?.previous_month?.previous_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{quarterPeriods?.previous_month?.previous_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#E9F6FF] text-[#286CFF]">
{quarterPeriods?.previous_month?.previous_period_three}
</th>
{/* Current Quarter Months */}
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{quarterPeriods?.current_month?.current_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{quarterPeriods?.current_month?.current_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#F3FAF4] text-[#2F663C]">
{quarterPeriods?.current_month?.current_period_three}
</th>
{/* Next Quarter Months */}
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{quarterPeriods?.forecast_month?.forecast_period_one}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{quarterPeriods?.forecast_month?.forecast_period_two}
</th>
<th className="border border-gray-300 py-2 px-3 bg-[#FFF7E9] text-[#F29F0E]">
{quarterPeriods?.forecast_month?.forecast_period_three}
</th>
</>
)}
</tr>
</thead>

View File

@ -80,39 +80,51 @@ const Survey = () => {
setStep(initialStepFromState);
}, [initialStepFromState]);
useEffect(() => {
const initializeSurvey = React.useCallback(() => {
// First check if we have survey data in the navigation state
if (locationState?.survey) {
const { quarter, year, establishment } = locationState.survey;
setSurveyData(locationState.survey);
// Only update if the data has changed
setSurveyData(prevData => {
return JSON.stringify(prevData) === JSON.stringify(locationState.survey)
? prevData
: locationState.survey;
});
// If we have establishment data in the navigation state, use it
if (establishment) {
setEstablishmentData(prev => ({
...prev,
quarter: quarter || '',
year: year || '',
establishmentName: establishment.establishmentName || prev.establishmentName,
permanentFactoryCode: establishment.permanentFactoryCode || prev.permanentFactoryCode,
industryCode: establishment.industryCode || prev.industryCode,
licenseNumber: establishment.licenseNumber || prev.licenseNumber,
isicCode: establishment.isicCode || prev.isicCode,
emirate: establishment.emirate || prev.emirate,
employeeInfo: {
emiratiMale: establishment.employeeInfo?.emiratiMale || prev.employeeInfo.emiratiMale,
emiratiFemale: establishment.employeeInfo?.emiratiFemale || prev.employeeInfo.emiratiFemale,
nonEmiratiMale: establishment.employeeInfo?.nonEmiratiMale || prev.employeeInfo.nonEmiratiMale,
nonEmiratiFemale: establishment.employeeInfo?.nonEmiratiFemale || prev.employeeInfo.nonEmiratiFemale,
totalEmirati: establishment.employeeInfo?.totalEmirati || prev.employeeInfo.totalEmirati,
totalEmployees: establishment.employeeInfo?.totalEmployees || prev.employeeInfo.totalEmployees
}
}));
setEstablishmentData(prev => {
const newData = {
...prev,
quarter: quarter || '',
year: year || '',
establishmentName: establishment.establishmentName || prev.establishmentName,
permanentFactoryCode: establishment.permanentFactoryCode || prev.permanentFactoryCode,
industryCode: establishment.industryCode || prev.industryCode,
licenseNumber: establishment.licenseNumber || prev.licenseNumber,
isicCode: establishment.isicCode || prev.isicCode,
emirate: establishment.emirate || prev.emirate,
employeeInfo: {
emiratiMale: establishment.employeeInfo?.emiratiMale ?? prev.employeeInfo.emiratiMale,
emiratiFemale: establishment.employeeInfo?.emiratiFemale ?? prev.employeeInfo.emiratiFemale,
nonEmiratiMale: establishment.employeeInfo?.nonEmiratiMale ?? prev.employeeInfo.nonEmiratiMale,
nonEmiratiFemale: establishment.employeeInfo?.nonEmiratiFemale ?? prev.employeeInfo.nonEmiratiFemale,
totalEmirati: establishment.employeeInfo?.totalEmirati ?? prev.employeeInfo.totalEmirati,
totalEmployees: establishment.employeeInfo?.totalEmployees ?? prev.employeeInfo.totalEmployees
}
};
return JSON.stringify(prev) === JSON.stringify(newData) ? prev : newData;
});
} else {
setEstablishmentData(prev => ({
...prev,
quarter: quarter || '',
year: year || ''
}));
setEstablishmentData(prev => {
const newData = {
...prev,
quarter: quarter || '',
year: year || ''
};
return JSON.stringify(prev) === JSON.stringify(newData) ? prev : newData;
});
}
// Also save to localStorage for page refreshes
@ -122,35 +134,45 @@ const Survey = () => {
const savedSurvey = localStorage.getItem('currentSurvey');
if (savedSurvey) {
const surveyData = JSON.parse(savedSurvey);
setSurveyData(surveyData);
// Only update if the data has changed
setSurveyData(prevData => {
return JSON.stringify(prevData) === savedSurvey ? prevData : surveyData;
});
if (surveyData.establishment) {
const { establishment } = surveyData;
setEstablishmentData(prev => ({
...prev,
quarter: surveyData.quarter || '',
year: surveyData.year || '',
establishmentName: establishment.establishmentName || prev.establishmentName,
permanentFactoryCode: establishment.permanentFactoryCode || prev.permanentFactoryCode,
industryCode: establishment.industryCode || prev.industryCode,
licenseNumber: establishment.licenseNumber || prev.licenseNumber,
isicCode: establishment.isicCode || prev.isicCode,
emirate: establishment.emirate || prev.emirate,
employeeInfo: {
emiratiMale: establishment.employeeInfo?.emiratiMale || prev.employeeInfo.emiratiMale,
emiratiFemale: establishment.employeeInfo?.emiratiFemale || prev.employeeInfo.emiratiFemale,
nonEmiratiMale: establishment.employeeInfo?.nonEmiratiMale || prev.employeeInfo.nonEmiratiMale,
nonEmiratiFemale: establishment.employeeInfo?.nonEmiratiFemale || prev.employeeInfo.nonEmiratiFemale,
totalEmirati: establishment.employeeInfo?.totalEmirati || prev.employeeInfo.totalEmirati,
totalEmployees: establishment.employeeInfo?.totalEmployees || prev.employeeInfo.totalEmployees
}
}));
setEstablishmentData(prev => {
const newData = {
...prev,
quarter: surveyData.quarter || '',
year: surveyData.year || '',
establishmentName: establishment.establishmentName || prev.establishmentName,
permanentFactoryCode: establishment.permanentFactoryCode || prev.permanentFactoryCode,
industryCode: establishment.industryCode || prev.industryCode,
licenseNumber: establishment.licenseNumber || prev.licenseNumber,
isicCode: establishment.isicCode || prev.isicCode,
emirate: establishment.emirate || prev.emirate,
employeeInfo: {
emiratiMale: establishment.employeeInfo?.emiratiMale ?? prev.employeeInfo.emiratiMale,
emiratiFemale: establishment.employeeInfo?.emiratiFemale ?? prev.employeeInfo.emiratiFemale,
nonEmiratiMale: establishment.employeeInfo?.nonEmiratiMale ?? prev.employeeInfo.nonEmiratiMale,
nonEmiratiFemale: establishment.employeeInfo?.nonEmiratiFemale ?? prev.employeeInfo.nonEmiratiFemale,
totalEmirati: establishment.employeeInfo?.totalEmirati ?? prev.employeeInfo.totalEmirati,
totalEmployees: establishment.employeeInfo?.totalEmployees ?? prev.employeeInfo.totalEmployees
}
};
return JSON.stringify(prev) === JSON.stringify(newData) ? prev : newData;
});
} else {
setEstablishmentData(prev => ({
...prev,
quarter: surveyData.quarter || '',
year: surveyData.year || ''
}));
setEstablishmentData(prev => {
const newData = {
...prev,
quarter: surveyData.quarter || '',
year: surveyData.year || ''
};
return JSON.stringify(prev) === JSON.stringify(newData) ? prev : newData;
});
}
}
}
@ -346,53 +368,125 @@ const Survey = () => {
const mapProducts = (detailProducts = []) => {
if (!Array.isArray(detailProducts)) return [];
return detailProducts.map((product, index) => ({
id: product?.id ?? index + 1,
productId: product?.product_id || product?.product?.id || '',
product: ensureString(product?.product?.product_name) || ensureString(product?.product_id) || '',
unit: ensureString(product?.unit?.uom) || ensureString(product?.unit?.name) || ensureString(product?.unit_id) || '',
unitId: product?.unit_id || product?.unit?.id || '',
capacity: ensureString(product?.annual_installed_capacity),
return detailProducts.map((product, index) => {
// Extract product and unit information
const productId = product?.product_id || product?.product?.id || '';
const productName = ensureString(product?.product?.product_name) ||
ensureString(product?.product?.name) ||
ensureString(product?.product_name) ||
'';
const hsCode = ensureString(product?.product?.hs_code) ||
ensureString(product?.hs_code) ||
'';
// Previous Quarter (Q4) - October, November, December
octQuantity: ensureString(product?.previous_quantity_period_one || ''),
novQuantity: ensureString(product?.previous_quantity_period_two || ''),
decQuantity: ensureString(product?.previous_quantity_period_three || ''),
octCost: ensureString(product?.previous_cost_period_one || ''),
novCost: ensureString(product?.previous_cost_period_two || ''),
decCost: ensureString(product?.previous_cost_period_three || ''),
// Combine HS Code and Product Name for display
const displayName = (hsCode && productName)
? `${hsCode} - ${productName}`
: productName || `Product ${index + 1}`;
const unitId = product?.unit_id || product?.unit?.id || '';
const unitName = ensureString(product?.unit?.uom) ||
ensureString(product?.unit?.name) ||
ensureString(unitId) || '';
// Current Quarter (Q1) - January, February, March
janQuantity: ensureString(product?.current_quantity_period_one || ''),
febQuantity: ensureString(product?.current_quantity_period_two || ''),
marQuantity: ensureString(product?.current_quantity_period_three || ''),
janCost: ensureString(product?.current_cost_period_one || ''),
febCost: ensureString(product?.current_cost_period_two || ''),
marCost: ensureString(product?.current_cost_period_three || ''),
console.log('Product mapping:', {
id: productId,
productName,
hsCode,
displayName,
unitId,
unitName,
originalProduct: product
});
return {
id: product?.id ?? index + 1,
productId: productId,
product: productId, // This should match the value in productOptions
productName: productName,
hsCode: hsCode, // Store HS code separately
name: displayName, // This will be used for display
unit: unitId, // This should match the value in unitOptions
unitName: unitName,
unitId: unitId,
capacity: ensureString(product?.annual_installed_capacity),
// Previous Quarter (Q4) - October, November, December
octQuantity: ensureString(product?.previous_quantity_period_one || ''),
novQuantity: ensureString(product?.previous_quantity_period_two || ''),
decQuantity: ensureString(product?.previous_quantity_period_three || ''),
octCost: ensureString(product?.previous_cost_period_one || ''),
novCost: ensureString(product?.previous_cost_period_two || ''),
decCost: ensureString(product?.previous_cost_period_three || ''),
// Current Quarter (Q1) - January, February, March
janQuantity: ensureString(product?.current_quantity_period_one || ''),
febQuantity: ensureString(product?.current_quantity_period_two || ''),
marQuantity: ensureString(product?.current_quantity_period_three || ''),
janCost: ensureString(product?.current_cost_period_one || ''),
febCost: ensureString(product?.current_cost_period_two || ''),
marCost: ensureString(product?.current_cost_period_three || ''),
// Forecast Quarter (Q2) - April, May, June
aprQuantity: ensureString(product?.forecast_quantity_period_one || ''),
mayQuantity: ensureString(product?.forecast_quantity_period_two || ''),
junQuantity: ensureString(product?.forecast_quantity_period_three || ''),
aprCost: ensureString(product?.forecast_cost_period_one || ''),
mayCost: ensureString(product?.forecast_cost_period_two || ''),
junCost: ensureString(product?.forecast_cost_period_three || ''),
// Totals (for backward compatibility)
previousQuantity: ensureString(product?.previous_quantity || ''),
previousCost: ensureString(product?.previous_cost || ''),
currentQuantity: ensureString(product?.current_quantity || ''),
currentCost: ensureString(product?.current_cost || ''),
forecastQuantity: ensureString(product?.forecast_quantity || ''),
forecastCost: ensureString(product?.forecast_cost || ''),
// Reasons and remarks
variationReason: ensureString(product?.variation_reason_master_id || ''),
otherVariationReason: ensureString(product?.other_variation_reason || ''),
zeroTargetReason: ensureString(product?.zero_target_reason_master_id || ''),
otherZeroTargetReason: ensureString(product?.other_zero_target_reason || ''),
remarks: ensureString(product?.remarks || '')
};
});
// decQuantity: ensureString(product?.previous_quantity_period_three || ''),
// octCost: ensureString(product?.previous_cost_period_one || ''),
// novCost: ensureString(product?.previous_cost_period_two || ''),
// decCost: ensureString(product?.previous_cost_period_three || ''),
// Forecast Quarter (Q2) - April, May, June
aprQuantity: ensureString(product?.forecast_quantity_period_one || ''),
mayQuantity: ensureString(product?.forecast_quantity_period_two || ''),
junQuantity: ensureString(product?.forecast_quantity_period_three || ''),
aprCost: ensureString(product?.forecast_cost_period_one || ''),
mayCost: ensureString(product?.forecast_cost_period_two || ''),
junCost: ensureString(product?.forecast_cost_period_three || ''),
// // Current Quarter (Q1) - January, February, March
// janQuantity: ensureString(product?.current_quantity_period_one || ''),
// febQuantity: ensureString(product?.current_quantity_period_two || ''),
// marQuantity: ensureString(product?.current_quantity_period_three || ''),
// janCost: ensureString(product?.current_cost_period_one || ''),
// febCost: ensureString(product?.current_cost_period_two || ''),
// marCost: ensureString(product?.current_cost_period_three || ''),
// Totals (for backward compatibility)
previousQuantity: ensureString(product?.previous_quantity || ''),
previousCost: ensureString(product?.previous_cost || ''),
currentQuantity: ensureString(product?.current_quantity || ''),
currentCost: ensureString(product?.current_cost || ''),
forecastQuantity: ensureString(product?.forecast_quantity || ''),
forecastCost: ensureString(product?.forecast_cost || ''),
// // Forecast Quarter (Q2) - April, May, June
// aprQuantity: ensureString(product?.forecast_quantity_period_one || ''),
// mayQuantity: ensureString(product?.forecast_quantity_period_two || ''),
// junQuantity: ensureString(product?.forecast_quantity_period_three || ''),
// aprCost: ensureString(product?.forecast_cost_period_one || ''),
// mayCost: ensureString(product?.forecast_cost_period_two || ''),
// junCost: ensureString(product?.forecast_cost_period_three || ''),
// Reasons and remarks
variationReason: ensureString(product?.variation_reason_master_id || ''),
otherVariationReason: ensureString(product?.other_variation_reason || ''),
zeroTargetReason: ensureString(product?.zero_target_reason_master_id || ''),
otherZeroTargetReason: ensureString(product?.other_zero_target_reason || ''),
remarks: ensureString(product?.remarks || '')
}));
// // Totals (for backward compatibility)
// previousQuantity: ensureString(product?.previous_quantity || ''),
// previousCost: ensureString(product?.previous_cost || ''),
// currentQuantity: ensureString(product?.current_quantity || ''),
// currentCost: ensureString(product?.current_cost || ''),
// forecastQuantity: ensureString(product?.forecast_quantity || ''),
// forecastCost: ensureString(product?.forecast_cost || ''),
// // Reasons and remarks
// variationReason: ensureString(product?.variation_reason_master_id || ''),
// otherVariationReason: ensureString(product?.other_variation_reason || ''),
// zeroTargetReason: ensureString(product?.zero_target_reason_master_id || ''),
// otherZeroTargetReason: ensureString(product?.other_zero_target_reason || ''),
// remarks: ensureString(product?.remarks || '')
// }));
};
const loadDetail = async () => {
@ -466,10 +560,29 @@ const Survey = () => {
const buildSubmissionPayload = React.useCallback((isResubmitFlow = false) => {
const info = establishmentData?.employeeInfo ?? {};
// Check for resubmission data in localStorage
let quarter = surveyData?.quarter || '';
let year = surveyData?.year || '';
// First try to get quarter and year from currentSurveyPeriod in localStorage
let quarter = '';
let year = '';
// Check for currentSurveyPeriod in localStorage
const currentSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
if (currentSurveyPeriod) {
try {
const { quarter: storedQuarter, year: storedYear } = JSON.parse(currentSurveyPeriod);
quarter = storedQuarter || '';
year = storedYear || '';
} catch (e) {
console.error('Error parsing currentSurveyPeriod:', e);
}
}
// Fall back to surveyData if currentSurveyPeriod is not available
if (!quarter || !year) {
quarter = surveyData?.quarter || '';
year = surveyData?.year || '';
}
// For resubmission, check resubmission data last (highest priority)
if (isResubmitFlow) {
const resubmitData = localStorage.getItem('resubmissionData');
if (resubmitData) {
@ -616,13 +729,14 @@ const Survey = () => {
const payload = buildSubmissionPayload();
const response = await submitSurvey(payload);
// Navigate to overview page after successful submission
// navigate('/overview');
// Show success message and popup
showToast('success', response?.message || 'You have successfully submitted the survey.');
setShowSuccessPopup(true);
setHasSubmitted(true);
// Clear the stored survey data after successful submission
localStorage.removeItem('currentSurvey');
// Remove currentSurveyPeriod from localStorage
localStorage.removeItem('currentSurveyPeriod');
} catch (error) {
console.error('Failed to submit survey', error);
const message = error?.response?.data?.message || error?.message || 'Failed to submit survey.';
@ -643,6 +757,16 @@ const Survey = () => {
id: submissionId // Include submission ID in the payload
};
// Remove currentSurveyPeriod from localStorage for resubmission
localStorage.removeItem('currentSurveyPeriod');
// Store resubmission data in localStorage
const resubmissionData = {
...payload,
resubmittedAt: new Date().toISOString()
};
localStorage.setItem('resubmissionData', JSON.stringify(resubmissionData));
// Use the resubmitSurvey API with the submission ID and payload
await resubmitSurvey(submissionId, payload);
@ -651,7 +775,7 @@ const Survey = () => {
showToast('success', 'Survey resubmitted successfully!');
localStorage.removeItem('surveyRemarks');
// Clear the stored survey data after successful resubmission
localStorage.removeItem('currentSurvey');
localStorage.removeItem('currentSurvey');
} catch (error) {
console.error('Resubmission failed:', error);
const message = error?.response?.data?.message || error?.message || 'Failed to resubmit survey. Please try again.';
@ -679,7 +803,9 @@ const Survey = () => {
showToast('success', 'Survey submitted successfully!');
localStorage.removeItem('surveyRemarks');
// Clear the stored survey data after successful submission
localStorage.removeItem('currentSurvey');
localStorage.removeItem('currentSurvey');
// Remove currentSurveyPeriod from localStorage
localStorage.removeItem('currentSurveyPeriod');
} catch (error) {
const message = error?.response?.data?.message || error?.message || 'Failed to submit survey. Please try again.';
console.error('Submission Error:', error);
@ -939,7 +1065,20 @@ const Survey = () => {
{/* Message */}
<p className="text-sm text-gray-700">
Thank you. Your IP Quarterly Survey for <span className="font-semibold text-gray-900">
{surveyData?.quarter} {surveyData?.year}
{(() => {
// Try to get quarter and year from currentSurveyPeriod in localStorage first
try {
const currentSurveyPeriod = localStorage.getItem('currentSurveyPeriod');
if (currentSurveyPeriod) {
const { quarter, year } = JSON.parse(currentSurveyPeriod);
if (quarter && year) return `${quarter} ${year}`;
}
} catch (e) {
console.error('Error parsing currentSurveyPeriod:', e);
}
// Fall back to surveyData if currentSurveyPeriod is not available
return `${surveyData?.quarter || ''} ${surveyData?.year || ''}`.trim();
})()}
</span> has been {isResubmit ? 'resubmitted' : 'submitted'}.
</p>