From cac8d1cb2e813c73cc99e84eaeef52fc5ce839e2 Mon Sep 17 00:00:00 2001 From: Malini Date: Mon, 10 Nov 2025 11:51:41 +0530 Subject: [PATCH] added products in the company profike --- .../src/components/common/Footer.jsx | 19 +- .../survey/ProductData/ProductData.jsx | 2 +- .../AdminUsers/ListAdminUsers.jsx | 262 +++++---- .../AdminUsers/ResetPasswordModal.jsx | 216 +++++-- .../Admin/configuration/CompanyProfile.jsx | 348 ++++++++++-- .../Admin/configuration/QuarterlyWindows.jsx | 530 +++++++++++------- .../pages/Admin/configuration/UnitMaster.jsx | 352 +++++------- .../services/configuration/adminService.js | 136 +++-- 8 files changed, 1145 insertions(+), 720 deletions(-) diff --git a/ipi-survey-platform/src/components/common/Footer.jsx b/ipi-survey-platform/src/components/common/Footer.jsx index c8878fb..0e7677b 100644 --- a/ipi-survey-platform/src/components/common/Footer.jsx +++ b/ipi-survey-platform/src/components/common/Footer.jsx @@ -23,9 +23,9 @@ const Footer = () => {

Our Policies

@@ -33,9 +33,9 @@ const Footer = () => {

Information and Support

@@ -67,7 +67,12 @@ const Footer = () => { alt="Mail Icon" className="h-4 w-4 opacity-80" /> - info@fcsc.gov.ae + + info@fcsc.gov.ae + {/* ✅ properly closed main content div */} diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx index cb7d88c..11bda13 100644 --- a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx +++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx @@ -394,7 +394,7 @@ const ProductData = ({ setIsLoadingProducts(true); setProductsError(''); try { - const productsList = await fetchProducts({ signal: productsController.signal }); + setProductOptions(productsList); setProductsError(''); } catch (error) { 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 a991418..8017102 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,15 +83,16 @@ 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); + const [oldPassword, setOldPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [resettingPassword, setResettingPassword] = useState(false); const [resetErrors, setResetErrors] = useState({}); - + // Helper: show toast const showToast = (message, type = 'success') => { setToastData({ message, type }); @@ -102,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) @@ -117,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'; @@ -150,7 +151,7 @@ const AdminUsers = () => { raw: user, }; }); - + setUsers(mappedUsers); } catch (error) { console.error('Error fetching admin users:', error); @@ -160,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', @@ -188,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, @@ -199,7 +200,7 @@ const AdminUsers = () => { , 'actions', ]); - + const handleFormChange = (e) => { if (!e) return; if (e.target && e.target.name !== undefined) { @@ -211,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 @@ -248,7 +249,7 @@ const AdminUsers = () => { : u ) ); - + setCurrentUser(null); } catch (error) { console.error("Error updating user:", error); @@ -257,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"); @@ -286,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, @@ -313,13 +314,13 @@ const AdminUsers = () => { email, is_active, }); - + setFormData({ name, email, status: is_active ? 'Active' : 'Inactive', }); - + setErrors({}); setIsEditModalOpen(true); } catch (error) { @@ -329,123 +330,152 @@ const AdminUsers = () => { setLoading(false); } }; - + const handleOpenResetModal = (user) => { setResetUser(user); + setOldPassword(''); setNewPassword(''); setConfirmPassword(''); setResetErrors({}); setIsResetModalOpen(true); }; - + const handleCloseResetModal = () => { setResetUser(null); + setOldPassword(''); setNewPassword(''); setConfirmPassword(''); setResetErrors({}); setIsResetModalOpen(false); setResettingPassword(false); }; - - // Validate reset password form + + // Validate reset password form - Updated to allow 8 to 12 characters const validateResetForm = () => { const newErrors = {}; - - if (!newPassword.trim()) { - newErrors.newPassword = 'Password is required'; - } else if (newPassword.length < 6) { - newErrors.newPassword = 'Password must be at least 6 characters'; + + 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) { - // Don't show error message, just prevent submission - 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; } - - // Check if passwords match (without showing error message) - if (newPassword !== confirmPassword) { - // Just return without showing error - return; - } - + try { setResettingPassword(true); - + + // Prepare data according to API specification const passwordData = { - newPassword: newPassword, - confirmPassword: confirmPassword + old_password: oldPassword, + new_password: newPassword, + confirm_password: confirmPassword }; - - console.log('Initiating password reset for user:', resetUser.id); - + + console.log('🔄 Initiating admin password reset for user:', resetUser.id); + const res = await changeAdminUserPassword(resetUser.id, passwordData); - - console.log('Password reset response:', res); - - // Check for different success patterns + + console.log('✅ Admin password reset response:', res); + + // Check for success based on common response patterns const success = res?.status === "success" || res?.status === 200 || res?.code === 200 || res?.data?.status === "success" || - res?.message?.includes("success") || - res?.message?.includes("updated") || + res?.message?.toLowerCase().includes("success") || + 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"); } - - // No success toast message + + showToast('Admin password reset successfully!', 'success'); handleCloseResetModal(); - + } catch (error) { - console.error('Error resetting password:', error); - - // More detailed error handling + console.error('❌ Error resetting admin password:', error); + + // Handle specific error cases let errorMessage = 'Failed to reset password'; - - if (error.response?.data?.message) { - errorMessage = error.response.data.message; - } else if (error.response?.data?.errors) { - // Handle validation errors from backend - const errors = error.response.data.errors; - errorMessage = Object.values(errors).flat().join(', '); - } else if (error.message) { + + if (error.message) { errorMessage = error.message; } - - showToast(errorMessage, 'error'); + + // Check for common admin password change errors + if (errorMessage.toLowerCase().includes('old password') || + errorMessage.toLowerCase().includes('current password') || + errorMessage.toLowerCase().includes('incorrect password')) { + setResetErrors(prev => ({ + ...prev, + oldPassword: 'The current password is incorrect' + })); + showToast('The current password you entered is incorrect', 'error'); + } else if (errorMessage.toLowerCase().includes('match')) { + setResetErrors(prev => ({ + ...prev, + confirmPassword: 'New passwords do not match' + })); + showToast('New passwords do not match', 'error'); + } 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') || + errorMessage.toLowerCase().includes('strength')) { + setResetErrors(prev => ({ + ...prev, + newPassword: 'Password is too weak. Use a stronger password.' + })); + showToast('Password is too weak. Please use a stronger password.', 'error'); + } else if (error.status === 404) { + showToast('Admin user not found', 'error'); + } else { + showToast(errorMessage, 'error'); + } } finally { setResettingPassword(false); } }; - + const handleDelete = (rowIndex) => { const user = users[rowIndex]; setSelectedUser(user); setShowDeleteModal(true); }; - + const handleEditSubmit = async (e) => { e.preventDefault(); const formErrors = validateForm(); @@ -454,12 +484,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 = { @@ -467,16 +497,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 @@ -490,7 +520,7 @@ const AdminUsers = () => { : u ) ); - + showToast('User updated successfully', 'success'); setIsEditModalOpen(false); setEditingRow(null); @@ -502,7 +532,7 @@ const AdminUsers = () => { setSaving(false); } }; - + const tableToolbar = (

Admin Users

@@ -524,26 +554,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); }} @@ -557,7 +587,7 @@ const AdminUsers = () => { Export Export CSV - +
); - + return (
{/* Summary cards */} @@ -578,7 +608,7 @@ const AdminUsers = () => { ))}
- + {/* Table */} { className="h-4 w-4" /> - + - + - + {/* Form */}
+ {/* Old Password Field */}
- handleInputChange('newPassword', e.target.value)} - onKeyPress={handleKeyPress} - disabled={loading} - /> +
+ handleInputChange('oldPassword', e.target.value)} + onKeyPress={handleKeyPress} + disabled={loading} + /> + +
+ {errors.oldPassword && ( +

+ + + + {errors.oldPassword} +

+ )} +
+ + {/* New Password Field */} +
+ +
+ handleInputChange('newPassword', e.target.value)} + onKeyPress={handleKeyPress} + disabled={loading} + /> + +
{errors.newPassword && (

@@ -109,24 +183,44 @@ const ResetPasswordModal = ({

)}
- + + {/* Confirm Password Field */}
- handleInputChange('confirmPassword', e.target.value)} - onKeyPress={handleKeyPress} - disabled={loading} - /> +
+ handleInputChange('confirmPassword', e.target.value)} + onKeyPress={handleKeyPress} + disabled={loading} + /> + +
{errors.confirmPassword && (

@@ -137,7 +231,7 @@ const ResetPasswordModal = ({ )}

- + {/* Note Section */}
@@ -149,7 +243,7 @@ const ResetPasswordModal = ({

- + {/* Action Buttons */}
); - case 3: + // case 4: return (
@@ -736,7 +745,7 @@ const CompanyProfile = () => { onChange={handleFormChange('corporateMakaniNumber')} placeholder="Enter Number" width="100%" - required + // required error={fieldErrors.corporateMakaniNumber} readOnly={form.corporateSameAs} /> @@ -746,7 +755,7 @@ const CompanyProfile = () => { onChange={handleFormChange('corporateContactPersonName')} placeholder="Enter Name" width="100%" - required + // required error={fieldErrors.corporateContactPersonName} readOnly={form.corporateSameAs} /> @@ -795,9 +804,7 @@ const CompanyProfile = () => { case 4: return (
-
-

Employment in Establishment

-
+

Employment in Establishment

{ placeholder="Enter Count" width="100%" type="number" - required + // required error={fieldErrors.employmentEmiratiMale} /> { placeholder="Enter Count" width="100%" type="number" - required + // required error={fieldErrors.employmentNonEmiratiMale} /> { placeholder="Enter Count" width="100%" type="number" - required + // required error={fieldErrors.employmentEmiratiFemale} /> { placeholder="Enter Count" width="100%" type="number" - required + // required error={fieldErrors.employmentNonEmiratiFemale} /> {
); - case 5: + case 3: + return ( +
+

Products

+
+ {/* Available Products Panel */} +
+
+
Available Products
+
+
+
+ + Search +
+
+ {isLoadingProducts ? ( +
+
+
+ ) : availableProducts.length > 0 ? ( +
    + {availableProducts.map((product) => ( +
  • +
    +
    + {product.hsCode || ''} + {product.hsCode && (product.productName || product.label || product.product_name) ? ' - ' : ''} + {product.productName || product.label || product.product_name || ''} +
    +
    + +
  • + ))} +
+ ) : ( +
+ {productSearchTerm ? 'No matching products found' : 'No products available'} +
+ )} +
+
+
+ + {/* Selected Products Panel */} +
+
+
Selected Products
+ {selectedProducts.length > 0 && ( + + {selectedProducts.length} selected + + )} +
+
+ {selectedProducts.length > 0 ? ( +
    + {selectedProducts.map((product) => ( +
  • +
    +
    + {product.hsCode || ''} + {product.hsCode && (product.productName || product.label || product.product_name) ? ' - ' : ''} + {product.productName || product.label || product.product_name || ''} +
    +
    + +
  • + ))} +
+ ) : ( +
+ - +
+ )} +
+
+
+
+ ); + default: return (
@@ -936,6 +1046,113 @@ const CompanyProfile = () => { } }; + // Fetch products when Products step is active + React.useEffect(() => { + if (activeStep === 3) { + // In the loadProducts function (around line 1044), update the product processing: +const loadProducts = async () => { + try { + console.log('Fetching products...'); + setIsLoadingProducts(true); + const response = await fetchProducts(); + console.log('Products API Response:', response); + + let productsData = []; + + if (Array.isArray(response)) { + productsData = response; + } else if (response?.data) { + productsData = Array.isArray(response.data) ? response.data : response.data.products || []; + } + + console.log('Processed products data:', productsData); + + // Update the products state with the formatted data + const formattedProducts = productsData.map(p => ({ + id: p.value, + label: p.label, + value: p.value + })); + + setProducts(formattedProducts); + setAvailableProducts(formattedProducts.filter(p => + !selectedProducts.some(sp => sp.id === p.id) + )); + + } catch (error) { + console.error('Error loading products:', error); + showToast('error', 'Failed to load products'); + } finally { + setIsLoadingProducts(false); + } +}; + + loadProducts(); + } + }, [activeStep, selectedProducts]); + + const handleProductSearch = (e) => { + const searchTerm = e.target.value.toLowerCase(); + setProductSearchTerm(searchTerm); + + if (!searchTerm) { + setAvailableProducts(products.filter(p => + !selectedProducts.some(sp => sp.id === p.id) + )); + return; + } + + const filtered = products.filter(product => + product.label.toLowerCase().includes(searchTerm) && + !selectedProducts.some(sp => sp.id === product.id) + ); + + setAvailableProducts(filtered); +}; + + const handleAddProduct = (product) => { + console.log('Adding product:', product); + setSelectedProducts(prev => { + const updated = [...prev, product]; + console.log('Updated selected products:', updated); + return updated; + }); + // Remove from available products + setAvailableProducts(prev => prev.filter(p => p.id !== product.id)); + setAvailableProducts(prev => { + const updated = prev.filter(p => p.id !== product.id); + console.log('Updated available products after add:', updated); + return updated; + }); + }; + + const handleRemoveProduct = (product) => { + console.log('Removing product:', product); + setSelectedProducts(prev => { + const updated = prev.filter(p => p.id !== product.id); + console.log('Updated selected products after removal:', updated); + return updated; + }); + // Add back to available products if not already there + setAvailableProducts(prev => { + if (!prev.some(p => p.id === product.id)) { + return [...prev, product]; + } + return prev; + }); + + // Only add back to available if it matches the search term or there's no search term + if (!productSearchTerm || + (product.hs_code && product.hs_code.toLowerCase().includes(productSearchTerm)) || + (product.product_name && product.product_name.toLowerCase().includes(productSearchTerm))) { + setAvailableProducts(prev => { + const updated = [...prev, product]; + console.log('Updated available products after remove:', updated); + return updated; + }); + } + }; + const handleNextStep = React.useCallback(() => { if (!validateStepFields(activeStep)) { return; @@ -1015,12 +1232,7 @@ const CompanyProfile = () => { ); }; - // Calculate pagination range - const startIndex = (currentPage - 1) * pageSize; - const endIndex = Math.min(startIndex + pageSize, filteredProfiles.length); - const paginatedProfiles = filteredProfiles.slice(startIndex, endIndex); - - const displayedRows = paginatedProfiles.map((item) => { + const displayedRows = filteredProfiles.map((item) => { const isEditing = editingRow !== null && profiles[editingRow]?.establishmentId === item.establishmentId; const isDeleting = @@ -1032,7 +1244,9 @@ const CompanyProfile = () => { item.emirate, item.isicCode, item.establishmentId, - item.products, + item.products && item.products.length > 0 + ? item.products.map(p => p.hsCode ? `${p.hsCode} - ${p.productName || p.label || p.product_name || 'N/A'}` : (p.productName || p.product_name || 'N/A')).filter(Boolean).join(', ') + : '-', item.totalEmployees, item.createdBy, item.createdOn, @@ -1368,15 +1582,31 @@ const corporateFieldKeys = Object.values(contactToCorporateMap); const loadProfiles = async () => { setIsLoading(true); - const params = { - page: currentPage, - limit: pageSize, + // First, get the total number of records + const countParams = { search: debouncedSearch || undefined, emirateId: selectedEmirateFilter || undefined, status: selectedStatusFilter || undefined, + page: 1, + limit: 1, // Just need the total count }; try { + // First, get the total count + const countResponse = await fetchEstablishments({ params: countParams, signal: controller.signal }); + const countPayload = countResponse?.data ?? countResponse; + const totalRecords = countPayload?.pagination?.total_records || 0; + + // Then fetch all records with the total count as limit + const params = { + search: debouncedSearch || undefined, + emirateId: selectedEmirateFilter || undefined, + status: selectedStatusFilter || undefined, + page: 1, + limit: totalRecords || 1000, + }; + + // Fetch all records const response = await fetchEstablishments({ params, signal: controller.signal }); const payload = response?.data ?? response; @@ -1384,25 +1614,15 @@ const loadProfiles = async () => { console.group('API Response Details'); console.log('Full response:', response); console.log('Response data:', payload); - console.log('Pagination object:', payload?.pagination); - console.log('Total records from pagination:', payload?.pagination?.total_records); console.groupEnd(); const records = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : []; if (cancelled) return; - setProfiles(records.map(mapApiEstablishmentToProfile)); - // Get total records from pagination object in the API response - const totalRecords = payload?.pagination?.total_records; - if (totalRecords !== undefined) { - console.log(`Setting total items from pagination.total_records: ${totalRecords}`); - setTotalItems(totalRecords); - } else { - // Fallback to other possible locations if pagination is not available - const fallbackTotal = payload?.total || payload?.count || records.length; - console.warn('pagination.total_records not found, using fallback value:', fallbackTotal); - setTotalItems(fallbackTotal); - } + // Set all records to the profiles state + setProfiles(records.map(mapApiEstablishmentToProfile)); + // Set total items to the number of records + setTotalItems(records.length); } catch (error) { if (cancelled) return; if (error.name === 'CanceledError' || error.name === 'AbortError') return; @@ -1638,6 +1858,7 @@ const loadProfiles = async () => { } setModalMode(null); setForm(createEmptyProfile()); + setSelectedProducts([]); // Clear selected products when modal is closed setEditingRow(null); setFieldErrors({}); setIsDirty(false); @@ -2011,6 +2232,9 @@ const requiredFields = [ email: form.userProfileEmail || '', password: form.userProfilePassword || '' }, + establishment_products: Array.isArray(selectedProducts) && selectedProducts.length > 0 + ? selectedProducts.map(p => ({ product_id: p.id || p.product_id || 0 })) + : [{ product_id: 0 }], }; try { const apiResponse = await createEstablishment(apiPayload); @@ -2020,10 +2244,18 @@ const requiredFields = [ const createdRecord = apiResponse?.data; createdRecordId = createdRecord?.id ?? createdRecord?.establishment?.id ?? null; apiResultData = createdRecord; + setIsRefreshing(true); + setTimeout(() => { + window.location.reload(); + }, 1000); } catch (apiError) { const apiMessage = apiError?.response?.data?.message || apiError?.message || ''; if (apiMessage.includes("Field 'gender' doesn't have a default value")) { showToast('success', 'Establishment submitted successfully.'); + setIsRefreshing(true); + setTimeout(() => { + window.location.reload(); + }, 1000); } else { throw apiError; } @@ -2088,6 +2320,9 @@ const requiredFields = [ if (establishmentUserPayload.name || establishmentUserPayload.email || establishmentUserPayload.password) { updatePayload.establishment_user = establishmentUserPayload; } + updatePayload.establishment_products = Array.isArray(selectedProducts) && selectedProducts.length > 0 + ? selectedProducts.map(p => ({ product_id: p.id || p.product_id || 0 })) + : [{ product_id: 0 }]; const apiResponse = await updateEstablishment(targetId, updatePayload); console.log("apiResponse",apiResponse) const successMessage = apiResponse?.message || 'Establishment updated successfully.'; @@ -2355,7 +2590,7 @@ const requiredFields = [ window.scrollTo({ top: 0, behavior: 'smooth' }); }, pageSize, - totalItems: totalItems, // Use the total count from API response + totalItems: filteredProfiles.length, pageSizeOptions: [10, 20, 50, 100], onPageSizeChange: (size) => { setPageSize(size); @@ -2501,6 +2736,15 @@ const requiredFields = [
)} + {/* Full Page Loader */} + {isRefreshing && ( +
+
+

Updating data...

+

Please wait while we refresh the page

+
+ )} + {showDeleteConfirm && deletingProfile && (
{ + React.useEffect(() => { + const timer = setTimeout(() => { + onClose(); + }, 3000); + + return () => clearTimeout(timer); + }, [onClose]); + + const getToastStyles = () => { + switch (type) { + case 'success': + return 'bg-green-50 border-green-200 text-green-800'; + case 'error': + return 'bg-red-50 border-red-200 text-red-800'; + case 'warning': + return 'bg-yellow-50 border-yellow-200 text-yellow-800'; + default: + return 'bg-blue-50 border-blue-200 text-blue-800'; + } + }; + + const getIcon = () => { + switch (type) { + case 'success': + return '✅'; + case 'error': + return '❌'; + case 'warning': + return '⚠️'; + default: + return 'ℹ️'; + } + }; + + return ( +
+ {getIcon()} + {message} + +
+ ); +}; + const createEmptyQuarterForm = () => ({ survey_name: '', establishment: '-', // Default to hyphen @@ -24,7 +73,7 @@ const createEmptyQuarterForm = () => ({ submissionCount: '', status: 'Active', // Default to Active }); - + const QuarterlyWindows = () => { const [quarterData, setQuarterData] = React.useState([]); const [editingRow, setEditingRow] = React.useState(null); @@ -36,24 +85,25 @@ const QuarterlyWindows = () => { const [quarterFilter, setQuarterFilter] = React.useState('all'); const [hoveredEdit, setHoveredEdit] = React.useState(null); const [currentPage, setCurrentPage] = React.useState(1); - const pageSize = 10; + const [pageSize, setPageSize] = React.useState(10); const [isLoading, setIsLoading] = React.useState(false); - const [toast, setToast] = React.useState({ show: false, message: '', type: 'success' }); - + 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 () => { @@ -80,19 +130,17 @@ const QuarterlyWindows = () => { setQuarterData(formattedData); } else { console.error('Unexpected API response format:', response); - // Optionally set some error state here } } catch (error) { console.error('Error fetching quarterly windows:', error); - // You might want to add error handling UI here } finally { setIsLoading(false); } }; - + fetchQuarterlyWindows(); }, []); - + // Table headers with left alignment and no wrapping const headers = [ { name: 'Survey Name', className: 'whitespace-nowrap text-left' }, @@ -107,29 +155,30 @@ const QuarterlyWindows = () => { { name: 'Submission Count', className: 'whitespace-nowrap text-left' }, { name: 'Actions', className: 'whitespace-nowrap text-left' }, ]; - // Column widths significantly increased for better content visibility + const columnwidth = [ - 220, // Survey Name (reduced from 550) - 100, // Year (reduced from 150) - 120, // Quarter (reduced from 180) - 150, // Start Date (reduced from 220) - 150, // End Date (reduced from 220) - 180, // Grace Period (reduced from 250) - 120, // Assigned (reduced from 180) - 120, // Responded (reduced from 200) - 140, // Not Responded (reduced from 220) - 140, // Submission Count (reduced from 240) - 120 // Actions (reduced from 160) + 220, // Survey Name + 100, // Year + 120, // Quarter + 150, // Start Date + 150, // End Date + 180, // Grace Period + 120, // Assigned + 120, // Responded + 140, // Not Responded + 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 = [ @@ -151,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, @@ -185,25 +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; @@ -211,21 +259,22 @@ const QuarterlyWindows = () => { return parseInt(b) - parseInt(a); }); }, [quarterData]); - + const quarterOptions = ['all', 'Q1', 'Q2', 'Q3', 'Q4']; - + const openAddModal = () => { setForm(createEmptyQuarterForm()); setOriginalForm(null); setEditingRow(null); 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 @@ -238,13 +287,15 @@ const QuarterlyWindows = () => { setForm(formData); setOriginalForm(formData); setModalMode('edit'); + setValidationErrors({}); }; - + const handleCloseModal = () => { setModalMode(null); setEditingRow(null); + setValidationErrors({}); }; - + const handleReset = () => { if (modalMode === 'edit' && editingRow !== null) { if (originalForm) { @@ -258,45 +309,62 @@ const QuarterlyWindows = () => { } else { setForm(createEmptyQuarterForm()); } + setValidationErrors({}); }; - - - - const showToast = (message, type = 'success') => { - setToast({ show: true, message, type }); - setTimeout(() => setToast(prev => ({ ...prev, show: false })), 4000); - }; - - const handleSave = async () => { - try { - if (!form.survey_name || !form.year || !form.quarter || !form.startDate || !form.endDate) { - showToast('Please fill in all required fields', 'error'); - return; - } - + + const validateForm = () => { + const errors = {}; + + if (!form.survey_name.trim()) { + errors.survey_name = 'Survey Name is required'; + } + if (!form.year) { + errors.year = 'Year is required'; + } + if (!form.quarter) { + errors.quarter = 'Quarter is required'; + } + if (!form.startDate) { + errors.startDate = 'Open Date is required'; + } + if (!form.endDate) { + errors.endDate = 'Close Date is required'; + } + + // Date validation + if (form.startDate && form.endDate) { const startDate = new Date(form.startDate); const endDate = new Date(form.endDate); if (endDate <= startDate) { - showToast('Close Date must be after Open Date', 'error'); - return; + errors.endDate = 'Close Date must be after Open Date'; } - - const formatDateForAPI = (dateString) => { - if (!dateString) return ''; - // If already in YYYY-MM-DD format - if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) { - return dateString; - } - // Handle DD/MM/YYYY format - if (dateString.includes('/')) { - const [day, month, year] = dateString.split('/'); - return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`; - } - // For any other format, let the Date object handle it - return new Date(dateString).toISOString().split('T')[0]; - }; - - + } + + 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 => ({ + ...prev, + [field]: '' + })); + } + }; + + 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, @@ -307,17 +375,16 @@ const QuarterlyWindows = () => { assigned: form.assigned || 0, responded: form.responded || 0, not_responded: form.not_responded || 0, - // submission_count: form.submissionCount || 0, is_active: form.status === 'Active', establishment: form.establishment || '-', }; - + if (modalMode === 'add') { const response = await createQuarterlyWindow(formattedData); if (response.status === 'success') { const newItem = { ...formattedData, - id: response.data.id || Date.now(), // Use the ID from response or generate a temporary one + id: response.data.id || Date.now(), startDate: form.startDate, endDate: form.endDate, gracePeriod: formattedData.grace_periods_days ? `${formattedData.grace_periods_days} days` : '0 days', @@ -325,21 +392,26 @@ const QuarterlyWindows = () => { status: formattedData.is_active ? 'Active' : 'Inactive' }; setQuarterData((prev) => [...prev, newItem]); - showToast('Quarterly window added successfully!'); + + // Show success toast for add + setToastData({ + message: 'Quarterly survey created successfully!', + type: 'success' + }); } else { - showToast('Failed to add quarterly window', 'error'); + console.error('Failed to add quarterly window'); } } else if (modalMode === 'edit' && editingRow !== null) { const itemId = quarterData[editingRow]?.id; if (!itemId) { - showToast('Error: Could not find the item to update', 'error'); + console.error('Error: Could not find the item to update'); return; } 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, @@ -347,34 +419,41 @@ const QuarterlyWindows = () => { endDate: form.endDate, gracePeriod: formattedData.grace_periods_days ? `${formattedData.grace_periods_days} days` : '0 days', status: formattedData.is_active ? 'Active' : 'Inactive' - } + } : item ) ); - showToast('Quarterly window updated successfully!'); + + // Show success toast for edit + setToastData({ + message: 'Quarterly survey updated successfully!', + type: 'success' + }); } else { - showToast('Failed to update quarterly window', 'error'); + console.error('Failed to update quarterly window'); } } handleCloseModal(); } catch (error) { console.error('Error saving quarterly window:', error); - showToast('An error occurred while saving. Please try again.', 'error'); } }; - + return ( - <> - {toast.show && ( -
- setToast({ ...toast, show: false })} - /> +
+ {/* Custom Toast Notification */} + {toastData && ( +
+
+ setToastData(null)} + /> +
)} -
+

Manage Surveys ({filteredRows.length})

@@ -425,40 +504,40 @@ const QuarterlyWindows = () => {
- +
{ currentPage, onPageChange: (page) => { setCurrentPage(page); - // Scroll to top when changing pages window.scrollTo({ top: 0, behavior: 'smooth' }); }, pageSize, @@ -523,12 +601,12 @@ const QuarterlyWindows = () => { pageSizeOptions: [10, 20, 50, 100], onPageSizeChange: (size) => { setPageSize(size); - setCurrentPage(1); // Reset to first page when changing page size + setCurrentPage(1); } }} /> - + {modalMode && (
@@ -541,28 +619,6 @@ const QuarterlyWindows = () => { border: '1px solid #E5E7EB', }} > - {/* Toast Notification */} - {toast.show && ( -
- - - - - {toast.message} - -
-)} - - -

{modalMode === 'edit' ? 'Edit Quarter' : 'Create Quarterly Survey'} @@ -595,70 +651,110 @@ const QuarterlyWindows = () => {

- +
+ Survey Name + + } value={form.survey_name} required - onChange={(e) => setForm({ ...form, survey_name: e.target.value })} + onChange={(e) => handleFormChange('survey_name')(e.target.value)} placeholder="Enter survey name" + // error={validationErrors.survey_name} /> -
-
- setForm({ ...form, quarter: e.target.value })} - options={['Q1', 'Q2', 'Q3', 'Q4'].map((q) => ({ label: q, value: q }))} - placeholder="Select Quarter" - /> - setForm({ ...form, year: e.target.value })} - options={Array.from({ length: 6 }).map((_, idx) => { - const yr = 2023 + idx; - return { label: String(yr), value: String(yr) }; - })} - required - placeholder="Select Year" - /> - setForm({ ...form, startDate: e.target.value })} - placeholder="Select Date" - /> - setForm({ ...form, endDate: e.target.value })} - placeholder="Select Date" - /> + {validationErrors.survey_name && ( +

{validationErrors.survey_name}

+ )}
- {/* setForm({ ...form, status: e.target.value })} - options={[ - { label: 'Active', value: 'Active' }, - { label: 'Inactive', value: 'Inactive' }, - ]} - placeholder="Select Status" - /> */} - {/*
*/} -
+
+
+ + Quarter + + } + value={form.quarter} + required + onChange={(e) => handleFormChange('quarter')(e.target.value)} + options={['Q1', 'Q2', 'Q3', 'Q4'].map((q) => ({ label: q, value: q }))} + placeholder="Select Quarter" + // error={validationErrors.quarter} + /> + {validationErrors.quarter && ( +

{validationErrors.quarter}

+ )} +
+ +
+ + Year + + } + value={form.year} + onChange={(e) => handleFormChange('year')(e.target.value)} + options={Array.from({ length: 6 }).map((_, idx) => { + const yr = 2023 + idx; + return { label: String(yr), value: String(yr) }; + })} + required + placeholder="Select Year" + // error={validationErrors.year} + /> + {validationErrors.year && ( +

{validationErrors.year}

+ )} +
+ +
+ + Opens On + + } + value={form.startDate} + required + onChange={(e) => handleFormChange('startDate')(e.target.value)} + placeholder="Select Date" + error={validationErrors.startDate} + /> + {validationErrors.startDate && ( +

{validationErrors.startDate}

+ )} +
+ +
+ + Closes On + + } + value={form.endDate} + required + onChange={(e) => handleFormChange('endDate')(e.target.value)} + placeholder="Select Date" + error={validationErrors.endDate} + /> + {validationErrors.endDate && ( +

{validationErrors.endDate}

+ )} +
+
+ +
setForm({ ...form, gracePeriod: e.target.value })} + onChange={(e) => handleFormChange('gracePeriod')(e.target.value)} options={['5 days', '10 days', '15 days', '20 days', '30 days'].map((item) => ({ label: item, value: item, @@ -666,7 +762,7 @@ const QuarterlyWindows = () => { placeholder="Select Grace Period" />
- +
)} -
- ); }; - -export default QuarterlyWindows; + +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 f549ed4..9390af6 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,18 +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': @@ -38,7 +37,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'bg-blue-50 border-blue-200 text-blue-800'; } }; - + const getIcon = () => { switch (type) { case 'success': @@ -51,7 +50,7 @@ const CustomToast = ({ message, type, onClose }) => { return 'ℹ️'; } }; - + return (
{getIcon()} @@ -65,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); @@ -81,30 +80,30 @@ const UnitMaster = () => { const [modalMode, setModalMode] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [form, setForm] = useState(createEmptyUnitForm()); + const [formErrors, setFormErrors] = useState({}); const [isProductDropdownOpen, setIsProductDropdownOpen] = useState(false); const productDropdownRef = useRef(null); const pageSize = 10; 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); @@ -112,23 +111,24 @@ const UnitMaster = () => { setLoading(false); } }; - + const filteredUnits = React.useMemo(() => { - if (!searchTerm) return units; - const term = searchTerm.toLowerCase(); - 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]); + if (!searchTerm) return units; + const term = searchTerm.toLowerCase(); + 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, @@ -162,8 +162,8 @@ const UnitMaster = () => { 'actions', ]; }); - }, [units, currentUser,filteredUnits]); - + }, [units, currentUser, filteredUnits]); + const statusOptions = React.useMemo( () => [ { label: 'Active', value: 'Active' }, @@ -171,32 +171,34 @@ const UnitMaster = () => { ], [] ); - - // const productOptions = React.useMemo( - // () => [ - // { label: 'Wheat', value: 'Wheat' }, - // { label: 'Barley', value: 'Barley' }, - // { label: 'Oats', value: 'Oats' }, - // { label: 'Rice', value: 'Rice' }, - // { label: 'Corn', value: 'Corn' }, - // { label: 'Soybeans', value: 'Soybeans' }, - // { label: 'Sorghum', value: 'Sorghum' }, - // { label: 'Millet', value: 'Millet' }, - // { label: 'Quinoa', value: 'Quinoa' }, - // { label: 'Buckwheat', value: 'Buckwheat' }, - // ], - // [] - // ); - + 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()); + setFormErrors({}); setModalMode('add'); setIsProductDropdownOpen(false); }; - + const handleEdit = (index) => { const selected = units[index]; if (!selected) return; @@ -207,26 +209,36 @@ const UnitMaster = () => { productsMapped: Array.isArray(selected.productsMapped) ? [...selected.productsMapped] : [], status: selected.is_active ? 'Active' : 'Inactive', }); + setFormErrors({}); setModalMode('edit'); setIsProductDropdownOpen(false); }; - + const handleDelete = (index) => { setDeletingRow(index); }; - + const closeModal = () => { setModalMode(null); setForm(createEmptyUnitForm()); + setFormErrors({}); 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 => ({ + ...prev, + [field]: '' + })); + } }; - + const handleProductToggle = (value) => { setForm((prev) => { const current = Array.isArray(prev.productsMapped) ? prev.productsMapped : []; @@ -237,7 +249,7 @@ const UnitMaster = () => { }; }); }; - + React.useEffect(() => { const handleClickOutside = (event) => { if (!productDropdownRef.current) return; @@ -245,41 +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"); - // ✅ Validation checks - if (!form.unitName) { - setToastData({ - message: 'Please enter Unit Name.', - type: 'error' - }); - return; - } - if (!form.description) { - setToastData({ - message: 'Please enter Description.', - type: 'error' - }); - return; - } - // Status validation removed as per request - + // ✅ 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({ @@ -288,7 +289,7 @@ const UnitMaster = () => { }); return; } - + const unitData = { uom: form.unitName, uom_short_name: form.description, @@ -296,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!', @@ -322,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) { @@ -342,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!', @@ -364,47 +365,30 @@ 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 (
-

- Unit Master ({units.length}{units.length === 1 ? ' unit' : ''}) -

- {/*
-
-
- Search -
- setSearchTerm(e.target.value)} - placeholder="Search by code or name" - className="h-10 w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]" - /> -
-
*/} - - - {/*
*/} +

+ Unit Master ({units.length}{units.length === 1 ? ' unit' : ''}) +

- +
- + {loading ? (
@@ -516,7 +500,7 @@ const UnitMaster = () => { }} /> )} - + {/* Add/Edit Modal */} {modalMode && (
@@ -545,86 +529,45 @@ const UnitMaster = () => {
- +
- - Unit Name * - - } - value={form.unitName} - onChange={handleFormChange('unitName')} - placeholder="Enter Unit Name" - /> - - {/*
- - - {isProductDropdownOpen && ( -
-
    - {productOptions.map((option) => { - const checked = form.productsMapped.includes(option.value); - return ( -
  • - -
  • - ); - })} -
-
+
+ + Unit Name * + + } + value={form.unitName} + onChange={handleFormChange('unitName')} + placeholder="Enter Unit Name" + // error={formErrors.unitName} + /> + {formErrors.unitName && ( +

{formErrors.unitName}

)} -
*/} - - {/* - Status * - - } - value={form.status} - onChange={handleFormChange('status')} - options={statusOptions} - placeholder="Select Status" - /> */} +
+ +
+ + Description * + + } + value={form.description} + onChange={handleFormChange('description')} + placeholder="Enter Description" + width="100%" + // error={formErrors.description} + /> + {formErrors.description && ( +

{formErrors.description}

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

- +
)} - + {/* Custom Toast Notification */} - {/* Custom Toast Notification - CENTERED */} -{toastData && ( -
-
- setToastData(null)} - /> -
-
-)} + {toastData && ( +
+
+ setToastData(null)} + /> +
+
+ )} ); }; - + export default UnitMaster; \ No newline at end of file diff --git a/ipi-survey-platform/src/services/configuration/adminService.js b/ipi-survey-platform/src/services/configuration/adminService.js index 97910fc..f435071 100644 --- a/ipi-survey-platform/src/services/configuration/adminService.js +++ b/ipi-survey-platform/src/services/configuration/adminService.js @@ -1,8 +1,7 @@ -// src/services/configuration/unitService.js import { getRequest, postRequest, putRequest, deleteRequest } from '@/services/api/CommonService'; - + const admin = '/admin_users'; - + export const getAdminUser = async () => { try { const response = await getRequest(admin); @@ -12,7 +11,7 @@ export const getAdminUser = async () => { throw error; } }; - + export const createadminusers = async (admindata) => { try { const response = await postRequest(admin, admindata); @@ -22,7 +21,7 @@ export const createadminusers = async (admindata) => { throw error; } }; - + export const updateAdminUser = async (id, admindata) => { try { const response = await putRequest(`${admin}/${id}`, admindata); @@ -41,7 +40,73 @@ export const getAdminUserById = async (id) => { throw error; } }; - + +export const changeAdminUserPassword = async (id, passwordData) => { + try { + if (!id) throw new Error("Invalid user ID"); + + console.log('🔍 Debug: Changing password for admin user:', id); + console.log('🔍 Debug: Password data received:', { + hasOldPassword: !!passwordData.old_password, + hasNewPassword: !!passwordData.new_password, + hasConfirmPassword: !!passwordData.confirm_password + }); + + // Prepare the request data according to API specification + const requestData = { + old_password: passwordData.old_password, + new_password: passwordData.new_password, + confirm_password: passwordData.confirm_password + }; + + console.log('🔍 Debug: Final request data being sent:', requestData); + + // Use the correct endpoint for admin users + // const response = await putRequest(`/admin_users/${id}/change-password`, requestData); + const response = await putRequest(`admin_users/${id}/change-password`, requestData); + + console.log('🔍 Debug: Admin password change successful:', response.data); + return response.data; + + } catch (error) { + console.error("❌ Error changing admin user password:", error); + + if (error.response) { + console.error("🔍 Debug: API error response:", { + status: error.response.status, + data: error.response.data, + headers: error.response.headers + }); + + // Extract meaningful error message + let errorMessage = 'Failed to change password'; + + if (error.response.status === 404) { + errorMessage = 'Admin user not found or endpoint unavailable'; + } else if (error.response.data.message) { + errorMessage = error.response.data.message; + } else if (error.response.data.errors) { + // Handle Laravel validation errors + const errors = error.response.data.errors; + errorMessage = Object.values(errors).flat().join(', '); + } else if (error.response.data.error) { + errorMessage = error.response.data.error; + } + + const detailedError = new Error(errorMessage); + detailedError.status = error.response.status; + detailedError.data = error.response.data; + + throw detailedError; + } else if (error.request) { + console.error("🔍 Debug: No response received:", error.request); + throw new Error('No response from server. Please check your connection.'); + } else { + console.error("🔍 Debug: Request setup error:", error.message); + throw error; + } + } +}; // export const deleteAdminUser = async (id) => { // try { // const response = await deleteRequest(`${admin}/${id}`); @@ -51,57 +116,7 @@ export const getAdminUserById = async (id) => { // throw error; // } // }; -export const changeAdminUserPassword = async (id, passwordData) => { - try { - if (!id) throw new Error("Invalid user ID"); - - // Debug: see what fields the API expects - console.log("🔍 Debug: Making test request to discover required fields"); - - const testData = { test_field: "test_value" }; - - try { - await putRequest(`/admin/${id}/change-password`, testData); - } catch (testError) { - console.log( - "🔍 Debug: Test request failed with:", - testError.response?.data || testError.message - ); - } - - // Common Laravel-style field names - const requestData = { - password: passwordData.newPassword, - password_confirmation: passwordData.confirmPassword, - new_password: passwordData.newPassword, - new_password_confirmation: passwordData.confirmPassword, - }; - - console.log("🔍 Debug: Final request data:", requestData); - - const response = await putRequest(`/admin/${id}/change-password`, requestData); - - return response.data; - } catch (error) { - console.error("Error changing admin user password:", error); - - if (error.response) { - console.error("🔍 Debug: Full error response:", { - status: error.response.status, - data: error.response.data, - headers: error.response.headers, - }); - - if (error.response.data.errors) { - console.error("🔍 Debug: Validation errors:", error.response.data.errors); - } - } - - throw error; - } -}; - - + export const deleteAdminUser = async (id) => { try { if (!id) throw new Error("Invalid user ID"); @@ -128,8 +143,5 @@ export const deleteAdminUser = async (id) => { console.error("Error deleting admin user:", error); throw error; } - - - - -}; \ No newline at end of file +}; + \ No newline at end of file