From 9b0dc99f0f182f6afc0f8a56198f6ef784836aa9 Mon Sep 17 00:00:00 2001 From: Malini Date: Thu, 6 Nov 2025 08:08:58 +0530 Subject: [PATCH] Bug resolved in products code --- ipi-survey-platform/src/App.jsx | 17 +- .../src/components/common/FormControls.jsx | 3 + .../EstablishmentInfo/EstablishmentInfo.jsx | 4 +- .../Admin/configuration/CompanyProfile.jsx | 428 ++++++++++++++---- .../pages/Admin/configuration/IsicHsCodes.jsx | 245 +++++++--- .../Admin/configuration/QuarterlyWindows.jsx | 113 ++--- .../pages/Admin/configuration/UnitMaster.jsx | 324 ++++++++++--- .../src/pages/History/History.jsx | 87 ++++ 8 files changed, 959 insertions(+), 262 deletions(-) diff --git a/ipi-survey-platform/src/App.jsx b/ipi-survey-platform/src/App.jsx index dacc339..ec3874d 100644 --- a/ipi-survey-platform/src/App.jsx +++ b/ipi-survey-platform/src/App.jsx @@ -268,7 +268,7 @@ function App() { } /> - + {/* } /> - + */} + + + + } /> + {/* + + + } + /> */} } /> } /> } /> diff --git a/ipi-survey-platform/src/components/common/FormControls.jsx b/ipi-survey-platform/src/components/common/FormControls.jsx index c677b62..a0e273f 100644 --- a/ipi-survey-platform/src/components/common/FormControls.jsx +++ b/ipi-survey-platform/src/components/common/FormControls.jsx @@ -329,6 +329,7 @@ export const SelectField = ({ clearValue = '', error = '', readOnly = false, + required = false, }) => { const wrapperStyle = {}; if (width !== 'auto') { @@ -383,6 +384,7 @@ export const SelectField = ({ {label && ( )}
@@ -399,6 +401,7 @@ export const SelectField = ({ style={inputStyle} onFocus={handleFocus} onBlur={handleBlur} + required={required} > {placeholder && (
@@ -385,11 +486,12 @@ const CompanyProfile = () => {
@@ -1027,6 +1129,35 @@ const CompanyProfile = () => { [showToast] ); + const captureCorporateValues = (formData) => { + const corporateValues = {}; + const corporateFields = [ + 'corporateName', + 'corporateAddress', + 'corporateCityTown', + 'corporateCityTownId', + 'corporateEmirate', + 'corporateEmirateId', + 'corporatePostalCode', + 'corporatePoBox', + 'corporateMakaniNumber', + 'corporateContactPersonName', + 'corporateContactPersonDesignation', + 'corporateCountryCode', + 'corporateMobileNumber', + 'corporateEmail', + 'corporateWebsite' + ]; + + corporateFields.forEach(field => { + corporateValues[field] = formData[field] || ''; + }); + + return corporateValues; +}; + +// Add this array as well +const corporateFieldKeys = Object.values(contactToCorporateMap); const loadCorporateCityOptions = React.useCallback( async (emirateId) => { if (!emirateId) { @@ -1082,6 +1213,16 @@ const CompanyProfile = () => { }, []); const mapApiEstablishmentToProfile = React.useCallback((item) => { + // Get current user from session storage + let currentUser = null; + try { + const profile = sessionStorage.getItem('user_profile'); + if (profile) { + currentUser = JSON.parse(profile); + } + } catch (error) { + console.error('Error parsing user profile:', error); + } const base = createEmptyProfile(); const primaryUser = Array.isArray(item?.users) && item.users.length ? item.users[0] : item?.establishment_user ?? null; const emiratiMale = item?.emirati_male ?? ''; @@ -1094,6 +1235,24 @@ const CompanyProfile = () => { const corporateEmirateName = item?.corporate_emirate?.name ?? item?.emirate ?? ''; const establishmentCityName = item?.establishment_city?.name ?? base.contactCityTown; const corporateCityName = item?.corporate_city?.name ?? base.corporateCityTown; + const contactEmail = item?.establishment_contact_email ?? base.contactEmail; + + // Get creator's name - check if current user is the creator + let creatorName = '-'; + if (currentUser && item?.created_by && currentUser.id === item.created_by) { + // If current user is the creator, use their name + creatorName = currentUser.name || currentUser.username || `User ${item.created_by}`; + } else if (item?.created_by) { + // If not the current user, try to get creator's name from the item or use ID as fallback + creatorName = item?.created_by_name || + item?.created_by_user?.name || + item?.creator?.name || + `User ${item.created_by}`; + } + + console.log('Creator ID:', item?.created_by, 'Current User ID:', currentUser?.id); + console.log('Creator Name:', creatorName); + return { ...base, @@ -1114,12 +1273,13 @@ const CompanyProfile = () => { totalEmployees: totalEmployees !== '' && totalEmployees !== null ? String(totalEmployees) : '', status: item?.is_active === false ? 'Inactive' : 'Active', isActive: item?.is_active !== false, - createdBy: item?.created_by_name ?? primaryUser?.name ?? '', + createdBy: creatorName, createdById: item?.created_by ?? base.createdById, createdOn: formatApiDate(item?.created_at), lastUpdated: item?.updated_by ?? formatApiDate(item?.updated_at), contactName: item?.factory_name ?? '', - contactEmail: item?.email ?? '', + // contactEmail: item?.email ?? '', + contactEmail: item?.establishment_contact_email ?? base.contactEmail, contactPostalCode: item?.establishment_postal_code ?? base.contactPostalCode, contactPoBox: item?.establishment_po_box ?? base.contactPoBox, contactMakaniNumber: item?.establishment_makani_number ?? base.contactMakaniNumber, @@ -1173,6 +1333,7 @@ const loadProfiles = async () => { try { const response = await fetchEstablishments({ params, signal: controller.signal }); const payload = response?.data ?? response; + console.log("payloadtest",payload) const records = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : []; if (!cancelled) { @@ -1257,73 +1418,103 @@ const loadProfiles = async () => { [] ); - const handleEdit = (identifier) => { - if (modalMode && isDirty) { - const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?'); - if (!shouldContinue) { - return; - } + const handleEdit = async (identifier) => { + if (modalMode && isDirty) { + const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?'); + if (!shouldContinue) { + return; } + } + + // Update URL with the establishment ID + navigate(`?edit=${encodeURIComponent(identifier)}`); + if (!identifier) return; + + try { + // Show loading state + setModalMode('loading'); - // Update URL with the establishment ID - navigate(`?edit=${encodeURIComponent(identifier)}`); - if (!identifier) return; + // Find the profile record const originalIndex = profiles.findIndex((item) => { if (item.apiId !== null && item.apiId !== undefined && item.apiId === identifier) { return true; } return item.establishmentId === identifier; }); - if (originalIndex === -1) return; - setEditingRow(originalIndex); + + if (originalIndex === -1) { + showToast('error', 'Establishment not found'); + return; + } + const profileRecord = profiles[originalIndex]; - const base = createEmptyProfile(); - let nextForm = { - ...base, - ...profileRecord, - corporateSameAs: Boolean(profileRecord?.corporateSameAs), - }; - if (nextForm.corporateSameAs) { - Object.entries(contactToCorporateMap).forEach(([source, target]) => { - nextForm[target] = nextForm[source]; - }); + setEditingRow(originalIndex); + + // If we have an API ID, fetch the latest data + if (profileRecord?.apiId) { + const response = await fetchEstablishmentDetail(profileRecord.apiId); + const detail = response?.data || response; + + if (!detail) { + showToast('error', 'No data received from server'); + return; + } + + // Map the API response to form fields + const mapped = { + ...createEmptyProfile(), + ...mapApiEstablishmentToProfile(detail), + }; + + mapped.apiId = mapped.apiId ?? profileRecord.apiId; + mapped.corporateSameAs = Boolean(mapped.corporateSameAs); + + if (mapped.corporateSameAs) { + Object.entries(contactToCorporateMap).forEach(([source, target]) => { + mapped[target] = mapped[source]; + }); + } + + // Set the form with the mapped data + setForm(prev => ({ + ...prev, + ...mapped, + ...computeEmploymentTotals(mapped) + })); + + initialSnapshotRef.current = { ...mapped, ...computeEmploymentTotals(mapped) }; + corporateBackupRef.current = captureCorporateValues(mapped); + } else { + // Fallback to local data if no API ID + const base = createEmptyProfile(); + const nextForm = { + ...base, + ...profileRecord, + corporateSameAs: Boolean(profileRecord?.corporateSameAs), + }; + + if (nextForm.corporateSameAs) { + Object.entries(contactToCorporateMap).forEach(([source, target]) => { + nextForm[target] = nextForm[source]; + }); + } + + Object.assign(nextForm, computeEmploymentTotals(nextForm)); + setForm(nextForm); + initialSnapshotRef.current = { ...nextForm }; + corporateBackupRef.current = captureCorporateValues(nextForm); } - Object.assign(nextForm, computeEmploymentTotals(nextForm)); - setForm(nextForm); + setModalMode('edit'); - initialSnapshotRef.current = nextForm; - corporateBackupRef.current = captureCorporateValues(nextForm); setIsDirty(false); - - if (profileRecord?.apiId !== null && profileRecord?.apiId !== undefined) { - (async () => { - try { - const response = await fetchEstablishmentDetail(profileRecord.apiId); - const detail = response?.data || response; - if (!detail) return; - const mapped = { - ...createEmptyProfile(), - ...mapApiEstablishmentToProfile(detail), - }; - mapped.apiId = mapped.apiId ?? profileRecord.apiId; - mapped.corporateSameAs = Boolean(mapped.corporateSameAs); - if (mapped.corporateSameAs) { - Object.entries(contactToCorporateMap).forEach(([source, target]) => { - mapped[target] = mapped[source]; - }); - } - Object.assign(mapped, computeEmploymentTotals(mapped)); - setForm(mapped); - initialSnapshotRef.current = mapped; - corporateBackupRef.current = captureCorporateValues(mapped); - setIsDirty(false); - } catch (error) { - const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details.'; - showToast('error', message); - } - })(); - } - }; + + } catch (error) { + console.error('Error in handleEdit:', error); + const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details'; + showToast('error', message); + setModalMode(null); + } +}; const handleDelete = (establishmentId) => { if (!establishmentId) return; @@ -1387,7 +1578,7 @@ const loadProfiles = async () => { setModalMode(null); setForm(createEmptyProfile()); setEditingRow(null); - setErrors({}); + setFieldErrors({}); setIsDirty(false); // Clear URL parameters when closing modal navigate(''); @@ -1401,6 +1592,56 @@ const loadProfiles = async () => { setDeletingRow(null); }; + const computeFieldErrorMessage = (field, value) => { + // Required field validation + if (!value && requiredFields.some(f => f.field === field)) { + return 'This field is required'; + } + + // Email validation + if ((field === 'contactEmail' || field === 'corporateEmail' || field === 'userProfileEmail') && value) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(value)) { + return 'Please enter a valid email address'; + } + } + + // Website URL validation + if ((field === 'contactWebsite' || field === 'corporateWebsite') && value) { + if (!/^https?:\/\/.+\..+/.test(value)) { + return 'Please enter a valid URL (e.g., https://example.com)'; + } + } + + // Numeric field validation + const numericFields = [ + 'employmentEmiratiMale', + 'employmentEmiratiFemale', + 'employmentNonEmiratiMale', + 'employmentNonEmiratiFemale', + 'employmentTotalEmirati', + 'employmentTotalEmployees' + ]; + if (numericFields.includes(field) && value && !/^\d+$/.test(value)) { + return 'Please enter a valid number'; + } + + return ''; +}; + +// Add this array to define required fields +const requiredFields = [ + { field: 'userProfileName', label: 'Name' }, + { field: 'userProfileEmail', label: 'Email' }, + { field: 'contactName', label: 'Contact Name' }, + { field: 'contactAddress', label: 'Contact Address' }, + { field: 'contactEmirate', label: 'Emirate' }, + { field: 'contactMobileNumber', label: 'Mobile Number' }, + { field: 'corporateName', label: 'Corporate Name' }, + { field: 'corporateAddress', label: 'Corporate Address' }, + { field: 'corporateEmirate', label: 'Corporate Emirate' }, + { field: 'corporateMobileNumber', label: 'Corporate Mobile Number' } +]; const handleDeleteConfirm = async () => { if (deletingRow === null) return; if (isDeletingApi) return; @@ -1650,7 +1891,8 @@ const loadProfiles = async () => { non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0, total_emirati: Number(totalEmirati) || 0, total_employees: Number(employmentTotals) || 0, - created_by: Number(form.createdById) || 0, + created_by: currentUserId?.id, + created_by_name: currentUserId?.name, // Add this line establishment_user: { name: form.userProfileName || '', email: form.userProfileEmail || '', @@ -1659,6 +1901,7 @@ const loadProfiles = async () => { }; try { const apiResponse = await createEstablishment(apiPayload); + console.log('API Response:', apiResponse); const successMessage = apiResponse?.message || 'Establishment added successfully.'; showToast('success', successMessage); const createdRecord = apiResponse?.data; @@ -1689,25 +1932,50 @@ const loadProfiles = async () => { delete establishmentUserPayload.email; } const updatePayload = { - establishment_code: form.establishmentId || '', - factory_name: form.establishmentName || form.contactName || '', - email: form.contactEmail || form.userProfileEmail || '', - permanent_factory_code: form.permanentFactoryCode || '', - industry_code: form.industryCodeCurrent || form.industryCodeBusiness || '', - license_number: form.uniqueLicenseNumber || '', - isic_code: form.industryCodeBusiness || form.isicCode || '', - emirate: form.emirate || form.contactEmirate || '', - emirati_male: Number(form.employmentEmiratiMale) || 0, - emirati_female: Number(form.employmentEmiratiFemale) || 0, - non_emirati_male: Number(form.employmentNonEmiratiMale) || 0, - non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0, - total_emirati: Number(totalEmirati) || 0, - total_employees: Number(employmentTotals) || 0, + establishment_code: form.establishmentId || '', + factory_name: form.establishmentName || form.contactName || '', + permanent_factory_code: form.permanentFactoryCode || '', + industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '', + license_number: form.uniqueLicenseNumber || '', + isic_code: form.isicCode || form.industryCodeBusiness || '', + description: form.industryDescription || '', + establishment_address: form.contactAddress || '', + establishment_city_town_id: Number(form.contactCityTownId) || 0, + establishment_emirate_id: Number(form.contactEmirateId) || 0, + establishment_postal_code: form.contactPostalCode || '', + establishment_po_box: form.contactPoBox || '', + establishment_makani_number: form.contactMakaniNumber || '', + establishment_contact_person_name: form.contactPersonName || '', + establishment_contact_person_designation: form.contactPersonDesignation || '', + establishment_mobile_number: form.contactMobileNumber || '', + establishment_contact_email: form.contactEmail || '', + establishment_website: form.contactWebsite || '', + corporate_same_as_establishment: Boolean(form.corporateSameAs), + corporate_name: form.corporateName || '', + corporate_address: form.corporateAddress || '', + corporate_city_town_id: Number(form.corporateCityTownId) || 0, + corporate_emirate_id: Number(form.corporateEmirateId) || 0, + corporate_postal_code: form.corporatePostalCode || '', + corporate_po_box: form.corporatePoBox || '', + corporate_makani_number: form.corporateMakaniNumber || '', + corporate_contact_person_name: form.corporateContactPersonName || '', + corporate_contact_person_designation: form.corporateContactPersonDesignation || '', + corporate_mobile_number: form.corporateMobileNumber || '', + corporate_email: form.corporateEmail || '', + corporate_website: form.corporateWebsite || '', + emirati_male: Number(form.employmentEmiratiMale) || 0, + emirati_female: Number(form.employmentEmiratiFemale) || 0, + non_emirati_male: Number(form.employmentNonEmiratiMale) || 0, + non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0, + total_emirati: (Number(form.employmentEmiratiMale) || 0) + (Number(form.employmentEmiratiFemale) || 0), + total_employees: Number(form.employmentTotalEmployees) || 0, + updated_by: 1 // Replace with actual user ID from your auth context }; if (establishmentUserPayload.name || establishmentUserPayload.email || establishmentUserPayload.password) { updatePayload.establishment_user = establishmentUserPayload; } const apiResponse = await updateEstablishment(targetId, updatePayload); + console.log("apiResponse",apiResponse) const successMessage = apiResponse?.message || 'Establishment updated successfully.'; showToast('success', successMessage); apiResultData = apiResponse?.data; @@ -1932,7 +2200,7 @@ const loadProfiles = async () => { 120, // ISIC Code (120px) 150, // Establishment ID (150px) 100, // Products (100px) - 120, // Total Employees (120px) + 130, // Total Employees (120px) 120, // Created By (120px) 120, // Created On (120px) 120, // Last Updated (120px) diff --git a/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx index 77357f9..e3ffd0e 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx @@ -56,6 +56,17 @@ const IsicHsCodes = () => { const [form, setForm] = React.useState(createEmptyCodeForm()); const [unitOptions, setUnitOptions] = React.useState([]); const [toast, setToast] = React.useState({ show: false, message: '' }); + + // Get user profile from session + const userProfile = React.useMemo(() => { + try { + const profile = sessionStorage.getItem('user_profile'); + return profile ? JSON.parse(profile) : null; + } catch (error) { + console.error('Error parsing user profile:', error); + return null; + } + }, []); // Fetch units on mount useEffect(() => { @@ -111,18 +122,32 @@ const IsicHsCodes = () => { : response.data.data || []; if (products.length > 0) { - const formattedData = products.map((product) => ({ - id: product.id, - code: product.hs_code || 'N/A', - product: product.product_name || 'N/A', - unit: 'Unit', - estimatedMapped: 0, - createdBy: product.created_by || 'System', - updated: product.updated_at - ? new Date(product.updated_at).toLocaleDateString() - : '-', - status: product.is_active ? 'Active' : 'Inactive', - description: product.hs_description || '', + const formattedData = await Promise.all(products.map(async (product) => { + // Try to get creator's name if available + let createdByName = '-'; + if (product.created_by) { + try { + // If the API returns the creator's name in the product data, use it + createdByName = product.created_by_user?.name || '-'; + } catch (error) { + console.error('Error fetching creator info:', error); + } + } + + return { + id: product.id, + code: product.hs_code || 'N/A', + product: product.product_name || 'N/A', + unit: product.unit?.uom || 'N/A', + estimatedMapped: 0, + createdBy: createdByName, + createdById: product.created_by || null, + updated: product.updated_at + ? new Date(product.updated_at).toLocaleDateString() + : '-', + status: product.is_active ? 'Active' : 'Inactive', + description: product.hs_description || '', + }; })); setRowsData(formattedData); setFilteredData(formattedData); @@ -199,7 +224,7 @@ const IsicHsCodes = () => { setForm({ code: product.hs_code || '', product: product.product_name || '', - unit: 'Unit', // Update this if you have unit in the API response + unit: product.unit_id ? String(product.unit_id) : '', status: product.is_active ? 'Active' : 'Inactive', description: product.hs_description || '' }); @@ -235,7 +260,7 @@ const IsicHsCodes = () => { setForm({ code: product.hs_code || '', product: product.product_name || '', - unit: 'Unit', // Update this if you have unit in the API response + unit: product.unit_id ? String(product.unit_id) : '', status: product.is_active ? 'Active' : 'Inactive', description: product.hs_description || '' }); @@ -253,30 +278,98 @@ const IsicHsCodes = () => { } }; - const handleDelete = (index) => { - setDeletingRow(index); - setShowDeleteConfirm(true); - }; + // Store the row index when delete is clicked + const [deletingRowIndex, setDeletingRowIndex] = React.useState(null); - const deletingCode = React.useMemo(() => { - if (deletingRow === null || deletingRow < 0 || deletingRow >= rowsData.length) - return null; - return rowsData[deletingRow]; - }, [deletingRow, rowsData]); - - const closeDeleteConfirm = () => { - setShowDeleteConfirm(false); - setDeletingRow(null); - }; - - const handleDeleteConfirm = async () => { - if (deletingRow === null) return; + const handleDelete = async (paginationIndex) => { + // Convert pagination index to actual data index + const dataIndex = (currentPage - 1) * pageSize + paginationIndex; + console.log('Delete button clicked, paginationIndex:', paginationIndex, 'dataIndex:', dataIndex); + + if (dataIndex < 0 || dataIndex >= rowsData.length) { + console.error('Invalid row index for deletion'); + return; + } + + const productId = rowsData[dataIndex]?.id; + if (!productId) { + console.error('No product ID found for deletion'); + return; + } + + // Store the row index for later use + setDeletingRowIndex(dataIndex); try { setIsLoading(true); - const productId = rowsData[deletingRow]?.id; + console.log('Fetching product details before deletion, ID:', productId); + + // Fetch the latest product data by ID + const response = await productService.getProductById(productId); + console.log('Product details from API:', response); + + if (!response || !response.data) { + throw new Error('Invalid product data received'); + } + + // Update the row data with the latest from the server + const updatedRowsData = [...rowsData]; + updatedRowsData[dataIndex] = { + ...updatedRowsData[dataIndex], + ...response.data + }; + + setRowsData(updatedRowsData); + setShowDeleteConfirm(true); + } catch (error) { + console.error('Error preparing product for deletion:', error); + showToast('Error loading product details for deletion', 'error'); + setDeletingRowIndex(null); + } finally { + setIsLoading(false); + } + }; + + const deletingCode = React.useMemo(() => { + if (deletingRowIndex === null || deletingRowIndex < 0 || deletingRowIndex >= rowsData.length) + return null; + return rowsData[deletingRowIndex]; + }, [deletingRowIndex, rowsData]); + + const closeDeleteConfirm = () => { + setShowDeleteConfirm(false); + setDeletingRowIndex(null); + }; + + const handleDeleteConfirm = async () => { + console.log('Delete confirmed, deletingRowIndex:', deletingRowIndex); + + if (deletingRowIndex === null || deletingRowIndex < 0 || deletingRowIndex >= rowsData.length) { + const errorMsg = 'Invalid row selected for deletion'; + console.error(errorMsg, { deletingRowIndex, rowsDataLength: rowsData.length }); + showToast(errorMsg, 'error'); + setShowDeleteConfirm(false); + setDeletingRowIndex(null); + return; + } + + try { + setIsLoading(true); + + const product = rowsData[deletingRowIndex]; + const productId = product?.id; + if (!productId) { - throw new Error('Product ID not found for deletion'); + throw new Error('No product ID found for deletion'); + } + + console.log('Deleting product:', { productId, product }); + console.log('Product ID to delete:', productId); + + if (!productId) { + const errorMsg = 'Product ID not found for deletion'; + console.error(errorMsg); + throw new Error(errorMsg); } // Call delete API @@ -376,6 +469,8 @@ const IsicHsCodes = () => { product_name: form.product, is_active: form.status === 'Active', hs_code: form.code, + unit_id: form.unit ? parseInt(form.unit) : null, + created_by: userProfile?.id || null, // Add created_by with user ID ...(form.description && { hs_description: form.description }) }; @@ -391,35 +486,60 @@ const IsicHsCodes = () => { console.log(`Updating product with ID: ${productId}`); try { - // Call the update API with only the necessary fields + console.log('Sending update request with data:', { + productId, + productData, + formUnit: form.unit, + unitOptions: unitOptions + }); + const response = await productService.updateProduct(productId, productData); console.log('Update response:', response); - // Update the local state with the updated product data - setRowsData(prev => - prev.map(item => - item.id === productId - ? { - ...item, - code: form.code, - product: form.product, - status: form.status, - description: form.description || '', - updated: new Date().toLocaleDateString('en-GB') - } - : item - ) - ); + // Find the selected unit from unitOptions to get the uom + const selectedUnit = unitOptions.find(u => u.value === form.unit); + console.log('Selected unit for update:', selectedUnit); + + // Create the updated product object + const updatedProduct = { + ...rowsData[editingRow], + code: form.code, + product: form.product, + unit: selectedUnit ? selectedUnit.label : 'N/A', + unit_id: form.unit ? parseInt(form.unit) : null, + updated: new Date().toLocaleDateString('en-GB'), + status: form.status, + description: form.description || '' + }; + + // Update the product in the list while maintaining the same position + setRowsData(prev => { + const updated = [...prev]; + updated[editingRow] = updatedProduct; + return updated; + }); + + // Also update filteredData if needed + setFilteredData(prev => { + const updated = [...prev]; + const index = updated.findIndex(item => item.id === updatedProduct.id); + if (index !== -1) { + updated[index] = updatedProduct; + } + return updated; + }); - console.log('Product updated successfully'); showToast('Product updated successfully!'); closeModal(); - return; // Exit the function after successful update + return; } catch (updateError) { console.error('Error updating product:', updateError); - const errorMessage = updateError.response?.data?.message || 'Failed to update product. Please try again.'; + let errorMessage = 'Failed to update product. Please try again.'; + if (updateError.response?.data?.message) { + errorMessage = updateError.response.data.message; + } setError(errorMessage); - return; // Exit the function after error + return; } } @@ -429,19 +549,26 @@ const IsicHsCodes = () => { const response = await productService.createProduct(productData); console.log('Create response:', response); + // Find the selected unit from unitOptions to get the uom + const selectedUnit = unitOptions.find(u => u.value === form.unit); + const newProduct = { id: response.data?.id || Date.now(), // Fallback to timestamp if no ID in response code: form.code, product: form.product, - unit: 'Unit', + unit: selectedUnit ? selectedUnit.label : 'N/A', + unit_id: form.unit ? parseInt(form.unit) : null, estimatedMapped: 0, - createdBy: 'Current User', + createdBy: userProfile?.name || 'System', // Use user's name or fallback to 'System' + createdById: userProfile?.id || null, // Store user ID for reference updated: new Date().toLocaleDateString('en-GB'), status: form.status, description: form.description || '' }; - setRowsData(prev => [...prev, newProduct]); + // Add new product to the beginning of the array to show it at the top + setRowsData(prev => [newProduct, ...prev]); + setFilteredData(prev => [newProduct, ...prev]); console.log('Product created successfully'); showToast('Product created successfully!'); closeModal(); @@ -782,14 +909,14 @@ const IsicHsCodes = () => {
- -
- +
+
{ + setShowDeleteConfirm(false); + setDeletingRow(null); + }} + /> +
+
+
+ Delete +
+

+ Delete Product +

+

+ Are you sure you want to delete{' '} + + {rowsData[deletingRow]?.product || 'this product'}? + +

+

+ This action will permanently delete the data and cannot be undone. +

- )} + +
+ + +
+
+
+
+)}
); diff --git a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx index 3cab700..9bdf98c 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx @@ -3,7 +3,8 @@ import Table from '@/components/common/Table'; import StatusBadge from '@/components/common/StatusBadge'; import { TextField, SelectField } from '@/components/common/FormControls'; import { getUnits, createUnit, updateUnit, deleteUnit } from '@/services/configuration/unitService'; - +import { toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; const downloadIconSrc = '/assets/images/DownloadSimple.svg'; const addIconSrc = '/assets/images/ic_baseline-plus.svg'; const pencilActiveSrc = '/assets/images/PencilSimple.svg'; @@ -40,17 +41,47 @@ const UnitMaster = () => { fetchUnits(); }, []); - const fetchUnits = async () => { - try { - setLoading(true); - const response = await getUnits(); - setUnits(response.data || []); - } catch (error) { - console.error('Error fetching units:', error); - } finally { - setLoading(false); - } - }; + // const fetchUnits = async () => { + // try { + // setLoading(true); + // const response = await getUnits(); + // setUnits(response.data || []); + // } catch (error) { + // console.error('Error fetching units:', error); + // } finally { + // setLoading(false); + // } + // }; + +const fetchUnits = async () => { + try { + setLoading(true); + const response = await getUnits(); + console.log("resposne",response) + const unitsData = response.data || []; + + // Process units to ensure mapped_products_count is included + const processedUnits = unitsData.map(unit => ({ + ...unit, + // Use existing mapped_products_count or calculate from productsMapped array + mapped_products_count: unit.mapped_products_count || + (Array.isArray(unit.productsMapped) ? unit.productsMapped.length : 0) + })); + console.log("processedUnits",processedUnits) + + // Sort by latest created_at date (newest first) + const sortedUnits = [...processedUnits].sort( + (a, b) => new Date(b.created_at) - new Date(a.created_at) + ); + + setUnits(sortedUnits); + } catch (error) { + console.error('Error fetching units:', error); + toast.error('Failed to fetch units. Please try again.'); + } finally { + setLoading(false); + } +}; useEffect(() => { const userProfile = sessionStorage.getItem('user_profile'); @@ -59,36 +90,45 @@ const UnitMaster = () => { setCurrentUser(JSON.parse(userProfile)); } }, []); - const headers = [ - 'Unit Name', - 'Description', - 'Mapped Products', - 'Created By', - 'Created On', - 'Status', - 'Actions', - ]; + const headers = [ + 'Unit Name', + 'Description', + 'Mapped Products', + 'Created By', + 'Created On', + 'Last Updated', + 'Status', + 'Actions', +]; + const rows = useMemo(() => { return units.map((item) => { - const createdDate = item.created_at ? new Date(item.created_at).toLocaleDateString('en-GB') : '-'; - const currentUserName = currentUser?.name || ''; + const createdDate = item.created_at + ? new Date(item.created_at).toLocaleDateString('en-GB') + : '-'; const updatedDate = item.updated_at && item.updated_at !== item.created_at ? new Date(item.updated_at).toLocaleDateString('en-GB') : '-'; + const currentUserName = currentUser?.name || ''; return [ - item.uom || item.unitName, - item.uom_short_name || item.description, - Array.isArray(item.productsMapped) ? item.productsMapped.length : 0, - item.created_by_name || currentUserName || '-', - createdDate, - , - 'actions', + item.uom || item.unitName, // Unit Name + item.uom_short_name || item.description, // Description + Array.isArray(item.productsMapped) ? item.productsMapped.length : 0, // Mapped Products + item.created_by_name || currentUserName || '-', // Created By + createdDate, // Created On + updatedDate, // Last Update (moved here) + , // Status + 'actions', // Actions ]; }); -}, [units]); +}, [units, currentUser]); + const statusOptions = React.useMemo( () => [ @@ -201,9 +241,89 @@ const UnitMaster = () => { return `${day}/${month}/${year}`; }; -const handleSaveForm = async () => { +// const handleSaveForm = async () => { +// const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : []; +// if (!form.unitName || !form.description || !form.status) { +// return; +// } + +// const unitData = { +// uom: form.unitName, +// uom_short_name: form.description, +// is_active: form.status === 'Active', +// }; + +// try { +// setLoading(true); + +// if (modalMode === 'edit' && editingRow !== null) { +// const updatedUnitData = { +// ...unitData, +// updated_at: new Date().toISOString(), +// updated_by: currentUser?.id || null, +// }; + +// const updatedUnit = await updateUnit(units[editingRow].id, updatedUnitData); + +// setUnits(prev => +// prev.map((item, index) => +// index === editingRow +// ? { +// ...item, +// ...updatedUnit, +// updated_at: updatedUnitData.updated_at, +// created_at: item.created_at, +// created_by: item.created_by, +// created_by_name: item.created_by_name, +// } +// : item +// ) +// ); +// } else { +// const newUnit = await createUnit({ +// ...unitData, +// created_at: new Date().toISOString(), +// created_by: currentUser?.id || null, +// created_by_name: currentUser?.name || '', +// }); + +// setUnits(prev => [newUnit, ...prev]); +// } + +// await fetchUnits(); +// closeModal(); +// } catch (error) { +// console.error('Error saving unit:', error); +// } finally { +// setLoading(false); +// } +// }; + +const handleSave = async () => { const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : []; - if (!form.unitName || !form.description || !form.status) { + + // Validation checks + if (!form.unitName) { + toast.error('Please enter Unit Name.'); + return; + } + if (!form.description) { + toast.error('Please enter Description.'); + return; + } + if (!form.status) { + toast.error('Please select Status.'); + return; + } + + // Check for duplicate unit name + const nameExists = units.some( + unit => unit.uom?.toLowerCase().trim() === form.unitName.toLowerCase().trim() + ); + + // Only check duplicates when adding (not editing) + if (modalMode !== 'edit' && nameExists) { + toast.error('Unit name already exists!'); return; } @@ -211,6 +331,7 @@ const handleSaveForm = async () => { uom: form.unitName, uom_short_name: form.description, is_active: form.status === 'Active', + productsMapped: selectedProducts, }; try { @@ -235,10 +356,13 @@ const handleSaveForm = async () => { created_at: item.created_at, created_by: item.created_by, created_by_name: item.created_by_name, + productsMapped: selectedProducts, } : item ) ); + + toast.success('Unit updated successfully!'); } else { const newUnit = await createUnit({ ...unitData, @@ -247,13 +371,22 @@ const handleSaveForm = async () => { created_by_name: currentUser?.name || '', }); - setUnits(prev => [newUnit, ...prev]); + setUnits(prev => [ + { + ...newUnit, + productsMapped: selectedProducts, + }, + ...prev, + ]); + + toast.success('New unit added successfully!'); } await fetchUnits(); closeModal(); } catch (error) { console.error('Error saving unit:', error); + toast.error('Something went wrong while saving the unit.'); } finally { setLoading(false); } @@ -261,6 +394,10 @@ const handleSaveForm = async () => { + + + + const deletingUnit = React.useMemo(() => { if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) { return null; @@ -289,10 +426,60 @@ const handleSaveForm = async () => {

Unit Master

- + */} + + +
@@ -463,19 +657,21 @@ const handleSaveForm = async () => { > Cancel - + + +
diff --git a/ipi-survey-platform/src/pages/History/History.jsx b/ipi-survey-platform/src/pages/History/History.jsx index 6d2aa15..292f832 100644 --- a/ipi-survey-platform/src/pages/History/History.jsx +++ b/ipi-survey-platform/src/pages/History/History.jsx @@ -257,6 +257,61 @@ const History = () => { })); }; + const downloadCsv = () => { + if (!filteredRows.length) return; + + // Define CSV headers + const headers = [ + 'Submission Date & Time', + 'Year', + 'Quarter', + 'HS Code', + 'Product', + 'Status', + 'Actors', + 'Details' + ]; + + // Helper function to safely get field values + const getFieldValue = (value) => { + if (value === null || value === undefined || value === '') return '-'; + return String(value); + }; + + // Convert data to CSV rows + const csvRows = [ + headers.join(','), + ...filteredRows.map(row => + [ + getFieldValue(formatDateTime(row.created_at)), + getFieldValue(row.year), + getFieldValue(row.quarter), + getFieldValue(row.hs_code), + getFieldValue(row.product_name), + getFieldValue(formatStatus(row.status)), + (row.actors && row.actors.length ? row.actors.join(', ') : '-').replace(/"/g, '""'), + 'Quantity and cost updates' // This is a placeholder for the details column + ].map(field => `"${field}"`).join(',') + ) + ]; + + // Create CSV content + const csvContent = csvRows.join('\n'); + + // Create download link + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.setAttribute('href', url); + link.setAttribute('download', `submission-history-${new Date().toISOString().split('T')[0]}.csv`); + link.style.visibility = 'hidden'; + + // Trigger download + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + const tableRows = filteredRows.map((entry, index) => { const isExpanded = expandedRow === index; return [ @@ -511,6 +566,38 @@ const History = () => {
+
+
+

Submission History

+
+
+
+ setSearchTerm(e.target.value)} + placeholder="Search establishment" + className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none" + /> + Search +
+ +
+
{filteredRows.length === 0 && !loading ? (

No details found