From ebd0e4c4f5b8ddd846e296405504f591f7d865b7 Mon Sep 17 00:00:00 2001 From: Malini Date: Mon, 10 Nov 2025 12:12:45 +0530 Subject: [PATCH] bug fixed --- .../AdminUsers/AddAdminUsers.jsx | 39 ++-- .../AdminUsers/ListAdminUsers.jsx | 217 +++++++++--------- .../AdminUsers/ResetPasswordModal.jsx | 154 +++++-------- .../Admin/configuration/QuarterlyWindows.jsx | 139 +++++------ .../pages/Admin/configuration/UnitMaster.jsx | 140 +++++------ 5 files changed, 340 insertions(+), 349 deletions(-) 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 57e8d44..8a09366 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/AddAdminUsers.jsx @@ -30,20 +30,31 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => { setToastData(null); }, 4000); }; - const validateForm = () => { - const newErrors = {}; - if (!formData.name.trim()) newErrors.name = 'Name is required'; - if (!formData.email.trim()) newErrors.email = 'Email is required'; - else if (!/^\S+@\S+\.\S+$/.test(formData.email)) - newErrors.email = 'Please enter a valid email'; - if (!formData.status) newErrors.status = 'Status is required'; - if (!formData.password) newErrors.password = 'Password is required'; - else if (formData.password.length < 12) - newErrors.password = 'Password must be at least 12 characters'; - if (formData.password !== formData.confirmPassword) - newErrors.confirmPassword = 'Passwords do not match'; - return newErrors; - }; + const validateForm = () => { + const newErrors = {}; + + if (!formData.name.trim()) newErrors.name = 'Name is required'; + + if (!formData.email.trim()) newErrors.email = 'Email is required'; + else if (!/^\S+@\S+\.\S+$/.test(formData.email)) + newErrors.email = 'Please enter a valid email'; + + if (!formData.status) newErrors.status = 'Status is required'; + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 8) { + newErrors.password = 'Password must be at least 8 characters'; + } else if (formData.password.length > 12) { + newErrors.password = 'Password cannot exceed 12 characters'; + } + + if (formData.password !== formData.confirmPassword) + newErrors.confirmPassword = 'Passwords do not match'; + + return newErrors; +}; + useEffect(() => { if (isOpen) { 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 8017102..52c2e48 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/AdminUsers/ListAdminUsers.jsx @@ -15,7 +15,7 @@ import { TextField, SelectField } from '@/components/common/FormControls'; import EditUserModal from './EditUserModal'; import DeleteUserModal from './DeleteUserModal'; import ResetPasswordModal from './ResetPasswordModal'; - + const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg'; const exportIconSrc = '/assets/images/DownloadSimple.svg'; const addIconSrc = '/assets/images/ic_baseline-plus.svg'; @@ -29,7 +29,7 @@ const inactiveUserIconSrc = '/assets/images/inactive-user.svg'; const resetPasswordIconSrc = '/assets/images/hugeicons_reset-password.svg'; const resetPasswordInactiveIconSrc = '/assets/images/hugeicons_reset-password-inactive.svg'; - + const SummaryCard = ({ label, count, icon, iconBg = '#FDF7EB' }) => (
@@ -44,7 +44,7 @@ const SummaryCard = ({ label, count, icon, iconBg = '#FDF7EB' }) => ( {count}
); - + const StatusBadge = ({ status }) => { const map = { Active: 'bg-green-50 text-green-700 ring-1 ring-green-200', @@ -60,7 +60,7 @@ const StatusBadge = ({ status }) => { ); }; - + const AdminUsers = () => { const [isAddUserModalOpen, setIsAddUserModalOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); @@ -71,7 +71,7 @@ const AdminUsers = () => { 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); @@ -83,7 +83,7 @@ const AdminUsers = () => { const [errors, setErrors] = useState({}); const [toastData, setToastData] = useState(null); const navigate = useNavigate(); - + // Reset password modal state const [isResetModalOpen, setIsResetModalOpen] = useState(false); const [resetUser, setResetUser] = useState(null); @@ -92,7 +92,7 @@ const AdminUsers = () => { const [confirmPassword, setConfirmPassword] = useState(''); const [resettingPassword, setResettingPassword] = useState(false); const [resetErrors, setResetErrors] = useState({}); - + // Helper: show toast const showToast = (message, type = 'success') => { setToastData({ message, type }); @@ -103,14 +103,14 @@ const AdminUsers = () => { setToastData(null); }, 4000); }; - + // Fetch users useEffect(() => { const fetchUsers = async () => { try { setLoading(true); const res = await getAdminUser(); - + const usersData = Array.isArray(res) ? res : Array.isArray(res?.data) @@ -118,13 +118,13 @@ const AdminUsers = () => { : Array.isArray(res?.data?.data) ? res.data.data : null; - + if (!usersData) { showToast('Failed to load users', 'error'); setUsers([]); return; } - + const mappedUsers = usersData.map((user) => { const id = user.id ?? user._id ?? user.user_id ?? null; const name = user.name ?? user.fullName ?? 'N/A'; @@ -151,7 +151,7 @@ const AdminUsers = () => { raw: user, }; }); - + setUsers(mappedUsers); } catch (error) { console.error('Error fetching admin users:', error); @@ -161,14 +161,14 @@ const AdminUsers = () => { setLoading(false); } }; - + fetchUsers(); }, []); - + const totalUsers = users.length; const activeUsers = users.filter((user) => user.status === 'Active').length; const inactiveUsers = totalUsers - activeUsers; - + const summaryItems = [ { label: 'Total Users', @@ -189,10 +189,10 @@ const AdminUsers = () => { iconBg: '#F5F5F7', }, ]; - + const headers = ['Name', 'Email', 'Last Login', 'Status', 'Actions']; const columnWidths = [100, 130, 150, 100, 130]; - + const rows = users.map((user) => [ user.name, user.email, @@ -200,7 +200,7 @@ const AdminUsers = () => { , 'actions', ]); - + const handleFormChange = (e) => { if (!e) return; if (e.target && e.target.name !== undefined) { @@ -212,30 +212,30 @@ const AdminUsers = () => { if (errors[e.name]) setErrors((prev) => ({ ...prev, [e.name]: '' })); } }; - + const handleUpdateUser = async () => { try { setLoading(true); - + const updatedUser = { name: formData.name, email: formData.email, is_active: formData.status === "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); - + setUsers((prev) => prev.map((u) => u.id === currentUser.id @@ -249,7 +249,7 @@ const AdminUsers = () => { : u ) ); - + setCurrentUser(null); } catch (error) { console.error("Error updating user:", error); @@ -258,27 +258,27 @@ const AdminUsers = () => { setLoading(false); } }; - + const validateForm = () => { const newErrors = {}; if (!formData.name || !formData.name.trim()) newErrors.name = 'Name is required'; if (!formData.status) newErrors.status = 'Status is required'; return newErrors; }; - + const handleDeleteClick = (user) => { setSelectedUser(user); setShowDeleteModal(true); }; - + const handleConfirmDelete = async () => { try { if (!selectedUser) return; setDeletingRow(selectedUser.id); await deleteAdminUser(selectedUser.id); - + showToast("User deleted successfully!", "success"); - + setUsers((prev) => prev.filter((u) => u.id !== selectedUser.id)); } catch (error) { showToast("Failed to delete user!", "error"); @@ -287,26 +287,26 @@ const AdminUsers = () => { setShowDeleteModal(false); } }; - + const handleEdit = async (user) => { if (!user || !user.id) { showToast('Invalid user selected', 'error'); 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'); - + 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, @@ -314,13 +314,13 @@ const AdminUsers = () => { email, is_active, }); - + setFormData({ name, email, status: is_active ? 'Active' : 'Inactive', }); - + setErrors({}); setIsEditModalOpen(true); } catch (error) { @@ -330,7 +330,7 @@ const AdminUsers = () => { setLoading(false); } }; - + const handleOpenResetModal = (user) => { setResetUser(user); setOldPassword(''); @@ -339,7 +339,7 @@ const AdminUsers = () => { setResetErrors({}); setIsResetModalOpen(true); }; - + const handleCloseResetModal = () => { setResetUser(null); setOldPassword(''); @@ -349,60 +349,67 @@ const AdminUsers = () => { setIsResetModalOpen(false); setResettingPassword(false); }; - + // Validate reset password form - Updated to allow 8 to 12 characters - const validateResetForm = () => { - const newErrors = {}; - - if (!oldPassword.trim()) { - newErrors.oldPassword = 'Old password is required'; - } - - if (!newPassword.trim()) { - newErrors.newPassword = 'New password is required'; - } else if (newPassword.length < 8) { - newErrors.newPassword = 'Password must be at least 8 characters'; - } - - if (!confirmPassword.trim()) { - newErrors.confirmPassword = 'Please confirm your password'; - } else if (newPassword !== confirmPassword) { - newErrors.confirmPassword = 'Passwords do not match'; - } - - return newErrors; - }; - + const validateResetForm = () => { + const newErrors = {}; + + // Old password validation + if (!oldPassword?.trim()) { + newErrors.oldPassword = 'Old password is required'; + } + + // New password validation + if (!newPassword?.trim()) { + newErrors.newPassword = 'New password is required'; + } else if (newPassword.length < 8) { + newErrors.newPassword = 'Password must be at least 8 characters long'; + } + + // Confirm password validation + if (!confirmPassword?.trim()) { + newErrors.confirmPassword = 'Please confirm your password'; + } else if (newPassword !== confirmPassword) { + newErrors.confirmPassword = 'Passwords do not match'; + } + + return newErrors; +}; + + + + + // Reset Password API Integration const handleResetPassword = async () => { if (!resetUser || !resetUser.id) { showToast('No user selected for password reset', 'error'); return; } - + // Validate form const formErrors = validateResetForm(); if (Object.keys(formErrors).length > 0) { setResetErrors(formErrors); return; } - + try { setResettingPassword(true); - + // Prepare data according to API specification const passwordData = { old_password: oldPassword, new_password: newPassword, confirm_password: confirmPassword }; - + console.log('🔄 Initiating admin password reset for user:', resetUser.id); - + const res = await changeAdminUserPassword(resetUser.id, passwordData); - + console.log('✅ Admin password reset response:', res); - + // Check for success based on common response patterns const success = res?.status === "success" || @@ -413,26 +420,26 @@ const AdminUsers = () => { res?.message?.toLowerCase().includes("updated") || res?.message?.toLowerCase().includes("changed") || (!res.error && res); - + if (!success) { throw new Error(res?.message || res?.data?.message || "Failed to reset password"); } - + showToast('Admin password reset successfully!', 'success'); handleCloseResetModal(); - + } catch (error) { console.error('❌ Error resetting admin password:', error); - + // Handle specific error cases let errorMessage = 'Failed to reset password'; - + if (error.message) { errorMessage = error.message; } - + // Check for common admin password change errors - if (errorMessage.toLowerCase().includes('old password') || + if (errorMessage.toLowerCase().includes('old password') || errorMessage.toLowerCase().includes('current password') || errorMessage.toLowerCase().includes('incorrect password')) { setResetErrors(prev => ({ @@ -446,14 +453,14 @@ const AdminUsers = () => { confirmPassword: 'New passwords do not match' })); showToast('New passwords do not match', 'error'); - } else if (errorMessage.toLowerCase().includes('same') || + } else if (errorMessage.toLowerCase().includes('same') || errorMessage.toLowerCase().includes('previous')) { setResetErrors(prev => ({ ...prev, newPassword: 'New password cannot be the same as current password' })); showToast('New password cannot be the same as current password', 'error'); - } else if (errorMessage.toLowerCase().includes('weak') || + } else if (errorMessage.toLowerCase().includes('weak') || errorMessage.toLowerCase().includes('strength')) { setResetErrors(prev => ({ ...prev, @@ -469,13 +476,13 @@ const AdminUsers = () => { setResettingPassword(false); } }; - + const handleDelete = (rowIndex) => { const user = users[rowIndex]; setSelectedUser(user); setShowDeleteModal(true); }; - + const handleEditSubmit = async (e) => { e.preventDefault(); const formErrors = validateForm(); @@ -484,12 +491,12 @@ const AdminUsers = () => { showToast('Please fix the form errors', 'error'); return; } - + if (!currentUser || !currentUser.id) { showToast('No user selected for update', 'error'); return; } - + setSaving(true); try { const payload = { @@ -497,16 +504,16 @@ const AdminUsers = () => { email: formData.email, is_active: formData.status === 'Active', }; - + const res = await updateAdminUser(currentUser.id, payload); const success = res?.status === 'success' || res?.status === 200 || res?.data?.status === 'success' || (!res.error && res); - + if (!success) throw new Error(res?.message || 'Failed to update user'); - + setUsers((prev) => prev.map((u) => u.id === currentUser.id @@ -520,7 +527,7 @@ const AdminUsers = () => { : u ) ); - + showToast('User updated successfully', 'success'); setIsEditModalOpen(false); setEditingRow(null); @@ -532,7 +539,7 @@ const AdminUsers = () => { setSaving(false); } }; - + const tableToolbar = (

Admin Users

@@ -554,26 +561,26 @@ const AdminUsers = () => { type="button" onClick={() => { if (!users.length) return; - + const csvHeader = ['Name', 'Email', 'Status', 'Last Login'].join(','); - + const csvRows = users.map((user) => [user.name, user.email, user.status, user.lastLogin] .map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`) .join(',') ); - + const blob = new Blob([csvHeader + '\n' + csvRows.join('\n')], { type: 'text/csv;charset=utf-8;', }); - + const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', 'admin-users.csv'); document.body.appendChild(link); link.click(); - + document.body.removeChild(link); URL.revokeObjectURL(url); }} @@ -587,7 +594,7 @@ const AdminUsers = () => { Export Export CSV - +
); - + return (
{/* Summary cards */} @@ -608,7 +615,7 @@ const AdminUsers = () => { ))}
- + {/* Table */} { className="h-4 w-4" /> - + - + - - {/* Form */} + + {/* Form Fields */}
- {/* Old Password Field */} + {/* Old Password */}
{errors.oldPassword && ( -

- - - - {errors.oldPassword} -

+

{errors.oldPassword}

)}
- - {/* New Password Field */} + + {/* New Password */}
{errors.newPassword && ( -

- - - - {errors.newPassword} -

+

{errors.newPassword}

)} - - {/* Confirm Password Field */} + + {/* Confirm Password */}
{errors.confirmPassword && ( -

- - - - {errors.confirmPassword} -

+

{errors.confirmPassword}

)} - - {/* Note Section */} + + {/* Note */}
- - + +

- Note: Password must be at least 6 characters long. + Note: Password must be at least 8 characters long.

- - {/* Action Buttons */} + + {/* Buttons */}
); }; - -export default ResetPasswordModal; \ No newline at end of file + +export default ResetPasswordModal; diff --git a/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx b/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx index 581dcc7..ade9977 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/QuarterlyWindows.jsx @@ -3,7 +3,7 @@ import Table from '@/components/common/Table'; import { TextField, SelectField, DateField } from '@/components/common/FormControls'; import StatusBadge from '@/components/common/StatusBadge'; import { createQuarterlyWindow, updateQuarterlyWindow, getQuarterlyWindows } from '@/services/configuration/quarterlyWindows'; - + const downloadIconSrc = '/assets/images/DownloadSimple.svg'; const addIconSrc = '/assets/images/ic_baseline-plus.svg'; const pencilActiveSrc = '/assets/images/PencilSimple.svg'; @@ -11,17 +11,17 @@ const pencilInactiveSrc = '/assets/images/pencilsimple-inactive.svg'; const caretDownActiveSrc = '/assets/images/caretdown-active.svg'; const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg'; const caretDownSrc = '/assets/images/CaretDown-black.svg'; - + // Custom Toast Component const CustomToast = ({ message, type, onClose }) => { React.useEffect(() => { const timer = setTimeout(() => { onClose(); }, 3000); - + return () => clearTimeout(timer); }, [onClose]); - + const getToastStyles = () => { switch (type) { case 'success': @@ -34,7 +34,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'bg-blue-50 border-blue-200 text-blue-800'; } }; - + const getIcon = () => { switch (type) { case 'success': @@ -47,7 +47,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'â„šī¸'; } }; - + return (
{getIcon()} @@ -61,7 +61,7 @@ const CustomToast = ({ message, type, onClose }) => {
); }; - + const createEmptyQuarterForm = () => ({ survey_name: '', establishment: '-', // Default to hyphen @@ -73,7 +73,7 @@ const createEmptyQuarterForm = () => ({ submissionCount: '', status: 'Active', // Default to Active }); - + const QuarterlyWindows = () => { const [quarterData, setQuarterData] = React.useState([]); const [editingRow, setEditingRow] = React.useState(null); @@ -89,21 +89,21 @@ const QuarterlyWindows = () => { const [isLoading, setIsLoading] = React.useState(false); const [validationErrors, setValidationErrors] = React.useState({}); const [toastData, setToastData] = React.useState(null); - + // Format date to yyyy-MM-dd for input fields const formatDateForInput = (dateString) => { if (!dateString) return ''; const date = new Date(dateString); return date.toISOString().split('T')[0]; }; - + // Format date for display const formatDateForDisplay = (dateString) => { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('en-GB'); }; - + // Fetch quarterly windows data on component mount React.useEffect(() => { const fetchQuarterlyWindows = async () => { @@ -137,10 +137,10 @@ const QuarterlyWindows = () => { setIsLoading(false); } }; - + fetchQuarterlyWindows(); }, []); - + // Table headers with left alignment and no wrapping const headers = [ { name: 'Survey Name', className: 'whitespace-nowrap text-left' }, @@ -155,7 +155,7 @@ const QuarterlyWindows = () => { { name: 'Submission Count', className: 'whitespace-nowrap text-left' }, { name: 'Actions', className: 'whitespace-nowrap text-left' }, ]; - + const columnwidth = [ 220, // Survey Name 100, // Year @@ -169,16 +169,16 @@ const QuarterlyWindows = () => { 140, // Submission Count 120 // Actions ]; - + const filteredRows = React.useMemo(() => { const term = searchTerm.trim().toLowerCase(); return quarterData.filter((item) => { // Apply year filter if (yearFilter !== 'all' && item.year !== yearFilter) return false; - + // Apply quarter filter if (quarterFilter !== 'all' && item.quarter !== quarterFilter) return false; - + // Apply search term if (!term) return true; const haystack = [ @@ -200,21 +200,21 @@ const QuarterlyWindows = () => { return haystack.includes(term); }); }, [quarterData, searchTerm, yearFilter, quarterFilter]); - + const Tooltip = ({ children, text }) => (
{children}
- info
); - + const rows = filteredRows.map((item) => [ item.survey_name || '-', item.year, @@ -234,24 +234,24 @@ const QuarterlyWindows = () => { item.submissionCount, 'actions', ]); - + // Generate year options from 2022 to current year, plus any additional years in data const yearOptions = React.useMemo(() => { const currentYear = new Date().getFullYear(); const years = new Set(['all']); - + // Add years from 2022 to current year for (let y = 2022; y <= currentYear; y++) { years.add(y.toString()); } - + // Add any additional years that might be in the data quarterData.forEach(item => { if (item.year && !years.has(item.year)) { years.add(item.year); } }); - + // Sort years in descending order (newest first) return Array.from(years).sort((a, b) => { if (a === 'all') return -1; @@ -259,9 +259,9 @@ const QuarterlyWindows = () => { return parseInt(b) - parseInt(a); }); }, [quarterData]); - + const quarterOptions = ['all', 'Q1', 'Q2', 'Q3', 'Q4']; - + const openAddModal = () => { setForm(createEmptyQuarterForm()); setOriginalForm(null); @@ -269,12 +269,12 @@ const QuarterlyWindows = () => { setModalMode('add'); setValidationErrors({}); }; - + React.useEffect(() => { const maxPage = Math.max(1, Math.ceil(filteredRows.length / pageSize)); setCurrentPage((prev) => Math.min(prev, maxPage)); }, [filteredRows.length, pageSize]); - + const openEditModal = (index) => { const selected = quarterData[index]; // Ensure dates are in the correct format for the form inputs @@ -289,13 +289,13 @@ const QuarterlyWindows = () => { setModalMode('edit'); setValidationErrors({}); }; - + const handleCloseModal = () => { setModalMode(null); setEditingRow(null); setValidationErrors({}); }; - + const handleReset = () => { if (modalMode === 'edit' && editingRow !== null) { if (originalForm) { @@ -311,10 +311,10 @@ const QuarterlyWindows = () => { } setValidationErrors({}); }; - + const validateForm = () => { const errors = {}; - + if (!form.survey_name.trim()) { errors.survey_name = 'Survey Name is required'; } @@ -330,7 +330,7 @@ const QuarterlyWindows = () => { if (!form.endDate) { errors.endDate = 'Close Date is required'; } - + // Date validation if (form.startDate && form.endDate) { const startDate = new Date(form.startDate); @@ -339,14 +339,14 @@ const QuarterlyWindows = () => { errors.endDate = 'Close Date must be after Open Date'; } } - + setValidationErrors(errors); return Object.keys(errors).length === 0; }; - + const handleFormChange = (field) => (value) => { setForm(prev => ({ ...prev, [field]: value })); - + // Clear validation error when user starts typing if (validationErrors[field]) { setValidationErrors(prev => ({ @@ -355,16 +355,16 @@ const QuarterlyWindows = () => { })); } }; - + const handleSave = async () => { try { if (!validateForm()) { return; } - + const startDate = new Date(form.startDate); const endDate = new Date(form.endDate); - + const formattedData = { survey_name: form.survey_name || '', year: form.year ? parseInt(form.year, 10) : 0, @@ -378,7 +378,7 @@ const QuarterlyWindows = () => { is_active: form.status === 'Active', establishment: form.establishment || '-', }; - + if (modalMode === 'add') { const response = await createQuarterlyWindow(formattedData); if (response.status === 'success') { @@ -392,7 +392,7 @@ const QuarterlyWindows = () => { status: formattedData.is_active ? 'Active' : 'Inactive' }; setQuarterData((prev) => [...prev, newItem]); - + // Show success toast for add setToastData({ message: 'Quarterly survey created successfully!', @@ -409,9 +409,9 @@ const QuarterlyWindows = () => { } const response = await updateQuarterlyWindow(itemId, formattedData); if (response.status === 'success') { - setQuarterData((prev) => - prev.map((item, idx) => - idx === editingRow + setQuarterData((prev) => + prev.map((item, idx) => + idx === editingRow ? { ...item, ...formattedData, @@ -419,11 +419,11 @@ const QuarterlyWindows = () => { endDate: form.endDate, gracePeriod: formattedData.grace_periods_days ? `${formattedData.grace_periods_days} days` : '0 days', status: formattedData.is_active ? 'Active' : 'Inactive' - } + } : item ) ); - + // Show success toast for edit setToastData({ message: 'Quarterly survey updated successfully!', @@ -438,7 +438,7 @@ const QuarterlyWindows = () => { console.error('Error saving quarterly window:', error); } }; - + return (
{/* Custom Toast Notification */} @@ -453,7 +453,7 @@ const QuarterlyWindows = () => {
)} - +

Manage Surveys ({filteredRows.length})

@@ -560,7 +560,7 @@ const QuarterlyWindows = () => {
- +
{ }} /> - + {modalMode && (
@@ -651,7 +651,7 @@ const QuarterlyWindows = () => {
- +
{ required onChange={(e) => handleFormChange('survey_name')(e.target.value)} placeholder="Enter survey name" - // error={validationErrors.survey_name} + error={validationErrors.survey_name} + className={validationErrors.survey_name ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {validationErrors.survey_name && ( -

{validationErrors.survey_name}

+

)}
- +
{ onChange={(e) => handleFormChange('quarter')(e.target.value)} options={['Q1', 'Q2', 'Q3', 'Q4'].map((q) => ({ label: q, value: q }))} placeholder="Select Quarter" - // error={validationErrors.quarter} + error={validationErrors.quarter} + className={validationErrors.quarter ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {validationErrors.quarter && ( -

{validationErrors.quarter}

+

)}
- +
{ })} required placeholder="Select Year" - // error={validationErrors.year} + error={validationErrors.year} + className={validationErrors.year ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {validationErrors.year && ( -

{validationErrors.year}

+

)}
- +
{ onChange={(e) => handleFormChange('startDate')(e.target.value)} placeholder="Select Date" error={validationErrors.startDate} + className={validationErrors.startDate ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {validationErrors.startDate && ( -

{validationErrors.startDate}

+

{validationErrors.startDate}

)}
- +
{ onChange={(e) => handleFormChange('endDate')(e.target.value)} placeholder="Select Date" error={validationErrors.endDate} + className={validationErrors.endDate ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {validationErrors.endDate && (

{validationErrors.endDate}

@@ -762,7 +767,7 @@ const QuarterlyWindows = () => { placeholder="Select Grace Period" />
- +
); }; - + export default QuarterlyWindows; \ No newline at end of file diff --git a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx index 9390af6..80edb2a 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx @@ -3,7 +3,7 @@ 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'; - + const downloadIconSrc = '/assets/images/DownloadSimple.svg'; const addIconSrc = '/assets/images/ic_baseline-plus.svg'; const pencilActiveSrc = '/assets/images/PencilSimple.svg'; @@ -14,17 +14,17 @@ const deleteIconSrc = '/assets/images/delete.svg'; const caretDownIconSrc = '/assets/images/caretdown-active.svg'; const rectangleIconSrc = '/assets/images/Rectangle.svg'; const checkboxIconSrc = '/assets/images/checkbox.svg'; - + // Custom Toast Component const CustomToast = ({ message, type, onClose }) => { useEffect(() => { const timer = setTimeout(() => { onClose(); }, 3000); - + return () => clearTimeout(timer); }, [onClose]); - + const getToastStyles = () => { switch (type) { case 'success': @@ -37,7 +37,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'bg-blue-50 border-blue-200 text-blue-800'; } }; - + const getIcon = () => { switch (type) { case 'success': @@ -50,7 +50,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'â„šī¸'; } }; - + return (
{getIcon()} @@ -64,14 +64,14 @@ const CustomToast = ({ message, type, onClose }) => {
); }; - + const createEmptyUnitForm = () => ({ unitName: '', description: '', productsMapped: [], status: '', }); - + const UnitMaster = () => { const [units, setUnits] = useState([]); const [loading, setLoading] = useState(true); @@ -87,23 +87,23 @@ const UnitMaster = () => { const [currentUser, setCurrentUser] = useState(null); const [toastData, setToastData] = useState(null); const [searchTerm, setSearchTerm] = React.useState(''); - + // Fetch units on component mount useEffect(() => { fetchUnits(); }, []); - + const fetchUnits = async () => { try { setLoading(true); const response = await getUnits(); const unitsData = response.data || []; - + // ✅ Sort by latest created_at date (newest first) const sortedUnits = [...unitsData].sort( (a, b) => new Date(b.created_at) - new Date(a.created_at) ); - + setUnits(sortedUnits); } catch (error) { console.error('Error fetching units:', error); @@ -111,24 +111,24 @@ const UnitMaster = () => { setLoading(false); } }; - + const filteredUnits = React.useMemo(() => { if (!searchTerm) return units; const term = searchTerm.toLowerCase(); - return units.filter(unit => + return units.filter(unit => (unit.uom && unit.uom.toLowerCase().includes(term)) || (unit.uom_short_name && unit.uom_short_name.toLowerCase().includes(term)) || (unit.description && unit.description.toLowerCase().includes(term)) ); }, [units, searchTerm]); - + useEffect(() => { const userProfile = sessionStorage.getItem('user_profile'); if (userProfile) { setCurrentUser(JSON.parse(userProfile)); } }, []); - + const headers = [ 'Unit Name', 'Description', @@ -138,7 +138,7 @@ const UnitMaster = () => { 'Last Updated', 'Actions', ]; - + const rows = useMemo(() => { return units.map((item) => { const createdDate = item.created_at @@ -149,12 +149,12 @@ const UnitMaster = () => { ? new Date(item.updated_at).toLocaleDateString('en-GB') : '-'; const currentUserName = currentUser?.name || ''; - + return [ item.uom || item.unitName, item.uom_short_name || item.description, - item.mapped_products_count !== undefined - ? item.mapped_products_count + item.mapped_products_count !== undefined + ? item.mapped_products_count : (Array.isArray(item.productsMapped) ? item.productsMapped.length : 0), item.created_by_name || currentUserName || '-', createdDate, @@ -163,7 +163,7 @@ const UnitMaster = () => { ]; }); }, [units, currentUser, filteredUnits]); - + const statusOptions = React.useMemo( () => [ { label: 'Active', value: 'Active' }, @@ -171,26 +171,26 @@ const UnitMaster = () => { ], [] ); - + const filteredRows = units; - + const validateForm = () => { const errors = {}; - + // Unit Name validation if (!form.unitName.trim()) { errors.unitName = 'Unit Name is required'; } - + // Description validation - red color validation if (!form.description.trim()) { errors.description = 'Please enter a description.'; } - + setFormErrors(errors); return Object.keys(errors).length === 0; }; - + const openAddModal = () => { setEditingRow(null); setForm(createEmptyUnitForm()); @@ -198,7 +198,7 @@ const UnitMaster = () => { setModalMode('add'); setIsProductDropdownOpen(false); }; - + const handleEdit = (index) => { const selected = units[index]; if (!selected) return; @@ -213,11 +213,11 @@ const UnitMaster = () => { setModalMode('edit'); setIsProductDropdownOpen(false); }; - + const handleDelete = (index) => { setDeletingRow(index); }; - + const closeModal = () => { setModalMode(null); setForm(createEmptyUnitForm()); @@ -225,11 +225,11 @@ const UnitMaster = () => { setEditingRow(null); setIsProductDropdownOpen(false); }; - + const handleFormChange = (field) => (event) => { const value = event?.target?.value ?? event; setForm((prev) => ({ ...prev, [field]: value })); - + // Clear error when user starts typing if (formErrors[field]) { setFormErrors(prev => ({ @@ -238,7 +238,7 @@ const UnitMaster = () => { })); } }; - + const handleProductToggle = (value) => { setForm((prev) => { const current = Array.isArray(prev.productsMapped) ? prev.productsMapped : []; @@ -249,7 +249,7 @@ const UnitMaster = () => { }; }); }; - + React.useEffect(() => { const handleClickOutside = (event) => { if (!productDropdownRef.current) return; @@ -257,30 +257,30 @@ const UnitMaster = () => { setIsProductDropdownOpen(false); } }; - + if (isProductDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); } - + return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isProductDropdownOpen]); - + const handleSave = async () => { // Validate form before saving if (!validateForm()) { return; } - + const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : []; console.log(selectedProducts,"selectedProducts"); - + // ✅ 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) { setToastData({ @@ -289,7 +289,7 @@ const UnitMaster = () => { }); return; } - + const unitData = { uom: form.unitName, uom_short_name: form.description, @@ -297,19 +297,19 @@ const UnitMaster = () => { productsMapped: selectedProducts.length, }; console.log("unitData",unitData) - + try { setLoading(true); - + if (modalMode === 'edit' && editingRow !== null) { const updatedUnitData = { ...unitData, updated_at: new Date().toISOString(), updated_by: currentUser?.id || null, }; - + await updateUnit(units[editingRow].id, updatedUnitData); - + // ✅ Show success message for edit setToastData({ message: 'Unit updated successfully!', @@ -323,14 +323,14 @@ const UnitMaster = () => { created_by: currentUser?.id || null, created_by_name: currentUser?.name || '', }); - + // Show success message for add setToastData({ message: 'New unit added successfully!', type: 'success' }); } - + await fetchUnits(); closeModal(); } catch (error) { @@ -343,14 +343,14 @@ const UnitMaster = () => { setLoading(false); } }; - + const handleDeleteConfirm = async () => { if (deletingRow === null) return; try { const unitToDelete = units[deletingRow]; await deleteUnit(unitToDelete.id); setUnits(prev => prev.filter((_, index) => index !== deletingRow)); - + // ✅ Show success message for delete setToastData({ message: 'Unit deleted successfully!', @@ -365,18 +365,18 @@ const UnitMaster = () => { }); } }; - + const closeDeleteConfirm = () => { setDeletingRow(null); }; - + const deletingUnit = React.useMemo(() => { if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) { return null; } return units[deletingRow]; }, [deletingRow, units]); - + return (
@@ -388,7 +388,7 @@ const UnitMaster = () => { type="button" onClick={() => { if (!units.length) return; - + const csvHeader = headers.slice(0, headers.length - 1).join(','); const csvRows = units.map((item) => { const createdDate = item.created_at @@ -402,7 +402,7 @@ const UnitMaster = () => { const mappedProducts = Array.isArray(item.productsMapped) ? item.productsMapped.join('; ') : '0'; - + return [ item.uom || item.unitName || '', item.uom_short_name || item.description || '', @@ -415,7 +415,7 @@ const UnitMaster = () => { .map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`) .join(','); }); - + const csvContent = csvHeader + '\n' + csvRows.join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); @@ -437,7 +437,7 @@ const UnitMaster = () => { Export Export CSV - +
- + {loading ? (
@@ -500,7 +500,7 @@ const UnitMaster = () => { }} /> )} - + {/* Add/Edit Modal */} {modalMode && (
@@ -529,7 +529,7 @@ const UnitMaster = () => {
- +
@@ -542,13 +542,14 @@ const UnitMaster = () => { value={form.unitName} onChange={handleFormChange('unitName')} placeholder="Enter Unit Name" - // error={formErrors.unitName} + error={formErrors.unitName} + className={formErrors.unitName ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {formErrors.unitName && ( -

{formErrors.unitName}

+

)}
- +
{ onChange={handleFormChange('description')} placeholder="Enter Description" width="100%" - // error={formErrors.description} + error={formErrors.description} + className={formErrors.description ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''} /> {formErrors.description && ( -

{formErrors.description}

+

)}
- +
)} - + {/* Delete Confirmation Modal */} {deletingUnit && (
@@ -630,7 +632,7 @@ const UnitMaster = () => {

- +
)} - + {/* Custom Toast Notification */} {toastData && (
@@ -667,5 +669,5 @@ const UnitMaster = () => {
); }; - + export default UnitMaster; \ No newline at end of file