diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx index 7291ce9..2bca05f 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx @@ -77,7 +77,7 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => { if (!/[a-z]/.test(password)) return 'Must contain at least one lowercase letter'; if (!/\d/.test(password)) return 'Must contain at least one number'; if (!/[!@#$%^&*]/.test(password)) return 'Must contain at least one special character (!@#$%^&*)'; - return ''; + return ''; // Return empty string if password is valid }; const handleChange = (e) => { @@ -85,20 +85,27 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => { setFormData((prev) => ({ ...prev, [name]: value })); // Clear any existing error for this field - if (errors[name]) setErrors((prev) => ({ ...prev, [name]: '' })); + if (errors[name]) { + setErrors(prev => ({ ...prev, [name]: '' })); + } - // Real-time password validation + // Handle password field changes if (name === 'password') { setPasswordTouched(true); const error = validatePassword(value); setPasswordError(error); + + // If password is valid and matches confirm password, clear confirm password error + if (!error && formData.confirmPassword && value === formData.confirmPassword) { + setErrors(prev => ({ ...prev, confirmPassword: '' })); + } } - // Clear confirm password error when either password changes - if ((name === 'password' || name === 'confirmPassword') && formData.password && formData.confirmPassword) { - if (formData.password !== formData.confirmPassword) { + // Handle confirm password field changes + if (name === 'confirmPassword') { + if (value && formData.password !== value) { setErrors(prev => ({ ...prev, confirmPassword: 'Passwords do not match' })); - } else { + } else if (value && formData.password === value) { setErrors(prev => ({ ...prev, confirmPassword: '' })); } } @@ -211,9 +218,9 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => { placeholder="Confirm password" showToggle /> - {formData.confirmPassword && !errors.confirmPassword && ( + {/* {formData.confirmPassword && !errors.confirmPassword && (

Passwords match

- )} + )} */}
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/EditUserModal.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/EditUserModal.jsx index c0e3a9a..91c36bf 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/EditUserModal.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/EditUserModal.jsx @@ -17,10 +17,7 @@ const EditUserModal = ({ formData, setFormData, onClose, onUpdate }) => { {/* Form */}
{ - e.preventDefault(); - onUpdate(); - }} + onSubmit={onUpdate} className="flex flex-col gap-4" > {/* Name & Email in one row */} diff --git a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx index 5bf710f..f0dee25 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx @@ -104,11 +104,13 @@ const AdminUsers = () => { }, 4000); }; - // Fetch users + // Fetch users with pagination useEffect(() => { const fetchUsers = async () => { try { setLoading(true); + // Pass currentPage and pageSize to the API if it supports pagination + // If not, we'll handle pagination client-side const res = await getAdminUser(); const usersData = Array.isArray(res) @@ -117,7 +119,7 @@ const AdminUsers = () => { ? res.data : Array.isArray(res?.data?.data) ? res.data.data - : null; + : []; if (!usersData) { showToast('Failed to load users', 'error'); @@ -163,7 +165,7 @@ const AdminUsers = () => { }; fetchUsers(); - }, []); + }, [currentPage]); // Add currentPage as a dependency const totalUsers = users.length; const activeUsers = users.filter((user) => user.status === 'Active').length; @@ -294,38 +296,41 @@ const AdminUsers = () => { return; } - setEditingRow(user); - setLoading(true); - try { - const res = await getAdminUserById(user.id); - const payload = res?.data ?? res; - if (!payload) throw new Error('No user data returned from API'); + setLoading(true); + + // Fetch the latest user data from the API + const response = await getAdminUserById(user.id); + const userData = response?.data?.data || response?.data || response; + + if (!userData) { + throw new Error('Failed to fetch user data'); + } - const name = payload.name ?? payload.fullName ?? user.name ?? ''; - const email = payload.email ?? user.email ?? ''; - const is_active = - payload.is_active !== undefined ? !!payload.is_active : !!user.is_active; - - setCurrentUser({ - ...payload, - id: user.id, - name, - email, - is_active, - }); + // Map the API response to match our form data structure + const userToEdit = { + id: userData.id || userData._id || user.id, + name: userData.name || userData.fullName || '', + email: userData.email || '', + status: userData.is_active ? 'Active' : 'Inactive', + is_active: userData.is_active !== undefined ? userData.is_active : true + }; + // Set the current user and form data + setCurrentUser(userToEdit); setFormData({ - name, - email, - status: is_active ? 'Active' : 'Inactive', + name: userToEdit.name, + email: userToEdit.email, + status: userToEdit.status, }); - - setErrors({}); + + setEditingRow(userToEdit); setIsEditModalOpen(true); + setErrors({}); + } catch (error) { console.error('Error fetching user data:', error); - showToast(error?.response?.data?.message || 'Failed to load user data', 'error'); + showToast('Failed to load user data. Please try again.', 'error'); } finally { setLoading(false); } @@ -476,13 +481,21 @@ const AdminUsers = () => { }; const handleDelete = (rowIndex) => { - const user = users[rowIndex]; - setSelectedUser(user); - setShowDeleteModal(true); + // Calculate the actual index in the full users array based on pagination + const actualIndex = (currentPage - 1) * pageSize + rowIndex; + const user = users[actualIndex]; + if (user) { + setSelectedUser(user); + setShowDeleteModal(true); + } }; const handleEditSubmit = async (e) => { - e.preventDefault(); + // Prevent default form submission if event is provided + if (e && e.preventDefault) { + e.preventDefault(); + } + const formErrors = validateForm(); if (Object.keys(formErrors).length > 0) { setErrors(formErrors); @@ -512,6 +525,7 @@ const AdminUsers = () => { if (!success) throw new Error(res?.message || 'Failed to update user'); + // Update the users list with the updated user data setUsers((prev) => prev.map((u) => u.id === currentUser.id @@ -521,6 +535,13 @@ const AdminUsers = () => { email: formData.email, status: formData.status, is_active: formData.status === 'Active', + lastLogin: u.lastLogin, // Preserve the last login time + raw: { + ...(u.raw || {}), + name: formData.name, + email: formData.email, + is_active: formData.status === 'Active' + } } : u ) @@ -530,6 +551,13 @@ const AdminUsers = () => { setIsEditModalOpen(false); setEditingRow(null); setCurrentUser(null); + + // Reset form data + setFormData({ + name: '', + email: '', + status: 'Active', + }); } catch (error) { console.error('Error updating user:', error); showToast(error?.response?.data?.message || 'Failed to update user', 'error'); @@ -630,7 +658,11 @@ const AdminUsers = () => { }} renderCell={(value, rowIndex, colIndex) => { if (colIndex === 4) { - const user = users[rowIndex]; + // Calculate the actual index in the full users array based on pagination + const actualIndex = (currentPage - 1) * pageSize + rowIndex; + const user = users[actualIndex]; + if (!user) return null; + const isActive = user.status === 'Active'; return (
@@ -638,7 +670,7 @@ const AdminUsers = () => { type="button" className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer" title="Edit" - onClick={() => handleEdit(user)} +onClick={() => handleEdit(user.raw || user)} > { )} {/* Edit Modal */} - {isEditModalOpen && ( + {isEditModalOpen && currentUser && ( setIsEditModalOpen(false)} - onUpdate={handleUpdateUser} + onClose={() => { + setIsEditModalOpen(false); + setCurrentUser(null); + setFormData({ name: '', email: '', status: 'Active' }); + setErrors({}); + }} + onUpdate={handleEditSubmit} + key={currentUser.id} // Add key to force re-render when user changes /> )} diff --git a/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx index 6bc1d65..23740e2 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/IsicHsCodes.jsx @@ -535,13 +535,16 @@ const IsicHsCodes = () => { }); const handleView = async (index) => { - const selected = rowsData[index]; + // Calculate the actual index in the filtered data based on pagination + const actualIndex = (currentPage - 1) * pageSize + index; + const selected = filteredData[actualIndex]; if (!selected) return; try { setIsLoading(true); - const productId = selected.id; - const response = await productService.getProductById(productId); + // Get fresh data from the server using the ID + const response = await productService.getProductById(selected.id); + if (response && response.data) { const product = response.data; setForm({ @@ -551,8 +554,8 @@ const IsicHsCodes = () => { status: product.is_active ? 'Active' : 'Inactive', description: product.hs_description || '', createdBy: selected.createdBy, - createdOn: selected.createdOn, - updated: selected.updated + 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') : '') }); setModalMode('view'); } else { @@ -568,7 +571,9 @@ const IsicHsCodes = () => { }; const handleEdit = async (index) => { - const selected = rowsData[index]; + // Calculate the actual index in the filtered data based on pagination + const actualIndex = (currentPage - 1) * pageSize + index; + const selected = filteredData[actualIndex]; if (!selected) return; try { @@ -620,39 +625,38 @@ const IsicHsCodes = () => { const [deletingRowIndex, setDeletingRowIndex] = React.useState(null); const handleDelete = async (paginationIndex) => { - const dataIndex = (currentPage - 1) * pageSize + paginationIndex; - if (dataIndex < 0 || dataIndex >= rowsData.length) { - console.error('Invalid row index for deletion'); + const dataIndex = (currentPage - 1) * pageSize + paginationIndex; + const selected = rowsData[dataIndex]; + + if (!selected || !selected.id) { + console.error('No product selected or missing ID'); + showToast('Cannot delete: Product information is incomplete', 'error'); return; } - const productId = rowsData[dataIndex]?.id; - if (!productId) { - console.error('No product ID found for deletion'); - return; - } - - setDeletingRowIndex(dataIndex); - try { - setIsLoading(true); - const response = await productService.getProductById(productId); + setIsLoading(true); + + // Fetch the latest product data before showing delete confirmation + const response = await productService.getProductById(selected.id); + if (!response || !response.data) { throw new Error('Invalid product data received'); } + // Update the local data with the latest information const updatedRowsData = [...rowsData]; updatedRowsData[dataIndex] = { - ...updatedRowsData[dataIndex], + ...selected, ...response.data }; setRowsData(updatedRowsData); + setDeletingRowIndex(dataIndex); 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); } @@ -670,7 +674,6 @@ const IsicHsCodes = () => { }; const handleDeleteConfirm = async () => { - if (deletingRowIndex === null || deletingRowIndex < 0 || deletingRowIndex >= rowsData.length) { const errorMsg = 'Invalid row selected for deletion'; console.error(errorMsg, { deletingRowIndex, rowsDataLength: rowsData.length }); @@ -693,6 +696,16 @@ const IsicHsCodes = () => { try { await productService.deleteProduct(productId); + // Calculate the current page after deletion + const currentItemsCount = rowsData.length - 1; // After deletion + const maxPage = Math.max(1, Math.ceil(currentItemsCount / pageSize)); + + // If we're on a page that no longer exists after deletion, go to the previous page + // But only if we're not already on the first page + if (currentPage > maxPage && maxPage > 0) { + setCurrentPage(maxPage); + } + const newData = [...rowsData]; newData.splice(deletingRowIndex, 1); setRowsData(newData); @@ -701,6 +714,16 @@ const IsicHsCodes = () => { } catch (apiError) { console.error('API Error:', apiError); if (apiError.response && apiError.response.status === 204) { + // Calculate the current page after deletion for 204 response + const currentItemsCount = rowsData.length - 1; // After deletion + const maxPage = Math.max(1, Math.ceil(currentItemsCount / pageSize)); + + // If we're on a page that no longer exists after deletion, go to the previous page + // But only if we're not already on the first page + if (currentPage > maxPage && maxPage > 0) { + setCurrentPage(maxPage); + } + const newData = [...rowsData]; newData.splice(deletingRowIndex, 1); setRowsData(newData); diff --git a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx index 3adf1e5..faa1eda 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useMemo, useRef } from 'react'; import Table from '@/components/common/Table'; import { TextField } from '@/components/common/FormControls'; -import { getUnits, createUnit, updateUnit, deleteUnit, uploadUnitCSV } from '@/services/configuration/unitService'; +import { getUnits, getUnitById, createUnit, updateUnit, deleteUnit, uploadUnitCSV } from '@/services/configuration/unitService'; const downloadIconSrc = '/assets/images/DownloadSimple.svg'; const addIconSrc = '/assets/images/ic_baseline-plus.svg'; @@ -193,27 +193,108 @@ const UnitMaster = () => { setForm(createEmptyUnitForm()); setModalMode('add'); }; - const handleView = (index) => { - const selected = units[index]; - if (!selected) return; - setSelectedUnit(selected); - setModalMode('view'); + const handleView = async (index) => { + try { + setLoading(true); + + // Get the actual index in the full units array based on pagination + const actualIndex = (currentPage - 1) * pageSize + index; + const selected = filteredUnits[actualIndex]; + + if (!selected) return; + + // Fetch the latest unit data by ID using the getUnitById function + const response = await getUnitById(selected.id); + + if (!response) { + throw new Error('Failed to fetch unit data'); + } + + // The API returns data in response.data + const unitData = response.data || response; + + setSelectedUnit(unitData); + setModalMode('view'); + } catch (error) { + console.error('Error fetching unit data:', error); + setToastData({ + message: 'Failed to load unit data. Please try again.', + type: 'error' + }); + } finally { + setLoading(false); + } }; - const handleEdit = (index) => { - const selected = filteredUnits[index]; - if (!selected) return; - setEditingRow(index); - setSelectedUnit(selected); - setForm({ - uom: selected.uom || '', - description: selected.description || '', - }); - setModalMode('edit'); + const handleEdit = async (index) => { + try { + setLoading(true); + + // Get the actual index in the full units array based on pagination + const actualIndex = (currentPage - 1) * pageSize + index; + const selected = filteredUnits[actualIndex]; + + if (!selected) return; + + // Fetch the latest unit data by ID using the getUnitById function + const response = await getUnitById(selected.id); + + if (!response) { + throw new Error('Failed to fetch unit data'); + } + + // The API returns data in response.data + const unitData = response.data || response; + + setEditingRow(selected.id); + setSelectedUnit(unitData); + setForm({ + uom: unitData.uom || '', + description: unitData.description || '', + }); + setModalMode('edit'); + } catch (error) { + console.error('Error fetching unit data:', error); + setToastData({ + message: 'Failed to load unit data. Please try again.', + type: 'error' + }); + } finally { + setLoading(false); + } }; - const handleDelete = (index) => { - setDeletingRow(index); + const handleDelete = async (index) => { + try { + setLoading(true); + + // Get the actual index in the full units array based on pagination + const actualIndex = (currentPage - 1) * pageSize + index; + const selected = filteredUnits[actualIndex]; + + if (!selected) return; + + // Fetch the latest unit data by ID using the getUnitById function + const response = await getUnitById(selected.id); + + if (!response) { + throw new Error('Failed to fetch unit data'); + } + + // The API returns data in response.data + const unitData = response.data || response; + + // Set the actual index in the filtered array for deletion + setDeletingRow(actualIndex); + } catch (error) { + console.error('Error fetching unit data:', error); + setToastData({ + message: 'Failed to load unit data. Please try again.', + type: 'error' + }); + } finally { + setLoading(false); + } }; const closeModal = () => { @@ -276,8 +357,7 @@ const UnitMaster = () => { setSaveLoading(true); if (modalMode === 'edit' && editingRow !== null) { - const unitToUpdate = filteredUnits[editingRow]; - await updateUnit(unitToUpdate.id, unitData); + await updateUnit(editingRow, unitData); setToastData({ message: 'Unit updated successfully!', diff --git a/ipi-survey-platform/src/services/configuration/quarterlyWindows.js b/ipi-survey-platform/src/services/configuration/quarterlyWindows.js index f448eb7..0deb62f 100644 --- a/ipi-survey-platform/src/services/configuration/quarterlyWindows.js +++ b/ipi-survey-platform/src/services/configuration/quarterlyWindows.js @@ -10,6 +10,16 @@ export const getQuarterlyWindows = async () => { } }; +export const getQuarterlyWindowById = async (id) => { + try { + const response = await getRequest(`/quarterly_windows/${id}`); + return response.data; + } catch (error) { + console.error(`Error fetching quarterly window with ID ${id}:`, error); + throw error; + } +}; + export const createQuarterlyWindow = async (data) => { try { const response = await postRequest('/quarterly_windows', data); diff --git a/ipi-survey-platform/src/services/configuration/unitService.js b/ipi-survey-platform/src/services/configuration/unitService.js index 793e5c6..39e5053 100644 --- a/ipi-survey-platform/src/services/configuration/unitService.js +++ b/ipi-survey-platform/src/services/configuration/unitService.js @@ -13,6 +13,17 @@ export const getUnits = async () => { } }; +export const getUnitById = async (id) => { + try { + const response = await getRequest(`${UNIT_MASTER_ENDPOINT}/${id}`); + // Handle nested data structure + return response.data || response; + } catch (error) { + console.error(`Error fetching unit with ID ${id}:`, error); + throw error; + } +}; + export const createUnit = async (unitData) => { try {