CR Bug fixed

This commit is contained in:
Malini 2026-03-02 17:40:16 +05:30
parent 465cc8f9b0
commit 2e1bd35f24
5 changed files with 462 additions and 126 deletions

View File

@ -313,6 +313,7 @@ export const SelectField = ({
error = '',
readOnly = false,
required = false,
forceDropdownBelow = false,
}) => {
const wrapperStyle = {};
if (width !== 'auto') {
@ -321,20 +322,84 @@ export const SelectField = ({
const baseStyle = baseInputStyles[variant] || baseInputStyles.default;
const inputStyle = { ...baseStyle, ...style };
const [focused, setFocused] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState('');
const dropdownRef = React.useRef(null);
const inputRef = React.useRef(null);
const normalizedClear = clearValue ?? '';
const hasValue = value !== undefined && value !== null && value !== '' && value !== normalizedClear;
const showClear = allowClear && hasValue && !readOnly;
if (rightIcon) {
inputStyle.paddingRight = '40px';
}
// Filter options based on search term
const filteredOptions = React.useMemo(() => {
if (!searchTerm) return options;
return options.filter(opt =>
opt.label.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [options, searchTerm]);
// Close dropdown when clicking outside
React.useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
setSearchTerm('');
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
const handleFocus = (event) => {
setFocused(true);
onFocus?.(event);
};
const handleBlur = (event) => {
setFocused(false);
onBlur?.(event);
};
const handleClear = (event) => {
event.preventDefault();
event.stopPropagation();
if (onClear) {
onClear();
} else if (onChange) {
onChange({ target: { value: normalizedClear } });
}
};
const handleSelect = (selectedValue) => {
if (!readOnly) {
onChange({ target: { value: selectedValue } });
setIsOpen(false);
setSearchTerm('');
}
};
const toggleDropdown = () => {
if (!readOnly) {
setIsOpen(!isOpen);
setSearchTerm('');
}
};
const getDisplayValue = () => {
const selected = options.find(opt => opt.value === value);
return selected ? selected.label : (placeholder || '');
};
const hasError = Boolean(error);
inputStyle.borderColor = hasError
? '#B91C1C'
@ -353,15 +418,98 @@ export const SelectField = ({
inputStyle.backgroundColor = '#F9FAFB';
inputStyle.cursor = 'not-allowed';
}
const handleClear = (event) => {
event.preventDefault();
event.stopPropagation();
if (onClear) {
onClear();
} else if (onChange) {
onChange({ target: { value: normalizedClear } });
}
};
// Custom dropdown implementation
if (forceDropdownBelow) {
return (
<div className={`w-full ${wrapperStyle ? '' : ''}`} style={wrapperStyle || {}} ref={dropdownRef}>
{label && (
<label htmlFor={id || name} className="block text-sm font-medium text-gray-900 mb-1">
{label}
{required && <span className="text-red-700 ml-1">*</span>}
</label>
)}
<div className="relative w-full">
<div
className={`w-full text-sm focus:outline-none ${className}`}
style={{
...inputStyle,
cursor: readOnly ? 'not-allowed' : 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
minHeight: '40px',
padding: '0 16px'
}}
onClick={toggleDropdown}
onFocus={handleFocus}
onBlur={handleBlur}
tabIndex={readOnly ? -1 : 0}
>
<span style={{ color: hasValue ? '#232528' : '#9CA3AF' }}>
{getDisplayValue()}
</span>
{showClear ? (
<button
type="button"
className="h-5 w-5 rounded-full text-[#9CA3AF] hover:text-[#4B5563]"
onClick={handleClear}
aria-label="Clear selection"
>
×
</button>
) : (
rightIcon && <img src={rightIcon} alt="" className="h-4 w-4" />
)}
</div>
{isOpen && !readOnly && (
<div
className="absolute z-50 w-full mt-1 bg-white border border-[#CBA344] rounded-md shadow-lg max-h-60 overflow-auto"
style={{
top: '100%',
left: 0,
right: 0
}}
>
{/* Search input */}
<div className="p-2 border-b border-gray-200">
<input
type="text"
placeholder="Search..."
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[#92722A] focus:border-transparent"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onClick={(e) => e.stopPropagation()}
/>
</div>
{/* Options list */}
{filteredOptions.length === 0 ? (
<div className="px-3 py-2 text-gray-500 text-sm">No options found</div>
) : (
filteredOptions.map((opt) => (
<div
key={opt.value}
className={`px-3 py-2 cursor-pointer text-sm hover:bg-[#F9FAFB] ${
opt.value === value ? 'bg-[#E6D7A2] text-[#232528]' : 'text-[#232528]'
}`}
onClick={() => handleSelect(opt.value)}
>
{opt.label}
</div>
))
)}
</div>
)}
</div>
{readOnly && <div className="absolute inset-0 pointer-events-none" />}
{error && <p className="mt-1 text-xs text-[#B91C1C]">{error}</p>}
</div>
);
}
// Original native select implementation
return (
<div className={`w-full ${wrapperStyle ? '' : ''}`} style={wrapperStyle || {}}>
{label && (

View File

@ -7,6 +7,7 @@ const getSafeEstablishmentId = () => {
};
import { useNavigate, useLocation } from 'react-router-dom';
import { getEstablishmentProducts } from '../../../services/submissions/submissionService';
import { fetchEstablishmentDetail } from '../../../services/establishments/establishmentService';
import { getEmirates } from '../../../services/masters/masterService';
import Table from '@/components/common/Table';
@ -154,11 +155,11 @@ const EstablishmentInfo = ({
setLoadingProducts(true);
try {
const response = await getEstablishmentProducts(establishmentId);
if (response.status === 'success') {
setProducts(response.data || []);
const response = await fetchEstablishmentDetail(establishmentId);
if (response?.data?.establishment_products) {
setProducts(response.data.establishment_products || []);
} else {
setProductsError(response.message || 'Failed to load products');
setProductsError('Failed to load products');
}
} catch (error) {
console.error('Error fetching products:', error);
@ -652,7 +653,7 @@ React.useEffect(() => {
</Card>
<Card className="mt-10">
<h3 className="text-sm font-semibold text-gray-900 mb-4">Products</h3>
<h3 className="text-sm font-semibold text-gray-900 mb-4">Productss</h3>
{loadingProducts ? (
<div className="flex justify-center py-4">
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-[#92722A]" />
@ -663,9 +664,9 @@ React.useEffect(() => {
<Table
headers={['Product Name', 'HS Code', 'Description']}
rows={products.map(product => [
product['product.product_name'] || 'N/A',
product['product.hs_code'] || 'N/A',
product['product.hs_description'] || 'N/A'
product.product.product_name || '-',
product.product.hs_code || '-',
product.product.hs_description || '-'
])}
columnWidths={['33.33%', '33.33%', '33.33%']}
separated={true}

View File

@ -91,7 +91,8 @@ const SearchableSelect = ({
disabled = false,
error = false,
className = '',
displayValue = undefined
displayValue = undefined,
allowNull = false // New prop to allow null values for no selection
}) => {
const [isOpen, setIsOpen] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState('');
@ -153,22 +154,25 @@ const SearchableSelect = ({
// Get selected option label
const [internalDisplayValue, setInternalDisplayValue] = React.useState(displayValue || '');
const selectedOption = options.find(opt => opt.value === value);
const selectedOption = allowNull ? (value === null ? null : options.find(opt => opt.value === value)) : options.find(opt => opt.value === value);
// Update internal display value when value, displayValue prop or options change
React.useEffect(() => {
if (displayValue !== undefined && displayValue !== '') {
setInternalDisplayValue(displayValue);
} else if (allowNull && value === null) {
// When allowNull is true and value is null, show empty (no selection)
setInternalDisplayValue('');
} else if (selectedOption) {
setInternalDisplayValue(selectedOption.label);
} else if (value) {
} else if (value !== null && value !== undefined && value !== '') {
// If we have a value but no matching option, try to find it in the options
const matchedOption = options.find(opt => opt.value === value);
setInternalDisplayValue(matchedOption?.label || '');
} else {
setInternalDisplayValue('');
}
}, [value, displayValue, selectedOption, options]);
}, [value, displayValue, selectedOption, options, allowNull]);
const displayValueToShow = internalDisplayValue;
@ -197,7 +201,7 @@ const SearchableSelect = ({
if (error) {
borderColor = 'border-red-500';
bgColor = 'bg-red-50';
} else if (value || isOpen) {
} else if ((value !== null && value !== undefined && value !== '') || isOpen) {
borderColor = 'border-[#92722A]';
}
@ -241,7 +245,7 @@ const SearchableSelect = ({
<div
key={option.value}
className={`px-4 py-2 text-sm cursor-pointer hover:bg-gray-100 ${
value === option.value ? 'bg-[#F5F0E1]' : ''
(allowNull ? value === option.value : value === option.value) ? 'bg-[#F5F0E1]' : ''
}`}
onClick={() => handleSelect(option)}
>
@ -267,7 +271,9 @@ const Select = ({
loading = false,
disabled = false,
error = false,
className = ''
className = '',
allowClear = false,
onClear
}) => {
const [isFocused, setIsFocused] = React.useState(false);
@ -281,6 +287,19 @@ const Select = ({
} else if (value || isFocused) {
borderColor = 'border-[#92722A]';
}
const hasValue = value !== undefined && value !== null && value !== '';
const showClear = allowClear && hasValue && !disabled && !loading;
const handleClear = (event) => {
event.preventDefault();
event.stopPropagation();
if (onClear) {
onClear();
} else if (onChange) {
onChange({ target: { value: '' } });
}
};
return (
<div className="relative">
@ -317,8 +336,17 @@ const Select = ({
})
)}
</select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 flex items-center">
{loading ? (
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center">
{showClear ? (
<button
type="button"
className="h-5 w-5 rounded-full text-[#9CA3AF] hover:text-[#4B5563] flex items-center justify-center"
onClick={handleClear}
aria-label="Clear selection"
>
×
</button>
) : loading ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-[#92722A] border-t-transparent" />
) : (
<img src={caretDownSrc} alt="open" className="h-4 w-4" />
@ -418,6 +446,11 @@ const ProductData = ({
const [unitsError, setUnitsError] = React.useState(null);
const [isSavingDraft, setIsSavingDraft] = React.useState(false);
const latestDraftRef = React.useRef({ products: [], deletedProducts: [], remarks: '' });
const draftKeyRef = React.useRef('');
const restoredDraftKeyRef = React.useRef('');
const restoredDraftSignatureRef = React.useRef('');
const location = useLocation(); // Add this line
@ -441,6 +474,57 @@ const ProductData = ({
return '';
});
const getLocalDraftKey = React.useCallback(() => {
if (typeof window === 'undefined') return '';
const currentSubmissionStr = localStorage.getItem('currentSubmission');
let currentSubmission = null;
try {
currentSubmission = currentSubmissionStr ? JSON.parse(currentSubmissionStr) : null;
} catch (e) {
currentSubmission = null;
}
const establishmentId =
currentSubmission?.establishment_id ||
currentSubmission?.establishment?.id ||
localStorage.getItem('establishment_id') ||
localStorage.getItem('establishmentId') ||
'';
const effectiveQuarter = quarter || localStorage.getItem('currentQuarter') || localStorage.getItem('current_quarter') || '';
const effectiveYear = year || localStorage.getItem('currentYear') || localStorage.getItem('current_year') || '';
if (!establishmentId || !effectiveQuarter || !effectiveYear) return '';
const formattedQuarter = String(effectiveQuarter).startsWith('Q')
? String(effectiveQuarter)
: `Q${effectiveQuarter}`;
return `productDataDraft:${establishmentId}:${effectiveYear}:${formattedQuarter}`;
}, [quarter, year]);
const persistLocalDraft = React.useCallback((payload) => {
if (typeof window === 'undefined') return;
const key = draftKeyRef.current || getLocalDraftKey();
if (!key) return;
try {
localStorage.setItem(
key,
JSON.stringify({
...payload,
updatedAt: Date.now(),
})
);
} catch (e) {
// ignore localStorage errors
}
}, [getLocalDraftKey]);
const flushLocalDraft = React.useCallback(() => {
persistLocalDraft(latestDraftRef.current);
}, [persistLocalDraft]);
const handleRemarksChange = (e) => {
const value = e.target.value;
setRemarks(value);
@ -449,6 +533,109 @@ const ProductData = ({
}
};
// Keep latest draft snapshot in a ref (used by beforeunload/offline handlers)
React.useEffect(() => {
latestDraftRef.current = {
products,
deletedProducts,
remarks,
};
}, [products, deletedProducts, remarks]);
// Initialize draft key and restore any existing draft for this establishment/period
React.useEffect(() => {
if (typeof window === 'undefined') return;
const key = getLocalDraftKey();
draftKeyRef.current = key;
if (!key) return;
try {
const raw = localStorage.getItem(key);
if (!raw) return;
const parsed = JSON.parse(raw);
if (!parsed) return;
const parsedSignature = JSON.stringify({
products: Array.isArray(parsed.products) ? parsed.products : [],
deletedProducts: Array.isArray(parsed.deletedProducts) ? parsed.deletedProducts : [],
remarks: typeof parsed.remarks === 'string' ? parsed.remarks : '',
});
// Restore only when the incoming props don't already have meaningful user-entered data
const hasExistingData = Array.isArray(products) && products.some((p) => {
if (!p) return false;
return Boolean(
p.product || p.productId || p.product_id ||
p.unit || p.unit_id ||
p.capacity ||
p.octQuantity || p.novQuantity || p.decQuantity ||
p.janQuantity || p.febQuantity || p.marQuantity ||
p.aprQuantity || p.mayQuantity || p.junQuantity ||
p.octCost || p.novCost || p.decCost ||
p.janCost || p.febCost || p.marCost ||
p.aprCost || p.mayCost || p.junCost ||
p.variationReason || p.zeroTargetReason || p.remarks
);
});
if (!hasExistingData && Array.isArray(parsed.products)) {
// If we've already restored this exact payload for this key, don't keep re-applying.
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;
}
} catch (e) {
// ignore parse errors
}
}, [getLocalDraftKey, onProductsChange, products]);
// Debounced autosave to localStorage whenever user changes anything
React.useEffect(() => {
if (typeof window === 'undefined') return;
const key = draftKeyRef.current || getLocalDraftKey();
if (!key) return;
const handle = setTimeout(() => {
persistLocalDraft({ products, deletedProducts, remarks });
}, 400);
return () => clearTimeout(handle);
}, [products, deletedProducts, remarks, getLocalDraftKey, persistLocalDraft]);
// Accidental exit handling: save on tab close, refresh, background, offline
React.useEffect(() => {
if (typeof window === 'undefined') return;
const onBeforeUnload = () => {
flushLocalDraft();
};
const onVisibilityChange = () => {
if (document.visibilityState === 'hidden') {
flushLocalDraft();
}
};
const onOffline = () => {
flushLocalDraft();
};
window.addEventListener('beforeunload', onBeforeUnload);
document.addEventListener('visibilitychange', onVisibilityChange);
window.addEventListener('offline', onOffline);
return () => {
window.removeEventListener('beforeunload', onBeforeUnload);
document.removeEventListener('visibilitychange', onVisibilityChange);
window.removeEventListener('offline', onOffline);
};
}, [flushLocalDraft]);
useEffect(() => {
const fetchQuarterPeriods = async () => {
try {
@ -564,9 +751,9 @@ useEffect(() => {
junCost: product.forecast_cost_period_three?.toString() || '0',
// Variation reasons
variationReason: product.variation_reason_master_id?.toString() || '0',
variationReason: product.variation_reason_master_id !== null && product.variation_reason_master_id !== undefined ? product.variation_reason_master_id.toString() : null,
otherVariationReason: product.other_variation_reason || '',
zeroTargetReason: product.zero_target_reason_master_id?.toString() || '',
zeroTargetReason: product.zero_target_reason_master_id !== null && product.zero_target_reason_master_id !== undefined ? product.zero_target_reason_master_id.toString() : null,
otherZeroTargetReason: product.other_zero_target_reason || '',
remarks: product.remarks || '',
@ -730,9 +917,9 @@ const existingProduct = currentSubmission.products?.find(p =>
// Next quarter forecast (Q2) - December
forecast_quantity: product.decQuantity || '0',
forecast_cost: product.decCost || '0',
variation_reason_master_id: product.variationReason || '0',
variation_reason_master_id: product.variationReason === null ? null : (product.variationReason || '0'),
other_variation_reason: product.otherVariationReason || '0',
zero_target_reason_master_id: product.zeroTargetReason || '0',
zero_target_reason_master_id: product.zeroTargetReason === null ? null : (product.zeroTargetReason || '0'),
other_zero_target_reason: product.otherZeroTargetReason || '0',
remarks: product.remarks || remarks || '' // Use product.remarks if available, otherwise use the component's remarks state
};
@ -775,9 +962,9 @@ const existingProduct = currentSubmission.products?.find(p =>
current_cost: product.novCost || '0',
forecast_quantity: product.decQuantity || '0',
forecast_cost: product.decCost || '0',
variation_reason_master_id: product.variationReason || '0',
variation_reason_master_id: product.variationReason === null ? null : (product.variationReason || '0'),
other_variation_reason: product.otherVariationReason || '0',
zero_target_reason_master_id: product.zeroTargetReason || '0',
zero_target_reason_master_id: product.zeroTargetReason === null ? null : (product.zeroTargetReason || '0'),
other_zero_target_reason: product.otherZeroTargetReason || '0',
remarks: product.remarks || remarks || '',
is_active: false // Mark as inactive
@ -893,9 +1080,9 @@ useEffect(() => {
junCost: product.forecast_cost_period_three || '0',
// Variation reasons
variationReason: product.variation_reason_master_id?.toString() || '',
variationReason: product.variation_reason_master_id !== null && product.variation_reason_master_id !== undefined ? product.variation_reason_master_id.toString() : null,
otherVariationReason: product.other_variation_reason || '',
zeroTargetReason: product.zero_target_reason_master_id?.toString() || '',
zeroTargetReason: product.zero_target_reason_master_id !== null && product.zero_target_reason_master_id !== undefined ? product.zero_target_reason_master_id.toString() : null,
otherZeroTargetReason: product.other_zero_target_reason || '',
remarks: product.remarks || ''
};
@ -1295,8 +1482,8 @@ useEffect(() => {
// Dynamic validation for variation reason based on cost variation
const mandatoryVariationReason = isVariationReasonMandatory(product);
if (mandatoryVariationReason) {
// Check if variation reason is selected
if (!product.variationReason || product.variationReason === '0' || product.variationReason === '') {
// Check if variation reason is selected (null means no selection)
if (product.variationReason === null || product.variationReason === undefined || product.variationReason === '') {
errors[`variationReason_${index}`] = 'Reason for variation is required when cost variation exceeds ±10%';
isValid = false;
}
@ -1718,8 +1905,8 @@ useEffect(() => {
const newErrors = { ...formErrors };
if (mandatoryVariationReason) {
// Check if variation reason is selected
if (!updatedProduct.variationReason || updatedProduct.variationReason === '0' || updatedProduct.variationReason === '') {
// Check if variation reason is selected (null means no selection)
if (updatedProduct.variationReason === null || updatedProduct.variationReason === undefined || updatedProduct.variationReason === '') {
newErrors[variationErrorKey] = 'Reason for variation is required when cost variation exceeds ±10%';
} else {
delete newErrors[variationErrorKey];
@ -1759,8 +1946,8 @@ useEffect(() => {
if (mandatoryVariationReason) {
if (field === 'variationReason') {
// Check if variation reason is selected
if (!updatedProduct.variationReason || updatedProduct.variationReason === '0' || updatedProduct.variationReason === '') {
// Check if variation reason is selected (null means no selection)
if (updatedProduct.variationReason === null || updatedProduct.variationReason === undefined || updatedProduct.variationReason === '') {
newErrors[variationErrorKey] = 'Reason for variation is required when cost variation exceeds ±10%';
} else {
delete newErrors[variationErrorKey];
@ -2566,12 +2753,14 @@ useEffect(() => {
Reason for Variation
{isVariationReasonMandatory(p) && <span className="text-red-600">*</span>}
</label>
<Select
<SearchableSelect
placeholder={isLoadingReasons ? 'Loading reasons...' : 'Select reason'}
options={variationReasons}
value={p.variationReason || ''}
value={p.variationReason === null || p.variationReason === '' ? null : p.variationReason}
onChange={(e) => updateProductField(p.id, 'variationReason', e.target.value, variationReasons)}
loading={isLoadingReasons}
error={!!formErrors[`variationReason_${idx}`]}
allowNull={true}
/>
{formErrors[`variationReason_${idx}`] && (
<p className="mt-1 text-xs text-red-600">{formErrors[`variationReason_${idx}`]}</p>
@ -2724,11 +2913,13 @@ useEffect(() => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Reason for 0 Target</label>
<Select
<SearchableSelect
placeholder={isLoadingZeroReasons ? 'Loading reasons...' : 'Select reason'}
options={zeroTargetReasons}
value={p.zeroTargetReason || ''}
value={p.zeroTargetReason === null || p.zeroTargetReason === '' ? null : p.zeroTargetReason}
onChange={(e) => updateProductField(p.id, 'zeroTargetReason', e.target.value, zeroTargetReasons)}
loading={isLoadingZeroReasons}
allowNull={true}
/>
{(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'))))) && (
<div className="mt-3 p-3 bg-gray-50 rounded-md border border-gray-200">

View File

@ -696,7 +696,7 @@ const CompanyProfile = () => {
{ field: 'permanentFactoryCode', label: 'Permanent Factory Code' },
{ field: 'uniqueLicenseNumber', label: 'Unique License Number' },
// { field: 'industryCodeBusiness', label: 'Industry Code (Business Register)' },
{ field: 'industryCodeProduction', label: 'Industry Code (Current Production)' },
{ field: 'industryCodeProduction', label: 'Principal activity Code (ISIC Rev 4 - 4 digits)' },
],
2: [
{ field: 'contactName', label: 'Name' },
@ -1009,7 +1009,7 @@ const CompanyProfile = () => {
<h4 className="text-[16px] font-semibold text-[#232528]">Details</h4>
{/* Responsive grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2 w-full overflow-x-hidden [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full overflow-x-hidden [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
<div>
<TextField
label="Permanent Factory Code"
@ -1044,10 +1044,11 @@ const CompanyProfile = () => {
readOnly={modalMode === 'view'}
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full">
<div>
<SelectField
label="Industry Code(Current Production)"
label="Principal activity Code (ISIC Rev 4 - 4 digits)"
value={form.industryCodeProduction}
onChange={handleFormChange('industryCodeProduction')}
placeholder="Select ISIC Code"
@ -1057,10 +1058,9 @@ const CompanyProfile = () => {
readOnly={modalMode === 'view'}
options={isicOptions}
searchable
forceDropdownBelow={true}
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 w-full">
<div>
<TextField
label="ERN"
@ -3784,7 +3784,7 @@ const handleImport = async () => {
<span></span><span>HS Code</span>
</div>
<div className="flex items-center gap-2">
<span></span><span>Industry Code (Current Production)</span>
<span></span><span>Principal activity Code (ISIC Rev 4 - 4 digits)</span>
</div>
</div>
</div>

View File

@ -122,6 +122,7 @@ const EditCompanyProfile = () => {
const [isLoadingProducts, setIsLoadingProducts] = useState(false);
const [productError, setProductError] = useState("");
const [productLimitMessage, setProductLimitMessage] = useState("");
const [isSearching, setIsSearching] = useState(false); // Flag to prevent validation during search
const [isLoadingCities, setIsLoadingCities] = useState(false);
const [visibleProductCount, setVisibleProductCount] = useState(14);
const [isLoadingMore, setIsLoadingMore] = useState(false);
@ -150,7 +151,7 @@ const EditCompanyProfile = () => {
// { field: 'permanentFactoryCode', label: 'Permanent Factory Code' },
{ field: 'uniqueLicenseNumber', label: 'Unique License Number' },
// { field: 'industryCodeBusiness', label: 'Industry Code (Business Register)' },
{ field: 'industryCodeCurrent', label: 'Industry Code (Current Production)' },
{ field: 'industryCodeCurrent', label: 'Principal activity Code (ISIC Rev 4 - 4 digits)' },
],
2: [
{ field: 'contactName', label: 'Name' },
@ -177,6 +178,11 @@ const EditCompanyProfile = () => {
// Special validation for Products step
if (stepIndex === 3) {
// Skip validation if search is in progress to prevent interference
if (isSearching) {
return true;
}
const checkedCount = selectedProducts.filter(p => checkedProducts.has(p.value || p.id || p.product_id)).length;
if (checkedCount === 0) {
setProductError("At least one product must be checked.");
@ -310,33 +316,60 @@ const EditCompanyProfile = () => {
const handleProductSearch = (e) => {
const searchTerm = e.target.value.toLowerCase();
setProductSearchTerm(searchTerm);
setIsSearching(true); // Set searching flag
// Preserve current checked products state before performing search
const currentCheckedProducts = new Set(checkedProducts);
if (!searchTerm) {
// If search is cleared, reload all products with selected ones filtered out
loadProducts();
setIsSearching(false); // Reset searching flag
return;
}
// Get all products (including those not currently visible)
const allProducts = [...availableProducts, ...selectedProducts];
// Get all products from the original full list (not the filtered availableProducts)
// We need to fetch all products again to ensure we have the complete list
const performSearch = async () => {
try {
const allProducts = await fetchProducts();
// Filter out selected products from the full list
const availableFromAll = allProducts.filter(product => {
return !selectedProducts.some(selected =>
(selected.value && (selected.value === product.value || selected.value === product.id || selected.value === product.product_id)) ||
(selected.id && (selected.id === product.id || selected.id === product.value || selected.id === product.product_id)) ||
(selected.product_id && (selected.product_id === product.product_id || selected.product_id === product.id || selected.product_id === product.value))
);
});
// Filter products based on search term
const filtered = availableFromAll.filter(product => {
const matchesSearch =
(product.label?.toLowerCase().includes(searchTerm) ||
product.hs_code?.toLowerCase().includes(searchTerm) ||
(product.product_name && product.product_name.toLowerCase().includes(searchTerm)));
return matchesSearch;
});
// Update availableProducts with search results (even if empty)
// This allows the "No data found" message to be displayed when search yields no results
setAvailableProducts(filtered);
// CRITICAL: Always preserve checked products state after search completes
// This prevents any accidental reset of checked state from any source
setTimeout(() => {
setCheckedProducts(currentCheckedProducts);
setIsSearching(false); // Reset searching flag after state is restored
}, 0);
} catch (error) {
console.error('Error searching products:', error);
setIsSearching(false); // Reset searching flag on error
}
};
// Filter products based on search term and exclude selected ones
const filtered = allProducts.filter(product => {
const matchesSearch =
(product.label?.toLowerCase().includes(searchTerm) ||
product.hs_code?.toLowerCase().includes(searchTerm) ||
(product.product_name && product.product_name.toLowerCase().includes(searchTerm)));
const isSelected = selectedProducts.some(selected =>
(selected.value && (selected.value === product.value || selected.value === product.id || selected.value === product.product_id)) ||
(selected.id && (selected.id === product.id || selected.id === product.value || selected.id === product.product_id)) ||
(selected.product_id && (selected.product_id === product.product_id || selected.product_id === product.id || selected.product_id === product.value))
);
return matchesSearch && !isSelected;
});
setAvailableProducts(filtered);
performSearch();
};
// Handle checkbox toggle for selected products
@ -401,8 +434,8 @@ const EditCompanyProfile = () => {
// Clear newly added product IDs
setNewlyAddedProductIds(new Set());
// Refresh component to update UI while staying on current step
navigate(window.location.pathname);
// Note: Removed navigation to prevent component remount and state reset
// The UI will update automatically through React state changes
}
}
}
@ -880,47 +913,10 @@ const EditCompanyProfile = () => {
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-[16px] font-semibold text-[#232528]">Details</h4>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* <TextField
label="Permanent Factory Code"
type="hidden"
value={form.permanentFactoryCode || ""}
onChange={(e) => handleFormChange("permanentFactoryCode", e.target.value)}
placeholder="Enter Factory Code"
width="100%"
// required
error={fieldErrors.permanentFactoryCode}
/> */}
{/* <TextField
label="Unique License Number"
value={form.uniqueLicenseNumber || ""}
onChange={(e) => handleFormChange("uniqueLicenseNumber", e.target.value)}
placeholder="Enter License Number"
width="100%"
required
error={fieldErrors.uniqueLicenseNumber}
/>
<TextField
label="ERN"
value={form.ERN || ""}
onChange={(e) => handleFormChange("ERN", e.target.value)}
placeholder="Enter ERN"
width="100%"
error={fieldErrors.ERN}
/>
<TextField
label="Industry Code (Business Register)"
value={form.industryCodeBusiness || ""}
onChange={(e) => handleFormChange("industryCodeBusiness", e.target.value)}
placeholder="Enter Code"
width="100%"
// required
error={fieldErrors.industryCodeBusiness}
/> */}
<div className="whitespace-nowrap">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-1">
<SelectField
label="Industry Code (Current Production)"
label="Principal activity Code (ISIC Rev 4 - 4 digits)"
name="industryCodeCurrent"
value={form.industryCodeCurrent || ''}
onChange={(e) => handleFormChange("industryCodeCurrent", e.target.value)}
@ -931,17 +927,16 @@ const EditCompanyProfile = () => {
error={fieldErrors.industryCodeCurrent}
/>
</div>
<div className="whitespace-nowrap">
<TextField
label="ERN"
value={form.ERN || ""}
onChange={(e) => handleFormChange("ERN", e.target.value)}
placeholder="Enter ERN"
width="100%"
error={fieldErrors.ERN}
readOnly
/>
<div className="md:col-span-1">
<TextField
label="ERN"
value={form.ERN || ""}
onChange={(e) => handleFormChange("ERN", e.target.value)}
placeholder="Enter ERN"
width="100%"
error={fieldErrors.ERN}
readOnly
/>
</div>
<div className="col-span-full">
{/* <TextField
@ -1138,11 +1133,12 @@ const EditCompanyProfile = () => {
)}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Available Products Panel - Only show if there are available products and not all selected products are checked */}
{/* Available Products Panel - Show if there are available products and not all selected products are checked, OR if there's a search term */}
{(() => {
// More robust condition that handles state inconsistencies
const hasAvailableProducts = availableProducts.length > 0;
const hasSelectedProducts = selectedProducts.length > 0;
const hasSearchTerm = productSearchTerm.length > 0;
// Count how many selected products are actually checked
let actuallyCheckedCount = 0;
@ -1154,7 +1150,7 @@ const EditCompanyProfile = () => {
}
const hasUncheckedProducts = actuallyCheckedCount < selectedProducts.length;
const shouldShow = hasAvailableProducts && hasSelectedProducts && hasUncheckedProducts;
const shouldShow = (hasAvailableProducts && hasSelectedProducts && hasUncheckedProducts) || hasSearchTerm;
return shouldShow;