bug fixed in admin profile

This commit is contained in:
Malini 2025-11-07 09:53:20 +05:30
parent b25e35d4c9
commit d0d8bd13f3
9 changed files with 449 additions and 119 deletions

View File

@ -170,15 +170,29 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
<span className="text-sm font-medium">{toast.message}</span>
{toast.type === 'rejected' && (
<button
onClick={() => {
// Add your resubmit logic here
// You can navigate to the edit page or trigger a resubmit function
}}
className="ml-auto px-3 py-1 bg-[#FEF2F2] text-[#7C2320] text-sm font-medium rounded hover:bg-[#FEF2F2] transition-colors"
>
Resubmit
<img src="/assets/images/arrow-right-bold 1.svg" alt="" className="w-4 h-4 ml-1 inline-block" />
</button>
onClick={() => {
navigate('/survey', {
state: {
submission: {
id: product.submission_id,
quarter: product.quarter,
year: product.year,
establishment_name: product.establishment_name,
permanent_factory_code: product.permanent_factory_code,
industry_code: product.industry_code,
license_number: product.license_number,
isic_code: product.isic_code,
emirate: product.emirate
},
isResubmit: true
}
});
}}
className="ml-auto px-3 py-1 bg-[#FEF2F2] text-[#7C2320] text-sm font-medium rounded hover:bg-[#FEF2F2] transition-colors"
>
Resubmit
<img src="/assets/images/arrow-right-bold 1.svg" alt="" className="w-4 h-4 ml-1 inline-block" />
</button>
)}
</div>
</div>

View File

@ -735,6 +735,7 @@ const ReviewSubmit = ({
<div className="space-y-2">
<textarea
placeholder="Enter your remarks here..."
readOnly
value={remarks}
onChange={(e) => setRemarks(e.target.value)}
className="w-full min-h-[100px] p-3 border border-#CBA344 rounded-md focus:ring-2 focus:ring-[#CBA344] focus:border-transparent"

View File

@ -0,0 +1,40 @@
import React from "react";
import { AlertTriangle } from "lucide-react";
const DeleteUserModal = ({ userName, onClose, onDelete }) => {
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/20">
<div className="bg-white rounded-lg w-[420px] p-6 shadow-lg relative">
<div className="flex items-center gap-3 mb-4">
<div className="bg-red-100 p-2 rounded-full">
<AlertTriangle className="text-red-500" size={20} />
</div>
<h3 className="text-lg font-semibold text-gray-800">Delete User</h3>
</div>
<p className="text-gray-600 mb-6">
Are you sure you want to delete{" "}
<span className="font-semibold text-gray-900">{userName}</span>?<br />
This action will permanently delete the data and cannot be undone.
</p>
<div className="flex justify-end gap-3">
<button
onClick={onClose}
className="px-5 py-2 rounded-md border border-gray-300 text-gray-700 hover:bg-gray-100"
>
Cancel
</button>
<button
onClick={onDelete}
className="px-5 py-2 rounded-md bg-[#92722A] text-white font-medium"
>
Delete
</button>
</div>
</div>
</div>
);
};
export default DeleteUserModal;

View File

@ -0,0 +1,102 @@
import React from "react";
const EditUserModal = ({ formData, setFormData, onClose, onUpdate }) => {
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/10">
<div className="bg-white rounded-lg w-[480px] shadow-lg p-6 relative">
{/* Close Button */}
<button
className="absolute top-3 right-3 text-gray-500 hover:text-gray-800"
onClick={onClose}
>
</button>
{/* Header */}
<h3 className="text-xl font-semibold text-gray-800 mb-6">Edit User</h3>
{/* Form */}
<form
onSubmit={(e) => {
e.preventDefault();
onUpdate();
}}
className="flex flex-col gap-4"
>
{/* Name & Email in one row */}
<div className="flex gap-4">
{/* Name */}
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">
Name <span className="text-red-500">*</span>
</label>
<input
type="text"
placeholder="Enter Name"
className="w-full border border-[#D9C9A3] rounded-md px-3 py-2 focus:ring-1 focus:ring-[#B68A35]"
value={formData.name}
onChange={(e) =>
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
required
/>
</div>
{/* Email */}
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">
Email <span className="text-red-500">*</span>
</label>
<input
type="email"
placeholder="Enter Email"
className="w-full border border-[#D9C9A3] rounded-md px-3 py-2 focus:ring-1 focus:ring-[#B68A35]"
value={formData.email}
onChange={(e) =>
setFormData((prev) => ({ ...prev, email: e.target.value }))
}
required
/>
</div>
</div>
{/* Status */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Status
</label>
<select
className="w-full border border-[#D9C9A3] rounded-md px-3 py-2 focus:ring-1 focus:ring-[#B68A35]"
value={formData.status}
onChange={(e) =>
setFormData((prev) => ({ ...prev, status: e.target.value }))
}
>
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
</select>
</div>
{/* Buttons */}
<div className="flex justify-end gap-3 mt-6">
<button
type="button"
className="px-5 py-2 rounded-md border border-gray-300 text-gray-700 hover:bg-gray-100"
onClick={onClose}
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2 rounded-md bg-[#B68A35] text-white font-medium hover:bg-[#9c6f28]"
>
Update
</button>
</div>
</form>
</div>
</div>
);
};
export default EditUserModal;

View File

@ -6,11 +6,14 @@ import {
getAdminUser,
getAdminUserById,
updateAdminUser,
deleteAdminUser
deleteAdminUser, // Added import
} from '@/services/configuration/adminService';
import CustomToast from '@/components/common/CustomToast';
import { X } from 'lucide-react';
import { TextField, SelectField } from '@/components/common/FormControls';
import EditUserModal from './EditUserModal';
import DeleteUserModal from './DeleteUserModal';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const exportIconSrc = '/assets/images/DownloadSimple.svg';
@ -63,11 +66,14 @@ const AdminUsers = () => {
const [currentUser, setCurrentUser] = useState(null);
const [users, setUsers] = useState([]);
const [editingRow, setEditingRow] = useState(null);
const [deletingRow, setDeletingRow] = useState(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const [deletingRow, setDeletingRow] = useState(null);
const [formData, setFormData] = useState({
name: '',
email: '',
@ -202,6 +208,54 @@ const [confirmPassword, setConfirmPassword] = useState('');
}
};
const handleUpdateUser = async () => {
try {
setLoading(true);
const updatedUser = {
name: formData.name,
email: formData.email,
is_active: formData.status === "Active", // status handled via is_active
};
const res = await updateAdminUser(currentUser.id, updatedUser);
const success =
res?.status === "success" ||
res?.code === 200 ||
res?.data?.status === "success" ||
(!res.error && res);
if (!success) throw new Error(res?.message || "Failed to update user");
showToast("User updated successfully!", "success");
setIsEditModalOpen(false);
// update UI
setUsers((prev) =>
prev.map((u) =>
u.id === currentUser.id
? {
...u,
name: formData.name,
email: formData.email,
status: formData.status,
is_active: formData.status === "Active",
}
: u
)
);
setCurrentUser(null);
} catch (error) {
console.error("Error updating user:", error);
showToast(error?.response?.data?.message || "Failed to update user", "error");
} finally {
setLoading(false);
}
};
const validateForm = () => {
const newErrors = {};
if (!formData.name || !formData.name.trim()) newErrors.name = 'Name is required';
@ -209,49 +263,70 @@ const [confirmPassword, setConfirmPassword] = useState('');
return newErrors;
};
const handleEdit = async (index) => {
const user = users[index];
if (!user || !user.id) {
showToast('Invalid user selected', 'error');
return;
}
setEditingRow(index);
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');
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,
});
setFormData({
name,
email,
status: is_active ? 'Active' : 'Inactive',
});
setErrors({});
setIsEditModalOpen(true);
} catch (error) {
console.error('Error fetching user data:', error);
showToast(error?.response?.data?.message || 'Failed to load user data', 'error');
} finally {
setLoading(false);
}
const handleDeleteClick = (user) => {
setSelectedUser(user);
setShowDeleteModal(true);
};
const handleConfirmDelete = async () => {
try {
if (!selectedUser) return;
setDeletingRow(selectedUser.id);
await deleteAdminUser(selectedUser.id); // 🔥 API call
showToast("User deleted successfully!", "success"); // use showToast()
setUsers((prev) => prev.filter((u) => u.id !== selectedUser.id));
} catch (error) {
showToast("Failed to delete user!", "error"); // same toast handler for error
} finally {
setDeletingRow(null);
setShowDeleteModal(false);
}
};
const handleEdit = async (user) => {
if (!user || !user.id) {
showToast('Invalid user selected', 'error');
return;
}
setEditingRow(user); // store which row is being edited
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');
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,
});
setFormData({
name,
email,
status: is_active ? 'Active' : 'Inactive',
});
setErrors({});
setIsEditModalOpen(true); // opens your modal
} catch (error) {
console.error('Error fetching user data:', error);
showToast(error?.response?.data?.message || 'Failed to load user data', 'error');
} finally {
setLoading(false);
}
};
const handleOpenResetModal = (user) => {
setResetUser(user);
@ -266,40 +341,12 @@ const handleCloseResetModal = () => {
};
// DELETE FUNCTIONALITY IMPLEMENTED HERE
const handleDelete = async (index) => {
const user = users[index];
if (!user || !user.id) {
showToast('Invalid user selected for delete', 'error');
return;
}
const handleDelete = (rowIndex) => {
const user = users[rowIndex];
setSelectedUser(user);
setShowDeleteModal(true);
};
const confirmDelete = window.confirm(`Are you sure you want to delete "${user.name}"?`);
if (!confirmDelete) return;
setDeletingRow(index);
setLoading(true);
try {
const res = await deleteAdminUser(user.id);
const success =
res?.status === 'success' ||
res?.code === 200 ||
res?.data?.status === 'success' ||
res?.message?.toLowerCase().includes('deleted');
if (!success) throw new Error(res?.message || 'Failed to delete user');
setUsers((prev) => prev.filter((u) => u.id !== user.id));
showToast('User deleted successfully', 'success');
} catch (error) {
console.error('Error deleting user:', error);
showToast(error?.response?.data?.message || error.message || 'Delete failed', 'error');
} finally {
setDeletingRow(null);
setLoading(false);
}
};
// END DELETE FUNCTIONALITY
const handleEditSubmit = async (e) => {
@ -376,10 +423,49 @@ const handleCloseResetModal = () => {
/>
</div>
<div className="flex items-center gap-2 sm:justify-end">
<button className="h-9 px-3 rounded-md bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 cursor-pointer">
<img src={exportIconSrc} alt="Export" className="h-4 w-4" />
<span>Export CSV</span>
</button>
<button
type="button"
onClick={() => {
if (!users.length) return;
// Define CSV headers (excluding date/time column logic)
const csvHeader = ['Name', 'Email', 'Status', 'Last Login'].join(',');
// Map user data into CSV rows
const csvRows = users.map((user) =>
[user.name, user.email, user.status, user.lastLogin]
.map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`)
.join(',')
);
// Create the CSV file content
const blob = new Blob([csvHeader + '\n' + csvRows.join('\n')], {
type: 'text/csv;charset=utf-8;',
});
// Create download link
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'admin-users.csv'); // 👈 No date in filename
document.body.appendChild(link);
link.click();
// Cleanup
document.body.removeChild(link);
URL.revokeObjectURL(url);
}}
className={`h-9 px-3 rounded-md border text-sm inline-flex items-center gap-2 ${
users.length
? 'bg-[#F7F7F7] border-[#C3C6CB] text-[#232528]'
: 'bg-[#F3F4F6] border-[#E5E7EB] text-[#9CA3AF] cursor-not-allowed'
}`}
disabled={!users.length}
>
<img src={exportIconSrc} alt="Export" className="h-4 w-4" />
<span>Export CSV</span>
</button>
<button
onClick={() => setIsAddUserModalOpen(true)}
className="h-9 px-3 rounded-md bg-[#B68A35] text-white text-sm inline-flex items-center gap-2 cursor-pointer"
@ -422,29 +508,31 @@ const handleCloseResetModal = () => {
return (
<div className="flex items-center gap-2">
<button
type="button"
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Edit"
onClick={() => handleEdit(rowIndex)}
>
<img
src={editingRow === rowIndex ? pencilActiveSrc : pencilInactiveSrc}
alt="Edit"
className="h-4 w-4"
/>
</button>
<button
type="button"
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Delete"
onClick={() => handleDelete(rowIndex)}
>
<img
src={deletingRow === rowIndex ? trashActiveSrc : trashInactiveSrc}
alt="Delete"
className="h-4 w-4"
/>
</button>
type="button"
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Edit"
onClick={() => handleEdit(user)} // pass user directly instead of rowIndex
>
<img
src={editingRow?.id === user.id ? pencilActiveSrc : pencilInactiveSrc}
alt="Edit"
className="h-4 w-4"
/>
</button>
<button
type="button"
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
title="Delete"
onClick={() => handleDelete(rowIndex)}
>
<img
src={deletingRow === rowIndex ? trashActiveSrc : trashInactiveSrc}
alt="Delete"
className="h-4 w-4"
/>
</button>
<button
type="button"
@ -523,6 +611,26 @@ const handleCloseResetModal = () => {
</div>
)}
{/* Edit Modal */}
{isEditModalOpen && (
<EditUserModal
formData={formData}
setFormData={setFormData}
onClose={() => setIsEditModalOpen(false)}
onUpdate={handleUpdateUser}
/>
)}
{/* Delete Modal */}
{showDeleteModal && (
<DeleteUserModal
userName={selectedUser?.name}
onClose={() => setShowDeleteModal(false)}
onDelete={handleConfirmDelete}
/>
)}
</div>
);

View File

@ -559,9 +559,17 @@ const CompanyProfile = () => {
<TextField
label="Postal Code"
value={form.contactPostalCode}
onChange={handleFormChange('contactPostalCode')}
onChange={(e) => {
// Only allow numeric input
if (e.target.value === '' || /^[0-9\b]+$/.test(e.target.value)) {
handleFormChange('contactPostalCode')(e);
}
}}
placeholder="Enter postal code"
width="100%"
type="tel"
inputMode="numeric"
pattern="[0-9]*"
/>
<TextField
label="PO Box"
@ -704,9 +712,17 @@ const CompanyProfile = () => {
<TextField
label="Postal Code"
value={form.corporatePostalCode}
onChange={handleFormChange('corporatePostalCode')}
onChange={(e) => {
// Only allow numeric input
if (e.target.value === '' || /^[0-9\b]+$/.test(e.target.value)) {
handleFormChange('corporatePostalCode')(e);
}
}}
placeholder="Enter postal code"
width="100%"
type="tel"
inputMode="numeric"
pattern="[0-9]*"
/>
<TextField
label="PO Box"

View File

@ -311,8 +311,10 @@ const filteredRows = useMemo(() => {
: hscodeFilter.trim();
return entry.product_name?.toLowerCase().includes(productName.toLowerCase());
})();
const matchesStatus = statusFilter === 'all' ||
entry.status?.toLowerCase() === statusFilter.toLowerCase();
return matchesSearch && matchesYear && matchesQuarter && matchesHscode;
return matchesSearch && matchesYear && matchesQuarter && matchesHscode && matchesStatus;
});
setPagination((prev) => ({
@ -322,7 +324,7 @@ const filteredRows = useMemo(() => {
}));
return filtered;
}, [auditHistory, searchTerm, yearFilter, quarterFilter, hscodeFilter]);
}, [auditHistory, searchTerm, yearFilter, quarterFilter, hscodeFilter,statusFilter]);

View File

@ -42,12 +42,40 @@ export const getAdminUserById = async (id) => {
}
};
// export const deleteAdminUser = async (id) => {
// try {
// const response = await deleteRequest(`${admin}/${id}`);
// return response.data;
// } catch (error) {
// console.error('Error deleting admin user:', error);
// throw error;
// }
// };
export const deleteAdminUser = async (id) => {
try {
if (!id) throw new Error("Invalid user ID");
const response = await deleteRequest(`${admin}/${id}`);
return response.data;
// Log for debugging
// Safely check success from API
const resData = response?.data || response;
const success =
resData?.status === "success" ||
resData?.success === true ||
resData?.code === 200 ||
resData?.message?.toLowerCase()?.includes("deleted");
if (!success) {
throw new Error("Delete API did not return success");
}
return resData;
} catch (error) {
console.error('Error deleting admin user:', error);
console.error("Error deleting admin user:", error);
throw error;
}
};

View File

@ -1,5 +1,6 @@
import { getRequest, postRequest } from '@/services/api/CommonService';
import resolveEstablishmentId from '@/services/utils/establishment';
import { putRequest } from '../api/CommonService';
const endpoint = '/submissions';
const QUARTER_PERIODS_ENDPOINT = '/getQuarterPeriods';
@ -32,6 +33,23 @@ export const submitSurvey = async (payload = {}, config = {}) => {
return response.data;
};
export const resubmitSurvey = async (submissionId, payload = {}, config = {}) => {
if (!submissionId) {
throw new Error('Submission ID is required for resubmission');
}
const establishmentId = resolveEstablishmentId(payload.establishment_id);
const requestBody = {
...payload,
...(establishmentId !== undefined ? { establishment_id: establishmentId } : {}),
};
const url = `${endpoint}/${submissionId}`;
// Use putRequest instead of postRequest with method override
const response = await putRequest(url, requestBody, config);
return response.data;
};
export const fetchSubmissionHistory = async (establishmentId, config = {}) => {
const resolvedId = resolveEstablishmentId(establishmentId);
if (resolvedId === undefined) {
@ -126,6 +144,7 @@ export const getSubmissionAuditHistory = async (establishmentId, params = {}) =>
export default {
getSubmissions,
submitSurvey,
resubmitSurvey,
fetchSubmissionHistory,
fetchSubmissionDetail,
getQuarterPeriods,