fcsc bug fixed
This commit is contained in:
parent
ac4e9a98c1
commit
fde8142acb
@ -86,14 +86,15 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
hour12: false
|
||||
};
|
||||
|
||||
// Format the date in Dubai time
|
||||
let formatted = new Intl.DateTimeFormat('en-GB', options).format(date);
|
||||
// Format the date in Dubai time with 24-hour format
|
||||
const formatter = new Intl.DateTimeFormat('en-GB', options);
|
||||
let formatted = formatter.format(date);
|
||||
|
||||
// Convert to uppercase AM/PM and ensure consistent formatting
|
||||
formatted = formatted.replace(/\s?(am|pm)$/i, (match) => match.toUpperCase());
|
||||
// Remove any AM/PM indicator since we're using 24-hour format
|
||||
formatted = formatted.replace(/\s*[AP]M$/, '').trim();
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
@ -410,7 +410,7 @@ const ProductDetails = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="block text-sm font-medium text-[#6B7280]">Status</label>
|
||||
<label className="block text-sm font-medium text-[#6B7280]">Submission Status</label>
|
||||
<div className="mt-1">
|
||||
{(() => {
|
||||
const status = String(submissionStatus).toLowerCase();
|
||||
|
||||
@ -328,6 +328,7 @@ const ProductData = ({
|
||||
const [showOtherVariationReason, setShowOtherVariationReason] = React.useState({});
|
||||
const [showOtherZeroTargetReason, setShowOtherZeroTargetReason] = React.useState({});
|
||||
const [formErrors, setFormErrors] = React.useState({});
|
||||
const [selectedProductIds, setSelectedProductIds] = React.useState([]);
|
||||
const [remarks, setRemarks] = React.useState(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('surveyRemarks') || '';
|
||||
@ -554,52 +555,68 @@ const location = useLocation();
|
||||
console.log("surveyDatatest", surveyData);
|
||||
|
||||
const requiredMessage = 'Required';
|
||||
const validateForm = () => {
|
||||
const errors = {};
|
||||
let isValid = true;
|
||||
const getAvailableProducts = (currentProductId) => {
|
||||
// Get the current product's selected value
|
||||
const currentProduct = products.find(p => p.id === currentProductId);
|
||||
const currentProductValue = currentProduct?.product || '';
|
||||
|
||||
// Get all selected product IDs except the current product's ID
|
||||
const otherSelectedProductIds = products
|
||||
.filter(p => p.id !== currentProductId && p.product)
|
||||
.map(p => p.product);
|
||||
|
||||
// Filter out selected products, but include the current product's selection
|
||||
return productOptions.filter(option =>
|
||||
!otherSelectedProductIds.includes(option.value) ||
|
||||
option.value === currentProductValue
|
||||
);
|
||||
};
|
||||
|
||||
const validateProduct = (product, index) => {
|
||||
const errors = {};
|
||||
let hasError = false;
|
||||
|
||||
// Check for duplicate products
|
||||
const duplicateProductIndex = products.findIndex(
|
||||
(p, i) => p.product === product.product && i !== index
|
||||
);
|
||||
|
||||
if (duplicateProductIndex !== -1) {
|
||||
errors[`product-${product.id}`] = 'This product has already been selected';
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
// Validate product selection
|
||||
if (!product.product) {
|
||||
errors[`product-${product.id}`] = 'Product is required';
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
// Validate unit selection
|
||||
if (!product.unit) {
|
||||
errors[`unit-${product.id}`] = 'Unit is required';
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
// Validate capacity
|
||||
if (!product.capacity) {
|
||||
errors[`capacity-${product.id}`] = 'Capacity is required';
|
||||
hasError = true;
|
||||
}
|
||||
|
||||
return { errors, hasError };
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
let isValid = true;
|
||||
const errors = {};
|
||||
|
||||
products.forEach((product, index) => {
|
||||
// Product validation
|
||||
if (!product.product) {
|
||||
// errors[`product_${index}`] = 'Product (HS Code - Name) is required';
|
||||
errors[`product_${index}`] = requiredMessage;
|
||||
const { errors: productErrors, hasError } = validateProduct(product, index);
|
||||
if (hasError) {
|
||||
Object.assign(errors, productErrors);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Unit validation
|
||||
if (!product.unit) {
|
||||
errors[`unit_${index}`] = requiredMessage;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Capacity validation
|
||||
if (!product.capacity) {
|
||||
errors[`capacity_${index}`] = requiredMessage;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Monthly quantity validations
|
||||
const requiredFields = [
|
||||
{ key: 'janQuantity', label: 'Jan Qty' },
|
||||
{ key: 'febQuantity', label: 'Feb Qty' },
|
||||
{ key: 'marQuantity', label: 'Mar Qty' },
|
||||
{ key: 'aprQuantity', label: 'Apr Qty' },
|
||||
{ key: 'mayQuantity', label: 'May Qty' },
|
||||
{ key: 'junQuantity', label: 'Jun Qty' },
|
||||
{ key: 'janCost', label: 'Jan Cost' },
|
||||
{ key: 'febCost', label: 'Feb Cost' },
|
||||
{ key: 'marCost', label: 'Mar Cost' },
|
||||
{ key: 'aprCost', label: 'Apr Cost' },
|
||||
{ key: 'mayCost', label: 'May Cost' },
|
||||
{ key: 'junCost', label: 'Jun Cost' }
|
||||
];
|
||||
|
||||
requiredFields.forEach(field => {
|
||||
if (product[field.key] === undefined || product[field.key] === '') {
|
||||
errors[`${field.key}_${index}`] = requiredMessage;
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setFormErrors(errors);
|
||||
@ -636,7 +653,13 @@ const location = useLocation();
|
||||
|
||||
const removeProduct = (id) => {
|
||||
if (products.length === 1) return;
|
||||
const productToRemove = products.find(p => p.id === id);
|
||||
onProductsChange(products.filter((p) => p.id !== id));
|
||||
|
||||
// Remove the product ID from selectedProductIds if it exists
|
||||
if (productToRemove?.product) {
|
||||
setSelectedProductIds(prev => prev.filter(pid => pid !== productToRemove.product));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPreviousForecastData = async (productId, establishmentId) => {
|
||||
@ -698,7 +721,43 @@ const location = useLocation();
|
||||
};
|
||||
|
||||
const updateProductField = (id, field, value, options = null) => {
|
||||
const updatedProducts = products.map(product => {
|
||||
// Create a copy of the current products array to work with
|
||||
let updatedProducts = [...products];
|
||||
|
||||
// If this is a product selection change, validate for duplicates
|
||||
if (field === 'product' && value) {
|
||||
// Check if this product is already selected in another row
|
||||
const isDuplicate = products.some(
|
||||
(p, idx) => p.product === value && p.id !== id
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
// Set error for this field
|
||||
setFormErrors(prev => ({
|
||||
...prev,
|
||||
[`product-${id}`]: 'This product has already been selected'
|
||||
}));
|
||||
return; // Don't update the product if it's a duplicate
|
||||
}
|
||||
|
||||
// Clear any existing error for this field
|
||||
const newErrors = { ...formErrors };
|
||||
delete newErrors[`product-${id}`];
|
||||
setFormErrors(newErrors);
|
||||
|
||||
// Update selected product IDs
|
||||
const currentProduct = products.find(p => p.id === id);
|
||||
if (currentProduct) {
|
||||
// Remove the old product ID if it exists
|
||||
setSelectedProductIds(prev =>
|
||||
prev.filter(pid => pid !== currentProduct.product)
|
||||
);
|
||||
}
|
||||
// Add the new product ID
|
||||
setSelectedProductIds(prev => [...prev, value]);
|
||||
}
|
||||
|
||||
updatedProducts = products.map(product => {
|
||||
if (product.id === id) {
|
||||
// Check if 'Others' is selected for variation reason
|
||||
if (field === 'variationReason' || field === 'otherVariationReason') {
|
||||
@ -1118,7 +1177,7 @@ const handleProductSelect = async (productId, establishmentId, id) => {
|
||||
<div>
|
||||
<SearchableSelect
|
||||
placeholder={isLoadingProducts ? 'Loading products...' : 'Search product HS code...'}
|
||||
options={productOptions}
|
||||
options={getAvailableProducts(p.id)}
|
||||
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) => {
|
||||
|
||||
@ -340,6 +340,7 @@ const ReviewSubmit = ({
|
||||
hasSubmitted = false,
|
||||
submitButtonText = 'Submit',
|
||||
quarter = 'Q1',
|
||||
// remarks = '',
|
||||
year = new Date().getFullYear()
|
||||
}) => {
|
||||
const [quarterPeriods, setQuarterPeriods] = useState(null);
|
||||
|
||||
@ -187,6 +187,7 @@ const CompanyProfile = () => {
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const [saveWarning, setSaveWarning] = React.useState(false);
|
||||
const [saveError, setSaveError] = React.useState('');
|
||||
const [productError, setProductError] = React.useState('');
|
||||
const [toast, setToast] = React.useState(null);
|
||||
const [emirateOptions, setEmirateOptions] = React.useState([]);
|
||||
const [emirateLookup, setEmirateLookup] = React.useState({});
|
||||
@ -890,7 +891,12 @@ const CompanyProfile = () => {
|
||||
case 3:
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4>
|
||||
{productError && (
|
||||
<div className="p-3 mb-4 text-sm text-red-700 bg-red-100 rounded-md">
|
||||
{productError}
|
||||
</div>
|
||||
)}
|
||||
<h4 className="text-[16px] font-semibold text-[#232528]">Products <span className="text-red-500">*</span></h4>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Available Products Panel */}
|
||||
<div className="bg-white rounded-lg border border-[#E5E7EB] overflow-hidden">
|
||||
@ -1133,6 +1139,9 @@ const loadProducts = async () => {
|
||||
};
|
||||
|
||||
const handleAddProduct = (product) => {
|
||||
// Clear any previous product errors
|
||||
setProductError('');
|
||||
setSaveError('');
|
||||
setSelectedProducts(prev => {
|
||||
const updated = [...prev, product];
|
||||
return updated;
|
||||
@ -1146,6 +1155,9 @@ const loadProducts = async () => {
|
||||
};
|
||||
|
||||
const handleRemoveProduct = (product) => {
|
||||
// Clear any previous product errors
|
||||
setProductError('');
|
||||
setSaveError('');
|
||||
setSelectedProducts(prev => {
|
||||
const updated = prev.filter(p => p.id !== product.id);
|
||||
return updated;
|
||||
@ -1171,6 +1183,13 @@ const loadProducts = async () => {
|
||||
};
|
||||
|
||||
const handleNextStep = React.useCallback(() => {
|
||||
if (activeStep === 3 && selectedProducts.length === 0) {
|
||||
// Show error if on products step and no products are selected
|
||||
setProductError('Please select at least one product');
|
||||
showToast('error', 'Please select at least one product');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateStepFields(activeStep)) {
|
||||
return;
|
||||
}
|
||||
@ -2856,13 +2875,24 @@ const requiredFields = [
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)]"
|
||||
onClick={handleNextStep}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
{activeStep > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white"
|
||||
onClick={() => setActiveStep(prev => Math.max(0, prev - 1))}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)]"
|
||||
onClick={handleNextStep}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -474,7 +474,7 @@ const UnitMaster = () => {
|
||||
|
||||
const downloadSampleCSV = () => {
|
||||
const sampleData = [
|
||||
['uom', 'description'],
|
||||
['Unit Name', 'Description'],
|
||||
['Kilogram', 'Weight measurement in kilograms'],
|
||||
['Gram', 'Weight measurement in grams'],
|
||||
['Liter', 'Volume measurement in liters'],
|
||||
@ -720,7 +720,7 @@ const UnitMaster = () => {
|
||||
>
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[#F1F2F4]">
|
||||
<h3 className="text-[18px] font-medium text-[#232528]">
|
||||
View Unit
|
||||
View Unit Master
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
@ -750,7 +750,7 @@ const UnitMaster = () => {
|
||||
Description <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="mt-1 px-3 py-2 border border-[#92722A] rounded-md bg-gray-50 min-h-[42px]">
|
||||
<p className="text-gray-900">{selectedUnit.uom_short_name || selectedUnit.description || 'N/A'}</p>
|
||||
<p className="text-gray-900">{selectedUnit.description || 'N/A'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect,useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { submitSurvey, fetchSubmissionDetail, resubmitSurvey } from '@/services/submissions/submissionService';
|
||||
import { fetchEstablishmentDetail, fetchEstablishmentDashboard } from '@/services/establishments/establishmentService';
|
||||
@ -66,6 +66,7 @@ const Survey = () => {
|
||||
const [toast, setToast] = React.useState(null);
|
||||
const [error, setError] = React.useState('');
|
||||
const [submissionDetail, setSubmissionDetail] = React.useState(null);
|
||||
const [remarks, setRemarks] = useState('');
|
||||
const [viewLoading, setViewLoading] = React.useState(false);
|
||||
const [viewError, setViewError] = React.useState('');
|
||||
const [establishmentLoading, setEstablishmentLoading] = React.useState(false);
|
||||
@ -606,6 +607,7 @@ const Survey = () => {
|
||||
non_emirati_female: parseNumber(info.nonEmiratiFemale),
|
||||
total_emirati: parseNumber(info.totalEmirati),
|
||||
total_employees: parseNumber(info.totalEmployees),
|
||||
remarks: remarks, // Add remarks to the payload
|
||||
products: productsData.map((product) => {
|
||||
const variationId = parseNumber(product.variationReason, null);
|
||||
const zeroTargetId = parseNumber(product.zeroTargetReason, null);
|
||||
@ -683,7 +685,7 @@ const Survey = () => {
|
||||
};
|
||||
}),
|
||||
};
|
||||
}, [establishmentData, parseNumber, productsData, stringify]);
|
||||
}, [establishmentData, parseNumber, productsData,remarks, stringify]);
|
||||
|
||||
const handleNext = () => {
|
||||
if (step < 3) {
|
||||
@ -1011,6 +1013,8 @@ const Survey = () => {
|
||||
error={error}
|
||||
quarter={establishmentData.quarter}
|
||||
year={establishmentData.year}
|
||||
remarks={remarks}
|
||||
onRemarksChange={setRemarks}
|
||||
/>
|
||||
)}
|
||||
{step === 3 && (
|
||||
@ -1030,6 +1034,7 @@ const Survey = () => {
|
||||
}}
|
||||
isSubmitting={isSubmitting}
|
||||
hasSubmitted={false}
|
||||
remarks={remarks}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user