This commit is contained in:
Malini 2025-12-08 14:16:50 +05:30
parent 396567cada
commit df6e81a2af
4 changed files with 110 additions and 59 deletions

View File

@ -113,13 +113,25 @@ const HeaderBar = () => {
`inline-flex items-center gap-2 pb-1 border-b-2 ${
isActive
? 'text-[#92722A] font-medium border-[#92722A]'
: 'text-[#232528] hover:text-gray-900 border-transparent'
: window.location.pathname === '/survey'
? 'text-[#232528] hover:text-gray-900 border-transparent'
: 'text-gray-400 cursor-not-allowed'
}`
}
onClick={(e) => {
if (window.location.pathname !== '/survey') {
e.preventDefault();
}
}}
>
{({ isActive }) => (
<>
<img src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc} className="h-[18px] w-[18px]" />
<img
src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc}
className={`h-[18px] w-[18px] ${
window.location.pathname !== '/survey' ? 'opacity-50' : ''
}`}
/>
<span>Survey</span>
</>
)}

View File

@ -239,6 +239,7 @@ React.useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
setError(null); // Reset error state before fetching
const data = await fetchDashboardData(selectedQuarter, selectedYear);
const mapped = (data || []).map((item) => ({
id: item.id,
@ -255,11 +256,12 @@ React.useEffect(() => {
}));
setSubmissions(mapped);
setError(null);
} catch (err) {
console.error('Failed to load submissions', err);
setError('Unable to load submissions. Please try again later.');
setSubmissions([]);
// Only show error if there are no existing submissions and it's not the initial load
if (submissions.length === 0 && !isInitialLoad) {
setError('Unable to load submissions. Please try again later.');
}
} finally {
setLoading(false);
setIsInitialLoad(false);
@ -334,11 +336,9 @@ React.useEffect(() => {
}));
setSubmissions(mapped);
setError(null);
} catch (err) {
console.error('Failed to load initial data:', err);
setError('Unable to load submissions. Please try again later.');
setSubmissions([]);
// Don't show error during initial load
} finally {
setLoading(false);
setIsInitialLoad(false);

View File

@ -111,6 +111,18 @@ const AdminUsers = () => {
}, 4000);
};
// Filter users based on search term
const filteredUsers = React.useMemo(() => {
if (!searchTerm.trim()) return users;
const searchLower = searchTerm.toLowerCase();
return users.filter(user =>
user.name.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower) ||
user.status.toLowerCase().includes(searchLower)
);
}, [users, searchTerm]);
// Fetch users with pagination
useEffect(() => {
const fetchUsers = async () => {
@ -118,7 +130,7 @@ const AdminUsers = () => {
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 res = await getAdminUser(searchTerm);
const usersData = Array.isArray(res)
? res
@ -172,10 +184,10 @@ const AdminUsers = () => {
};
fetchUsers();
}, [currentPage]); // Add currentPage as a dependency
}, [currentPage, searchTerm]); // Add currentPage and searchTerm as dependencies
const totalUsers = users.length;
const activeUsers = users.filter((user) => user.status === 'Active').length;
const totalUsers = searchTerm ? filteredUsers.length : users.length;
const activeUsers = (searchTerm ? filteredUsers : users).filter((user) => user.status === 'Active').length;
const inactiveUsers = totalUsers - activeUsers;
const summaryItems = [
@ -307,17 +319,8 @@ const AdminUsers = () => {
};
});
// Filter users based on search term
const filteredUsers = searchTerm
? users.filter(
(user) =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
)
: users;
// Apply sorting to filtered users
const sortedUsers = getSortedData(filteredUsers);
const sortedUsers = getSortedData(searchTerm ? filteredUsers : users);
const rows = sortedUsers.map((user) => [
user.name,
@ -405,12 +408,20 @@ const AdminUsers = () => {
showToast("User deleted successfully!", "success");
// Update both the main users list and filtered list
setUsers((prev) => prev.filter((u) => u.id !== selectedUser.id));
// If we're in search mode, also update the filtered list
if (searchTerm) {
setSearchTerm(''); // Clear search to show updated list
}
} catch (error) {
showToast("Failed to delete user!", "error");
console.error('Error deleting user:', error);
showToast(error?.response?.data?.message || "Failed to delete user!", "error");
} finally {
setDeletingRow(null);
setShowDeleteModal(false);
setSelectedUser(null);
}
};
@ -423,7 +434,7 @@ const AdminUsers = () => {
try {
setLoading(true);
// Fetch the latest user data from the API
// Fetch the latest user data from the API using the user's ID
const response = await getAdminUserById(user.id);
const userData = response?.data?.data || response?.data || response;
@ -437,7 +448,8 @@ const AdminUsers = () => {
name: userData.name || userData.fullName || '',
email: userData.email || '',
status: userData.is_active ? 'Active' : 'Inactive',
is_active: userData.is_active !== undefined ? userData.is_active : true
is_active: userData.is_active !== undefined ? userData.is_active : true,
raw: userData // Store the raw data for reference
};
// Set the current user and form data
@ -606,9 +618,11 @@ const AdminUsers = () => {
};
const handleDelete = (rowIndex) => {
// Calculate the actual index in the full users array based on pagination
// Get the current data source based on search
const dataSource = searchTerm ? filteredUsers : users;
// Calculate the actual index in the current data source based on pagination
const actualIndex = (currentPage - 1) * pageSize + rowIndex;
const user = users[actualIndex];
const user = dataSource[actualIndex];
if (user) {
setSelectedUser(user);
setShowDeleteModal(true);
@ -699,15 +713,23 @@ const AdminUsers = () => {
<img
src={searchIconSrc}
alt="Search"
className="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 transform"
className="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2"
/>
<input
type="text"
placeholder="Search by Names, Email"
className="h-10 w-[400px] rounded-lg border border-[#E6EAF5] pl-10 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-[#B68A35] focus:ring-opacity-50"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search by name, email, or status"
className="h-10 w-[350px] rounded-lg border border-[#E6EAF5] pl-10 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
</button>
)}
</div>
<div className="flex items-center gap-4">
<button
@ -780,7 +802,7 @@ const AdminUsers = () => {
currentPage,
onPageChange: setCurrentPage,
pageSize,
totalItems: users.length,
totalItems: searchTerm ? filteredUsers.length : users.length,
pageSizeOptions: [10, 20, 50, 100],
onPageSizeChange: (size) => {
setPageSize(size);
@ -789,9 +811,11 @@ const AdminUsers = () => {
}}
renderCell={(value, rowIndex, colIndex) => {
if (colIndex === 4) {
// Calculate the actual index in the full users array based on pagination
// Get the current data source based on search
const dataSource = searchTerm ? filteredUsers : users;
// Calculate the actual index in the current data source based on pagination
const actualIndex = (currentPage - 1) * pageSize + rowIndex;
const user = users[actualIndex];
const user = dataSource[actualIndex];
if (!user) return null;
const isActive = user.status === 'Active';
@ -800,8 +824,15 @@ const AdminUsers = () => {
<button
type="button"
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Edit"
onClick={() => handleEdit(user.raw || user)}
onClick={() => {
// Make sure we're using the full user object from the current data source
const dataSource = searchTerm ? filteredUsers : users;
const actualIndex = (currentPage - 1) * pageSize + rowIndex;
const currentUser = dataSource[actualIndex];
if (currentUser) {
handleEdit(currentUser.raw || currentUser);
}
}}
>
<img
src={editingRow?.id === user.id ? pencilActiveSrc : pencilInactiveSrc}

View File

@ -3023,30 +3023,30 @@ const handleImport = async () => {
setToastData({ message: msg, type: "error" });
return;
}
setImportLoading(true);
setImportError("");
try {
await validateFile(file);
const formData = new FormData();
formData.append("file", file);
const response = await uploadCompanyProfileCSV(formData);
const { status, message, errors } = response || {};
// Convert backend error object readable string
const formattedErrors = errors
?.map((e) => `Row ${e.row}: ${e.error}`)
.join(", ");
// FAILED
if (status === false) {
const finalError =
formattedErrors || message || "Upload failed due to server error.";
setImportError(finalError);
setToastData({
message: finalError,
@ -3054,30 +3054,43 @@ const handleImport = async () => {
});
return;
}
// SUCCESS
setToastData({
message: message || "File uploaded successfully!",
type: "success",
});
window.location.reload();
const successMessage = message || "File uploaded successfully!";
// Close the import modal
closeImportModal();
// Show success toast
// setToastData({
// message: successMessage,
// type: "success",
// });
// Show the toast before reload
showToast('success', successMessage);
// Wait a bit for user to see the success message, then reload
setTimeout(() => {
window.location.reload();
}, 2000);
} catch (error) {
console.error("Upload error:", error);
const backend = error?.response?.data;
const formattedErrors = backend?.errors
?.map((e) => `Row ${e.row}: ${e.error}`)
.join(", ");
const errorMessage =
formattedErrors ||
backend?.message ||
backend?.error ||
error?.message ||
"Upload failed. Please try again.";
setImportError(errorMessage);
setToastData({
message: errorMessage,
@ -3088,11 +3101,6 @@ const handleImport = async () => {
}
};
return (
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-[#E5E7EB]">
{toast && (