fcsc bug fix

This commit is contained in:
Malini 2025-11-21 20:39:59 +05:30
parent f78534b258
commit 236743053e
13 changed files with 535 additions and 226 deletions

View File

@ -119,7 +119,7 @@ export const SurveyStatus = () => {
return {
title: `${quarter} ${year} Survey`,
subtitle: 'Complete your quarterly Industrial Production Index (IPI) data submission',
subtitle: 'Complete your quarterly Index of Industrial Production (IIP) data submission',
dueDate: formatDate(end_date),
relativeDue: formatRelativeDays(end_date),
estTime: '1520 minutes',

View File

@ -1,4 +1,8 @@
import React from 'react';
import React, { useEffect, useState, useMemo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { getEstablishmentProducts } from '../../../services/submissions/submissionService';
import Table from '@/components/common/Table';
const caretDownSrc = '/assets/images/CaretDown.svg';
const backVectorSrc = '/assets/images/BackVector.svg';
const mailIconSrc = '/assets/images/material-symbols_mail-outline.svg';
@ -6,7 +10,6 @@ const phoneIconSrc = '/assets/images/line-md_phone.svg';
const calendarIcon = '/assets/images/Duedate.svg';
const clockIcon = '/assets/images/mingcute_time-line.svg';
const nextIcon = '/assets/images/mdi_page-next-outline.svg';
import { useNavigate, useLocation } from 'react-router-dom';
const Field = ({ label, placeholder = '', type = 'text', value = '', onChange = () => {}, disabled = true }) => (
<div>
@ -95,11 +98,40 @@ const EstablishmentInfo = ({
isComplete = false,
}) => {
const [showInfoCard, setShowInfoCard] = React.useState(true);
const [products, setProducts] = React.useState([]);
const [loadingProducts, setLoadingProducts] = React.useState(false);
const [productsError, setProductsError] = React.useState('');
const navigate = useNavigate();
const location = useLocation();
// Use a ref to persist the survey data across re-renders
const surveyDataRef = React.useRef(null);
// Fetch establishment products
React.useEffect(() => {
const fetchProducts = async () => {
const establishmentId = sessionStorage.getItem('establishment_id');
if (!establishmentId) return;
setLoadingProducts(true);
try {
const response = await getEstablishmentProducts(establishmentId);
if (response.status === 'success') {
console.log('Products data:', response.data); // Log the products data
setProducts(response.data || []);
} else {
setProductsError(response.message || 'Failed to load products');
}
} catch (error) {
console.error('Error fetching products:', error);
setProductsError('Failed to load products. Please try again later.');
} finally {
setLoadingProducts(false);
}
};
fetchProducts();
}, []);
const [surveyData, setSurveyData] = React.useState(() => {
// Initialize from location state if available, otherwise use defaults
if (location.state?.survey) {
@ -280,7 +312,7 @@ const EstablishmentInfo = ({
</div>
)}
<h2 className="text-xl font-semibold text-[#232528] mb-2">
Industrial Production Index (IPI) Survey: {surveyData.quarter} {surveyData.year}
IIP (Index of Industrial Production): {surveyData.quarter} {surveyData.year}
</h2>
{/* Survey Information */}
@ -309,7 +341,7 @@ const EstablishmentInfo = ({
<div className="space-y-4">
<p className="text-sm text-[#6C4527] mb-4">
You're completing the <b>IPI Quarterly Survey</b> for <b>{surveyData.quarter} {surveyData.year}</b>. This step shows establishment details already registered with us. <b>Step 1 is read-only.</b> To make corrections, update them in <b>Profile Edit Profile</b>, then return to this survey.
You're completing the <b>IIP Quarterly Survey</b> for <b>{surveyData.quarter} {surveyData.year}</b>. This step shows establishment details already registered with us. <b>Step 1 is read-only.</b> To make corrections, update them in <b>Profile Edit Profile</b>, then return to this survey.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -365,54 +397,7 @@ const EstablishmentInfo = ({
</div>
</div>
)}
{/* Blue Info Box */}
<div className="bg-[#E8F3FF] border border-[#2F80ED] text-[#0B4DA2] px-4 py-2 rounded-md mt-3 flex items-center gap-2 text-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="#0B4DA2"
className="w-5 h-5 flex-shrink-0"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{/* <p>
<span className="font-medium">Need to update details?</span> Go to{' '}
<b>Profile Edit Profile</b>. Changes saved there will appear{' '}
<a href="#" className="underline font-medium text-[#0B4DA2] hover:text-[#063B82]">
here
</a>.
</p> */}
{/* <p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<span className="font-medium">Need to update details?</span> Go to Profile {' '}
<a
href={`/admin/configuration/profile/edit=${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
. Changes saved there will appear here.
</p> */}
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<span className="font-medium">Need to update details?</span> Go to Profile {' '}
<a
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
. Changes saved there will appear here.
</p>
</div>
<div className="flex items-center gap-2 mb-2">
<h2 className="text-lg font-semibold text-[#232528]">Step 1: Review Establishment Information</h2>
{isComplete && (
@ -532,6 +517,31 @@ const EstablishmentInfo = ({
</div>
</Card>
<Card className="mt-10">
<h3 className="text-sm font-semibold text-gray-900 mb-4">Products</h3>
{loadingProducts ? (
<div className="flex justify-center py-4">
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-[#92722A]" />
</div>
) : productsError ? (
<div className="text-red-600 text-sm py-2">{productsError}</div>
) : products.length > 0 ? (
<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'
])}
columnWidths={['33.33%', '33.33%', '33.33%']}
separated={true}
className="w-full"
/>
) : (
<div className="text-gray-500 text-sm py-2">No products found for this establishment.</div>
)}
</Card>
<div className="mt-6">
{/* Employee Information */}
<Card>
@ -588,6 +598,109 @@ const EstablishmentInfo = ({
</div>
</Card>
{/* <Card className="mt-10">
<h3 className="text-sm font-semibold text-gray-900 mb-4">Products</h3>
{loadingProducts ? (
<div className="flex justify-center py-4">
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-[#92722A]" />
</div>
) : productsError ? (
<div className="text-red-600 text-sm py-2">{productsError}</div>
) : products.length > 0 ? (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Product Name
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
HS Code
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Unit
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
UOM
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{products.map((product, index) => (
<tr key={product.id || index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{product['product.product_name'] || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{product['product.hs_code'] || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{product['product.unit.uom_short_name'] || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{product['product.unit.uom'] || 'N/A'}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="text-gray-500 text-sm py-2">No products found for this establishment.</div>
)}
</Card> */}
{/* Products Table */}
{/* Blue Info Box */}
<div className="bg-[#E8F3FF] border border-[#2F80ED] text-[#0B4DA2] px-4 py-2 rounded-md mt-3 flex items-center gap-2 text-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="#0B4DA2"
className="w-5 h-5 flex-shrink-0"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{/* <p>
<span className="font-medium">Need to update details?</span> Go to{' '}
<b>Profile Edit Profile</b>. Changes saved there will appear{' '}
<a href="#" className="underline font-medium text-[#0B4DA2] hover:text-[#063B82]">
here
</a>.
</p> */}
{/* <p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<span className="font-medium">Need to update details?</span> Go to Profile {' '}
<a
href={`/admin/configuration/profile/edit=${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
. Changes saved there will appear here.
</p> */}
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
<span className="font-medium">Need to update details?</span> Go to Profile {' '}
<a
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
onClick={handleEditProfile}
>
Edit Profile
</a>
. Changes saved there will appear here.
</p>
</div>
{/* Footer actions */}
<div className="mt-8 flex flex-col sm:flex-row justify-between gap-3">
<button

View File

@ -1108,7 +1108,7 @@ const location = useLocation();
<div className="text-[#6C4527] space-y-2">
<h3 className="text-base font-semibold mb-2">Why we collect monthly product data</h3>
<p className="text-sm mb-4">
This step collects <strong>monthly output quantity and production cost</strong> for each product you manufacture or process. The data inform the <strong>Industrial Production Index (IPI)</strong> and capacity-utilization statistics. Results are published <strong>only in aggregated form</strong> and your information is protected by national statistics confidentiality provisions.
This step collects <strong>monthly output quantity and production cost</strong> for each product you manufacture or process. The data inform the <strong>Index of Industrial Production (IIP)</strong> and capacity-utilization statistics. Results are published <strong>only in aggregated form</strong> and your information is protected by national statistics confidentiality provisions.
</p>
<div className="flex gap-2 text-sm">

View File

@ -70,7 +70,7 @@ const AdminUsers = () => {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
// const pageSize = 10;
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
@ -92,7 +92,8 @@ const AdminUsers = () => {
const [confirmPassword, setConfirmPassword] = useState('');
const [resettingPassword, setResettingPassword] = useState(false);
const [resetErrors, setResetErrors] = useState({});
const [pageSize, setPageSize] = React.useState(10);
// Helper: show toast
const showToast = (message, type = 'success') => {
setToastData({ message, type });
@ -655,6 +656,10 @@ const AdminUsers = () => {
pageSize,
totalItems: users.length,
pageSizeOptions: [10, 20, 50, 100],
onPageSizeChange: (size) => {
setPageSize(size);
setCurrentPage(1);
}
}}
renderCell={(value, rowIndex, colIndex) => {
if (colIndex === 4) {

View File

@ -19,6 +19,7 @@ const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
const viewIconSrc = '/assets/images/Eye.svg';
const pencilInactiveSrc = '/assets/images/pencilsimple-inactive.svg';
const trashActiveSrc = '/assets/images/Trash-active.svg';
const trashInactiveSrc = '/assets/images/Trash-Inactive.svg';
@ -323,13 +324,13 @@ const validateCSVFile = (file, existingEstablishments = []) => {
}
// Check Factory Name
// if (factoryNameIndex >= 0 && !values[factoryNameIndex]) {
// rowErrors.push({
// column: 'Factory Name',
// message: 'Factory Name is required',
// value: ''
// });
// }
if (factoryNameIndex >= 0 && !values[factoryNameIndex]) {
rowErrors.push({
column: 'Factory Name',
message: 'Factory Name is required',
value: ''
});
}
// Check Email
if (emailIndex >= 0) {
@ -414,6 +415,35 @@ const validateCSVFile = (file, existingEstablishments = []) => {
}
};
// Log detailed validation errors to console
console.group('CSV Validation Errors');
if (duplicateErrors.length > 0) {
console.group('Duplicate Errors');
duplicateErrors.forEach((error, index) => {
console.group(`Error ${index + 1} (Row ${error.row})`);
console.log('Type:', error.isSystemDuplicate ? 'System Duplicate' : 'File Duplicate');
console.log('Establishment ID:', error.value);
console.log('Message:', error.message);
if (error.duplicateRows) {
console.log('Duplicate Rows:', error.duplicateRows.join(', '));
}
console.groupEnd();
});
console.groupEnd();
}
if (otherErrors.length > 0) {
console.group('Validation Errors');
otherErrors.forEach((error, index) => {
console.group(`Error ${index + 1} (Row ${error.row})`);
console.log('Column:', error.column);
console.log('Value:', error.value);
console.log('Message:', error.message);
console.groupEnd();
});
console.groupEnd();
}
// Customize message based on error types
if (duplicateErrors.length > 0 && otherErrors.length === 0) {
if (duplicateErrors.some(e => e.isSystemDuplicate)) {
@ -499,6 +529,8 @@ const CompanyProfile = () => {
const [isRefreshing, setIsRefreshing] = React.useState(false);
const [isUpdating, setIsUpdating] = React.useState(false);
const [formSubmitted, setFormSubmitted] = React.useState(false);
const [isViewMode, setIsViewMode] = React.useState(false);
// Import CSV state
const [importModalOpen, setImportModalOpen] = React.useState(false);
@ -510,6 +542,10 @@ const CompanyProfile = () => {
const [toastData, setToastData] = React.useState(null);
const [validationResults, setValidationResults] = React.useState(null);
const filteredAvailableProducts = availableProducts.filter(
(product) => !selectedProducts.some((sp) => sp.id === product.id)
);
// Load current user from session storage
React.useEffect(() => {
try {
@ -526,6 +562,7 @@ const CompanyProfile = () => {
const handleCloseModal = () => {
setModalMode(null);
setEditingRow(null);
setIsViewMode(false);
setForm(createEmptyProfile());
setActiveStep(0);
setFieldErrors({});
@ -860,61 +897,79 @@ const CompanyProfile = () => {
</div>
);
case 1:
return (
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-[16px] font-semibold text-[#232528]">Identification Particulars</h4>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<TextField
label="Permanent Factory Code"
value={form.permanentFactoryCode}
onChange={handleFormChange('permanentFactoryCode')}
placeholder="Enter Factory Code"
width="100%"
// required
// error={fieldErrors.permanentFactoryCode}
/>
<TextField
label="Unique License Number"
value={form.uniqueLicenseNumber}
onChange={handleFormChange('uniqueLicenseNumber')}
placeholder="Enter License Number"
width="100%"
required
error={fieldErrors.uniqueLicenseNumber}
/>
<TextField
label="Industry Code (Business Register)"
value={form.industryCodeBusiness}
onChange={handleFormChange('industryCodeBusiness')}
placeholder="Enter Code"
width="100%"
// required
// error={fieldErrors.industryCodeBusiness}
/>
<TextField
label="Industry Code (Current Production)"
value={form.industryCodeProduction}
onChange={handleFormChange('industryCodeProduction')}
placeholder="Enter Code"
width="100%"
// required
// error={fieldErrors.industryCodeProduction}
/>
</div>
<div className="mt-4">
<TextField
label="Description"
value={form.industryDescription}
onChange={handleFormChange('industryDescription')}
placeholder="Enter Description"
width="100%"
style={{ height: '72px' }}
/>
</div>
</div>
</div>
);
return (
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-[16px] font-semibold text-[#232528]">
Identification Particulars
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<TextField
label="Permanent Factory Code"
value={form.permanentFactoryCode}
onChange={handleFormChange('permanentFactoryCode')}
placeholder="Enter Factory Code"
width="100%"
/>
<TextField
label="Unique License Number"
value={form.uniqueLicenseNumber}
onChange={handleFormChange('uniqueLicenseNumber')}
placeholder="Enter License Number"
width="100%"
required
error={fieldErrors.uniqueLicenseNumber}
/>
<TextField
label="Industry Code (Business Register)"
value={form.industryCodeBusiness}
onChange={handleFormChange('industryCodeBusiness')}
placeholder="Enter Code"
width="100%"
/>
<TextField
label="Industry Code (Current Production)"
value={form.industryCodeProduction}
onChange={handleFormChange('industryCodeProduction')}
placeholder="Enter Code"
width="100%"
/>
</div>
{/* Description Field */}
<div className="mt-4">
<TextField
label="Description"
value={form.industryDescription}
onChange={handleFormChange('industryDescription')}
placeholder="Enter Description"
width="100%"
style={{ height: '72px' }}
/>
</div>
{/* 🟡 Show Remarks ONLY IF Current Production code is filled AND different from Business Register */}
{form.industryCodeProduction && form.industryCodeBusiness !== form.industryCodeProduction && (
<div className="mt-4">
<TextField
label="Remarks"
value={form.remarks}
onChange={handleFormChange('remarks')}
placeholder="Add Remarks"
width="100%"
style={{ height: '72px' }}
/>
</div>
)}
</div>
</div>
);
case 2:
return (
<div className="space-y-4">
@ -1007,7 +1062,7 @@ const CompanyProfile = () => {
onChange={handleFormChange('contactPersonName')}
placeholder="Enter Name"
width="100%"
// error={fieldErrors.contactPersonName}
error={fieldErrors.contactPersonName}
/>
<TextField
label="Contact Person Designation"
@ -1143,26 +1198,26 @@ const CompanyProfile = () => {
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div>
</div>
) : availableProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{availableProducts.map((product) => (
<li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div>
<div className="text-sm font-medium text-[#111827]">
{product.hsCode || product.hs_code || ''}
{(product.hsCode || product.hs_code) && (product.productName || product.label || product.product_name) ? ' - ' : ''}
{product.productName || product.label || product.product_name || ''}
</div>
</div>
<button
type="button"
onClick={() => handleAddProduct(product)}
className="text-[#92722A] hover:text-[#7A5F1E] text-sm font-medium"
>
Add
</button>
</li>
))}
</ul>
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{filteredAvailableProducts.map((product) => (
<li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div className="text-sm font-medium text-[#111827]">
{product.hsCode || product.hs_code || ''}
{(product.hsCode || product.hs_code) && (product.productName || product.label || product.product_name) ? ' - ' : ''}
{product.productName || product.label || product.product_name || ''}
</div>
<button
type="button"
onClick={() => handleAddProduct(product)}
className="text-[#92722A] hover:text-[#7A5F1E] text-sm font-medium"
>
Add
</button>
</li>
))}
</ul>
) : (
<div className="p-4 text-center text-sm text-[#6B7280]">
{productSearchTerm ? 'No matching products found' : 'No products available'}
@ -1257,6 +1312,8 @@ const CompanyProfile = () => {
!selectedProducts.some(sp => sp.id === p.id || sp.product_id === p.product_id)
);
setAvailableProducts(available);
} catch (error) {
console.error('Error loading products:', error);
@ -1433,53 +1490,112 @@ const CompanyProfile = () => {
);
};
const displayedRows = filteredProfiles.map((item) => {
return [
item.establishmentName,
item.userProfileName || item.contactPersonName || item.createdBy || '',
item.emirate,
item.isicCode,
item.establishmentId,
item.productCount ?? '0',
item.totalEmployees,
item.createdBy,
item.createdOn,
item.updated_at ? formatDate(item.updated_at) : (item.lastUpdated || '-'),
renderStatusBadge(item.status),
(
<div className="flex items-center gap-2" key={`actions-${item.establishmentId}`}>
<button
type="button"
className="h-[24px] w-[24px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Edit"
aria-label="Edit profile"
onClick={() => handleEdit(item.apiId ?? item.establishmentId)}
>
<img
src={pencilActiveSrc}
alt="Edit"
className="h-[24px] w-[24px]"
/>
</button>
<button
type="button"
className="h-[20px] w-[40px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title={item.status === 'Active' ? 'Deactivate' : 'Activate'}
onClick={(e) => {
e.stopPropagation();
handleToggleStatus(item.apiId, item.status === 'Active');
}}
>
<img
src={item.status === 'Active' ? activeToggleSrc : inactiveToggleSrc}
alt={item.status === 'Active' ? 'Active' : 'Inactive'}
className="h-[20px] w-[40px] object-contain"
/>
</button>
</div>
),
];
});
const displayedRows = filteredProfiles.map((item) => {
return [
// Establishment Name
item.establishmentName || "-",
// Contact Name (userProfileName contactPersonName createdBy "-")
item.userProfileName || item.contactPersonName || "-",
// Emirate
item.emirate || "-",
// ISIC Code
item.isicCode || "-",
// Establishment ID
item.establishmentId || "-",
// Product Count
item.productCount ?? "0",
// Total Employees
item.totalEmployees || "-",
// Created By
item.createdBy || "-",
// Created On
item.createdOn || "-",
// Updated On (formatted or fallback)
item.updated_at
? formatDate(item.updated_at)
: (item.lastUpdated || "-"),
// Status
renderStatusBadge(item.status),
// Action Buttons
(
<div
className="flex items-center gap-2"
key={`actions-${item.establishmentId}`}
>
{/* Edit */}
<button
type="button"
className="h-[24px] w-[24px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Edit"
aria-label="Edit profile"
onClick={() =>
handleEdit(item.apiId ?? item.establishmentId)
}
>
<img
src={pencilActiveSrc}
alt="Edit"
className="h-[24px] w-[24px]"
/>
</button>
{/* View */}
<button
type="button"
className="h-[24px] w-[24px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer ml-1"
title="View"
aria-label="View profile"
onClick={(e) => {
e.stopPropagation();
handleView(item.apiId ?? item.establishmentId);
}}
>
<img
src={viewIconSrc}
alt="View"
className="h-[24px] w-[24px]"
/>
</button>
{/* Toggle Status */}
<button
type="button"
className="h-[20px] w-[40px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title={item.status === "Active" ? "Deactivate" : "Activate"}
onClick={(e) => {
e.stopPropagation();
handleToggleStatus(
item.apiId,
item.status === "Active"
);
}}
>
<img
src={
item.status === "Active"
? activeToggleSrc
: inactiveToggleSrc
}
alt={item.status === "Active" ? "Active" : "Inactive"}
className="h-[20px] w-[40px] object-contain"
/>
</button>
</div>
),
];
});
React.useEffect(() => {
const handle = setTimeout(() => {
@ -1670,13 +1786,13 @@ const CompanyProfile = () => {
let creatorName = '-';
if (currentUser && item?.created_by && currentUser.id === item.created_by) {
const name = currentUser.name || currentUser.username || `User ${item.created_by}`;
const name = currentUser.name || currentUser.username || ` ${item.created_by_name}`;
creatorName = name.replace(/([a-zA-Z])([A-Z])/g, '$1 $2');
} else if (item?.created_by) {
const name = item?.created_by_name ||
item?.created_by_user?.name ||
item?.creator?.name ||
`User ${item.created_by}`;
` ${item.created_by_name}`;
creatorName = name.replace(/([a-zA-Z])([A-Z])/g, '$1 $2');
}
@ -2000,6 +2116,24 @@ const CompanyProfile = () => {
}
};
const handleView = async (id) => {
try {
setIsLoading(true);
const response = await fetchEstablishmentDetail(id);
const establishment = mapApiEstablishmentToProfile(response.data);
setForm(establishment);
setSelectedProducts(establishment.products || []);
setModalMode('view');
setIsViewMode(true);
setEditingRow(id);
} catch (error) {
console.error('Error fetching establishment details:', error);
showToast('error', 'Failed to load establishment details');
} finally {
setIsLoading(false);
}
};
const handleDelete = (establishmentId) => {
if (!establishmentId) return;
const originalIndex = profiles.findIndex((item) => {
@ -2798,7 +2932,7 @@ const handleImport = async () => {
</h3>
</div>
<div className="flex items-center gap-3">
<div className="relative min-w-[150px]">
<div className="relative min-w-[100px] ">
<input
type="text"
placeholder="Search By Establishment Name"
@ -2837,7 +2971,7 @@ const handleImport = async () => {
<button
type="button"
onClick={openImportModal}
className="h-10 px-2 rounded-[6px] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 bg-[#F7F7F7] text-[#232528] hover:bg-gray-50"
className="h-10 px-2 rounded-[6px] border border-[#C3C6CB] text-sm inline-flex items-center gap-1 bg-[#F7F7F7] text-[#232528] hover:bg-gray-50"
>
<img src={uploadImportIconSrc} alt="Import" className="h-5 w-5" />
<span className="font-medium">Import CSV</span>

View File

@ -92,6 +92,7 @@ const EditCompanyProfile = () => {
const [contactCityOptions, setContactCityOptions] = useState([]);
const [availableProducts, setAvailableProducts] = useState([]);
const [selectedProducts, setSelectedProducts] = useState([]);
const [newlyAddedProductIds, setNewlyAddedProductIds] = useState(new Set());
const [productSearchTerm, setProductSearchTerm] = useState('');
const [isLoadingProducts, setIsLoadingProducts] = useState(false);
const [productError, setProductError] = useState("");
@ -288,6 +289,9 @@ const EditCompanyProfile = () => {
const updated = [...prev, product];
return updated;
});
// Mark this product as newly added
setNewlyAddedProductIds(prev => new Set([...prev, product.value]));
// Remove from available products
setAvailableProducts(prev => prev.filter(p => p.value !== product.value));
@ -298,6 +302,10 @@ const EditCompanyProfile = () => {
// Remove product handler
const handleRemoveProduct = (product) => {
// Only allow removing newly added products
if (!newlyAddedProductIds.has(product.value)) {
return;
}
setSelectedProducts(prev => prev.filter(p => p.value !== product.value));
// Add back to available products if not already there and matches search
@ -408,11 +416,14 @@ const EditCompanyProfile = () => {
value: String(productId),
label: productData.product_name || productData.name || 'Unnamed Product',
hs_code: productData.hs_code || '',
product_name: productData.product_name || productData.name || 'Unnamed Product'
product_name: productData.product_name || productData.name || 'Unnamed Product',
isExisting: true // Mark as existing product
};
});
setSelectedProducts(formattedSelectedProducts);
// Initialize newly added products as empty since we're loading existing data
setNewlyAddedProductIds(new Set());
}
} catch (error) {
@ -850,22 +861,33 @@ const EditCompanyProfile = () => {
<div className="p-4">
{selectedProducts.length > 0 ? (
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
{selectedProducts.map((product) => (
<li key={product.value} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div>
<div className="text-sm font-medium text-[#111827]">
{product.label}
{selectedProducts.map((product) => {
const isNewlyAdded = newlyAddedProductIds.has(product.value);
return (
<li key={product.value} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
<div>
<div className="text-sm font-medium text-[#111827]">
{product.label}
</div>
{product.hs_code && (
<div className="text-xs text-gray-500 mt-1">
HS Code: {product.hs_code}
</div>
)}
</div>
</div>
<button
type="button"
onClick={() => handleRemoveProduct(product)}
className="text-[#EF4444] hover:text-[#DC2626] text-sm font-medium"
>
Remove
</button>
</li>
))}
{isNewlyAdded && (
<button
type="button"
onClick={() => handleRemoveProduct(product)}
className="text-[#EF4444] hover:text-[#DC2626] text-sm font-medium"
title="Remove product"
>
Remove
</button>
)}
</li>
);
})}
</ul>
) : (
<div className="p-4 text-center text-sm text-[#6B7280] border border-[#E5E7EB] rounded-md">

View File

@ -350,7 +350,7 @@ const IsicHsCodes = () => {
const [deletingRow, setDeletingRow] = React.useState(null);
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
const [currentPage, setCurrentPage] = React.useState(1);
const pageSize = 10;
const [pageSize, setPageSize] = React.useState(10);
const [modalMode, setModalMode] = React.useState(null);
const [form, setForm] = React.useState(createEmptyCodeForm());
const [unitOptions, setUnitOptions] = React.useState([]);
@ -484,6 +484,7 @@ const IsicHsCodes = () => {
const headers = [
'HS Code',
'Product Name',
'Description',
'Unit',
'Establishment Mapped',
'Created By',
@ -500,7 +501,8 @@ const IsicHsCodes = () => {
const filtered = rowsData.filter(
(item) =>
item.code.toLowerCase().includes(lowercasedSearch) ||
item.product.toLowerCase().includes(lowercasedSearch)
item.product.toLowerCase().includes(lowercasedSearch) ||
item.description.toLowerCase().includes(lowercasedSearch)
);
setFilteredData(filtered);
}
@ -526,6 +528,7 @@ const IsicHsCodes = () => {
return [
item.code,
item.product,
item.description || '-',
item.unit,
item.estimatedMapped,
displayName,
@ -550,9 +553,9 @@ const IsicHsCodes = () => {
setForm({
code: product.hs_code || '',
product: product.product_name || '',
description: product.hs_description || '',
unit: product.unit_id ? String(product.unit_id) : '',
status: product.is_active ? 'Active' : 'Inactive',
description: product.hs_description || '',
createdBy: selected.createdBy,
createdOn: selected.createdOn || new Date(product.created_at).toLocaleDateString('en-GB'),
updated: selected.updated || (product.updated_at ? new Date(product.updated_at).toLocaleDateString('en-GB') : '')
@ -599,9 +602,9 @@ const IsicHsCodes = () => {
id: productData.id,
code: productData.hs_code || '',
product: productData.product_name || '',
description: productData.hs_description || '',
unit: productData.unit_id ? String(productData.unit_id) : '',
status: productData.is_active ? 'Active' : 'Inactive',
description: productData.hs_description || ''
};
console.log('Mapped form data:', formData);
@ -801,6 +804,7 @@ const IsicHsCodes = () => {
return [
item.code,
item.product,
item.description || '-',
item.unit,
item.estimatedMapped,
item.createdBy,
@ -854,6 +858,7 @@ const IsicHsCodes = () => {
const productData = {
hs_code: form.code,
product_name: form.product,
hs_description: form.description || '',
unit_id: form.unit ? parseInt(form.unit) : null,
// Preserve the original created_by value for updates, or use current user for new records
created_by: modalMode === 'edit' ? (existingProduct?.created_by || userProfile?.id) : (userProfile?.id || null)
@ -890,6 +895,7 @@ const IsicHsCodes = () => {
// Only update these specific fields
code: form.code,
product: form.product,
description: form.description || '',
unit: selectedUnit ? selectedUnit.label : existingProduct?.unit || 'N/A',
unit_id: form.unit ? parseInt(form.unit) : existingProduct?.unit_id || null,
updated: new Date().toLocaleDateString('en-GB'),
@ -940,6 +946,7 @@ const IsicHsCodes = () => {
id: response.data?.id || Date.now(),
code: form.code,
product: form.product,
description: form.description || '',
unit: selectedUnit ? selectedUnit.label : 'N/A',
unit_id: form.unit ? parseInt(form.unit) : null,
estimatedMapped: 0,
@ -1031,6 +1038,7 @@ const IsicHsCodes = () => {
id: product.id,
code: product.hs_code || 'N/A',
product: product.product_name || 'N/A',
description: product.hs_description || '',
unit: product.unit?.uom || 'N/A',
estimatedMapped: 0,
createdBy: creatorName,
@ -1188,17 +1196,17 @@ const IsicHsCodes = () => {
headers={headers}
rows={rows}
renderCell={(value, rowIndex, colIndex) => {
// Handle product name column (index 1) with text wrapping
if (colIndex === 1) {
// Handle product name and description columns with text wrapping
if (colIndex === 1 || colIndex === 2) {
return (
<div className="max-w-[300px] whitespace-normal break-words">
<div className="max-w-[200px] whitespace-normal break-words">
{value}
</div>
);
}
// Handle action buttons column (index 6)
if (colIndex === 6) {
// Handle action buttons column (index 7)
if (colIndex === 7) {
return (
<div className="flex items-center gap-2">
<button
@ -1257,6 +1265,11 @@ const IsicHsCodes = () => {
onPageChange: setCurrentPage,
pageSize,
totalItems: filteredData.length,
pageSizeOptions: [10, 20, 50, 100],
onPageSizeChange: (size) => {
setPageSize(size);
setCurrentPage(1);
}
}}
/>
</div>
@ -1394,8 +1407,10 @@ const IsicHsCodes = () => {
placeholder="Enter Product Name"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mt-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div>
<label className="block text-sm font-medium text-[#374151] mb-1">
Unit <span className="text-[#EF4444]">*</span>
@ -1424,7 +1439,21 @@ const IsicHsCodes = () => {
</select>
)}
</div>
<div>
<label className="block text-sm font-medium text-[#374151] mb-1">
Description
</label>
<textarea
value={form.description || ''}
onChange={handleFormChange("description")}
disabled={modalMode === "view"}
rows={1}
className="w-full px-3 py-2 text-sm border border-[#CBA344] rounded-md focus:outline-none focus:ring-1 focus:ring-[#92722A] focus:border-[#92722A] min-h-[38px]"
placeholder="Enter product description"
style={{ resize: 'vertical' }}
/>
</div>
{/* <div>
<label className="block text-sm font-medium text-[#374151] mb-1">
Status <span className="text-[#EF4444]">*</span>

View File

@ -88,8 +88,9 @@ const UnitMaster = () => {
const [importLoading, setImportLoading] = useState(false);
const [importError, setImportError] = useState('');
const fileInputRef = useRef(null);
const pageSize = 10;
const [pageSize, setPageSize] = React.useState(10);
// const pageSize = 10;
// Fetch units on component mount
useEffect(() => {
@ -779,6 +780,11 @@ const UnitMaster = () => {
onPageChange: setCurrentPage,
pageSize,
totalItems: filteredUnits.length,
pageSizeOptions: [10, 20, 50, 100],
onPageSizeChange: (size) => {
setPageSize(size);
setCurrentPage(1);
}
}}
/>
)}

View File

@ -13,7 +13,7 @@ const Banner = () => {
{/* Title */}
<h1 className="text-2xl md:text-3xl font-semibold text-gray-800 mb-6">
Industrial Production Index (IPI) Survey UAE
IIP (Index of Industrial Production) UAE
</h1>
{/* Description */}

View File

@ -6,10 +6,10 @@ const Industrial = () => {
{/* Left Side: Content */}
<div>
<h2 className="text-2xl font-semibold text-gray-900 mb-4">
What is the Industrial Production Index (IPI)?
What is the Index of Industrial Production (IIP)?
</h2>
<p className="text-gray-700 leading-relaxed text-justify">
The Industrial Production Index (IPI) measures changes in the volume
The Index of Industrial Production (IIP) measures changes in the volume
of industrial output over time. It tracks production across
manufacturing activities classified by ISIC and is a key official
indicator used to monitor economic activity and inform policy.

View File

@ -76,7 +76,7 @@ const Navbar = () => {
{/* Desktop Menu */}
<nav className="hidden md:flex items-center space-x-8 text-[15px] font-medium">
<button onClick={() => handleClick("about-ipi")} className={linkClass("about-ipi")}>
About IPI
About IIP
</button>
<button onClick={() => handleClick("how-it-works")} className={linkClass("how-it-works")}>
How it works
@ -116,7 +116,7 @@ const Navbar = () => {
onClick={() => handleClick(id)}
className={`${linkClass(id)} block w-full text-left`}
>
{id === "about-ipi" && "About IPI"}
{id === "about-ipi" && "About IIP"}
{id === "how-it-works" && "How it works"}
{id === "eligibility" && "Eligibility"}
{id === "faqs" && "FAQs"}

View File

@ -5,7 +5,7 @@ const Survey = () => {
<section className="mx-auto px-6 md:px-10 lg:px-24 py-12">
{/* Heading */}
<h2 className="text-2xl font-semibold text-gray-900 mb-4">
Who should complete the IPI <span className="text-gray-900">survey</span>?
Who should complete the IIP <span className="text-gray-900">survey</span>?
</h2>
{/* Content */}

View File

@ -318,7 +318,7 @@ const Login = () => {
<img src={logoSrc} alt="FCSC Logo" className="mx-auto h-16 w-auto" />
</div>
<h1 className="text-[17px] font-semibold text-[#1F2937] mt-0">
Industrial Production Index (IPI) Survey Portal
IIP (Index of Industrial Production)
</h1>
</div>