diff --git a/ipi-survey-platform/src/components/admin/SubmissionTable.jsx b/ipi-survey-platform/src/components/admin/SubmissionTable.jsx
index a11e90e..5d4d5e3 100644
--- a/ipi-survey-platform/src/components/admin/SubmissionTable.jsx
+++ b/ipi-survey-platform/src/components/admin/SubmissionTable.jsx
@@ -2,12 +2,20 @@ import React, { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import Table from '@/components/common/Table';
import apiClient from '../../services/api/apiClient.js';
-import { CheckCircle2, XCircle, Eye } from 'lucide-react';
+import { CheckCircle2, XCircle, Eye, X } from 'lucide-react';
+import { toast } from 'react-toastify';
const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = React.useState(1);
+ const [showApproveModal, setShowApproveModal] = useState(false);
+ const [showRejectModal, setShowRejectModal] = useState(false);
+ const [selectedSubmission, setSelectedSubmission] = useState(null);
+ const [actionType, setActionType] = useState('');
+ const [rejectionReason, setRejectionReason] = useState('');
+ const [showRejectError, setShowRejectError] = useState(false);
+ const [isProcessing, setIsProcessing] = useState(false);
const pageSize = 10;
const navigate = useNavigate();
@@ -118,6 +126,123 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
+ const handleActionClick = (submission, type) => {
+ setSelectedSubmission(submission);
+ setActionType(type);
+ if (type === 'approve') {
+ setShowApproveModal(true);
+ } else {
+ setRejectionReason('');
+ setShowRejectError(false);
+ setShowRejectModal(true);
+ }
+ };
+
+ const handleCloseApproveModal = () => {
+ setShowApproveModal(false);
+ setSelectedSubmission(null);
+ };
+
+ const handleCloseRejectModal = () => {
+ setShowRejectModal(false);
+ setSelectedSubmission(null);
+ setRejectionReason('');
+ setShowRejectError(false);
+ };
+
+ const handleApproveConfirm = async () => {
+ if (!selectedSubmission || isProcessing) return;
+
+ setIsProcessing(true);
+ try {
+ const response = await apiClient.put(`/approveOrRejectSubmission/${selectedSubmission.id}`, {
+ approve_reject_status: 1,
+ remarks: ''
+ });
+
+ const ok = (response && response.data) || response?.status === 200 || response?.status === 'success';
+
+ if (ok) {
+ toast.success('Submission approved successfully');
+
+ // Update the status of the current item in the data array
+ setData(prevData =>
+ prevData.map(item =>
+ item.id === selectedSubmission.id
+ ? {
+ ...item,
+ status: 'Approved',
+ reviewer: JSON.parse(sessionStorage.getItem("user_profile"))?.name || 'Admin',
+ reviewerOn: new Date().toISOString()
+ }
+ : item
+ )
+ );
+ } else {
+ throw new Error('Failed to approve submission');
+ }
+ } catch (error) {
+ console.error('Error approving submission:', error);
+ toast.error(error.message || 'Failed to approve submission');
+ } finally {
+ setIsProcessing(false);
+ setShowApproveModal(false);
+ setSelectedSubmission(null);
+ }
+ };
+
+ const handleRejectConfirm = async () => {
+ if (!selectedSubmission || isProcessing) {
+ return;
+ }
+
+ // Validate the rejection reason
+ if (!rejectionReason.trim()) {
+ setShowRejectError(true);
+ toast.error('Please provide a reason for rejection');
+ return;
+ }
+
+ setIsProcessing(true);
+ try {
+ const response = await apiClient.put(`/approveOrRejectSubmission/${selectedSubmission.id}`, {
+ approve_reject_status: 0,
+ remarks: rejectionReason.trim()
+ });
+
+ const ok = (response && response.data) || response?.status === 200 || response?.status === 'success';
+
+ if (ok) {
+ toast.error('Submission has been rejected');
+
+ // Update the status of the current item in the data array
+ setData(prevData =>
+ prevData.map(item =>
+ item.id === selectedSubmission.id
+ ? {
+ ...item,
+ status: 'Rejected',
+ reviewer: JSON.parse(sessionStorage.getItem("user_profile"))?.name || 'Admin',
+ reviewerOn: new Date().toISOString()
+ }
+ : item
+ )
+ );
+
+ setRejectionReason('');
+ } else {
+ throw new Error('Failed to reject submission');
+ }
+ } catch (error) {
+ console.error('Error rejecting submission:', error);
+ toast.error(error.message || 'Failed to reject submission');
+ } finally {
+ setIsProcessing(false);
+ setShowRejectModal(false);
+ setSelectedSubmission(null);
+ }
+ };
+
const headers = [
'Establishment',
'Emirates',
@@ -195,30 +320,22 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
- navigate(`/admin/validations/${item.id}`, {
- state: { actionType: 'approve' },
- })
- }
+ className={`${item.status?.toLowerCase() === 'approved' ? 'text-gray-400 cursor-not-allowed' : 'text-green-600 cursor-pointer hover:scale-110'} transition-transform`}
+ onClick={item.status?.toLowerCase() !== 'approved' ? () => handleActionClick(item, 'approve') : undefined}
/>
- Approve
+ {item.status?.toLowerCase() === 'approved' ? 'Already Approved' : 'Approve'}
- navigate(`/admin/validations/${item.id}`, {
- state: { actionType: 'reject' },
- })
- }
+ className={`${item.status?.toLowerCase() === 'rejected' ? 'text-gray-400 cursor-not-allowed' : 'text-red-600 cursor-pointer hover:scale-110'} transition-transform`}
+ onClick={item.status?.toLowerCase() !== 'rejected' ? () => handleActionClick(item, 'reject') : undefined}
/>
- Reject
+ {item.status?.toLowerCase() === 'rejected' ? 'Already Rejected' : 'Reject'}
@@ -259,6 +376,135 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
View All Submissions
+
+ {/* Approve Confirmation Modal */}
+ {showApproveModal && selectedSubmission && (
+
+
+
+
Approve this submission?
+
+
+
+
+
+
+ You're about to approve {selectedSubmission.establishment?.factory_name} for {selectedSubmission.quarter} {selectedSubmission.year} .
+
+
+ This will lock the record for the establishment.
+
+
+
+ Cancel
+
+
+ {isProcessing ? (
+ <>
+
+
+
+
+ Approving...
+ >
+ ) : 'Approve'}
+
+
+
+
+
+ )}
+
+ {/* Reject Confirmation Modal */}
+ {showRejectModal && selectedSubmission && (
+
+
+
+
Reject this submission?
+
+
+
+
+
+
+ You're about to return {selectedSubmission.establishment?.factory_name} 's {selectedSubmission.quarter} {selectedSubmission.year} submission for correction.
+
+
+ The status will change to Rejected and the establishment can resubmit within the survey window.
+
+
+
+ Reason for rejection: *
+
+
+
+
+ Cancel
+
+
+ {isProcessing ? (
+ <>
+
+
+
+
+ Rejecting...
+ >
+ ) : 'Reject'}
+
+
+
+
+
+ )}
);
};
diff --git a/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx b/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx
index 85a88d5..a7e54ef 100644
--- a/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx
+++ b/ipi-survey-platform/src/pages/Admin/AdminDashboard.jsx
@@ -32,7 +32,7 @@ const AdminDashboard = () => {
const isMounted = useRef(false);
const prevParams = useRef({ quarter: null, year: null });
- const fetchDashboardData = React.useCallback(async (quarter, year) => {
+ const fetchDashboardData = React.useCallback(async (quarter = 'All', year = 'All') => {
// Skip if params haven't changed
if (prevParams.current.quarter === quarter && prevParams.current.year === year) {
return;
@@ -45,6 +45,7 @@ const AdminDashboard = () => {
setLoading(true);
const params = new URLSearchParams();
+ // Only add quarter and year to params if they are not 'All'
if (quarter && quarter !== 'All') {
params.append('quarter', quarter);
}
@@ -58,8 +59,6 @@ const AdminDashboard = () => {
if (!isMounted.current) return;
const data = response?.data?.summary || {};
- const selectedQuarterFromApi = response?.data?.selected_quarter || quarter || 'All';
- const selectedYearFromApi = response?.data?.selected_year || year || 'All';
const quarterlyWindowsData = response?.data?.quarterly_windows || {};
console.log("Fetching data for:", { quarter, year });
@@ -80,9 +79,8 @@ const AdminDashboard = () => {
grace_periods_days: quarterlyWindowsData.grace_periods_days || 0
}));
- // Only update these if they're different to prevent unnecessary re-renders
- setSelectedQuarter(prev => prev !== selectedQuarterFromApi ? selectedQuarterFromApi : prev);
- setSelectedYear(prev => prev !== selectedYearFromApi ? selectedYearFromApi : prev);
+ // Keep the selected values as they are
+ // The dropdowns will maintain their current selection
} catch (error) {
console.error('Error fetching dashboard summary:', error);
} finally {
@@ -121,11 +119,11 @@ const AdminDashboard = () => {
onChange={(e) => setSelectedQuarter(e.target.value)}
className="border border-[#D0D5DD] rounded-md h-8 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
>
- {/* All */}
- Q1
- Q2
- Q3
- Q4
+ All
+ Q1
+ Q2
+ Q3
+ Q4
{
onChange={(e) => setSelectedYear(e.target.value)}
className="border border-[#D0D5DD] rounded-md h-8 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
>
+ All
{Array.from(
{ length: new Date().getFullYear() - 2021 },
(_, i) => new Date().getFullYear() - i
diff --git a/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
index 9434950..d1e0ab5 100644
--- a/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
+++ b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
@@ -311,21 +311,14 @@ const ValidationReview = () => {
}) : [];
return (
-
+
-
-
- Dashboard
-
-
+
- Manage Submissions
-
+ Manage Submissions
-
- Submission Details
-
+ View Submission
@@ -587,8 +580,13 @@ const ValidationReview = () => {
+
+ {/*
+ Please refer to the screenshot
+
*/}
+
@@ -682,6 +680,50 @@ const ValidationReview = () => {
)}
+
+ {/* Fixed bottom action bar */}
+
+
+ setIsRejectOpen(true)}
+ disabled={actionLoading || submissionData?.status === 'Rejected'}
+ >
+ {submissionData?.status === 'Rejected' ? 'Already Rejected' : 'Reject'}
+
+
+ {actionLoading ? 'Processing...' : submissionData?.status === 'Approved' ? 'Already Approved' : 'Approve'}
+
+
+
+
+ {/* Fixed bottom action bar */}
+
+
+ setIsRejectOpen(true)}
+ disabled={actionLoading || submissionData?.status === 'Rejected'}
+ >
+ {submissionData?.status === 'Rejected' ? 'Already Rejected' : 'Reject'}
+
+
+ {actionLoading ? 'Processing...' : submissionData?.status === 'Approved' ? 'Already Approved' : 'Approve'}
+
+
+
);
};
diff --git a/ipi-survey-platform/src/pages/Admin/Validations.jsx b/ipi-survey-platform/src/pages/Admin/Validations.jsx
index f35883d..8f8b3c4 100644
--- a/ipi-survey-platform/src/pages/Admin/Validations.jsx
+++ b/ipi-survey-platform/src/pages/Admin/Validations.jsx
@@ -15,16 +15,16 @@ const STATUS_VARIANTS = {
Pending: { background: '#E7F5FF', text: '#286CFF', border: '#E7F5FF' },
Rejected: { background: '#FEF2F2', text: '#B52520', border: '#FEF2F2' },
Resubmitted: { background: '#F9F7ED', text: '#7C5E24', border: '#FEF2F2' },
- Submitted: { background: '#E7F5FF', text: '#003CFF', border: '#FEF2F2' },
+ Submitted: { background: '#E7F5FF', text: '#003CFF', border: '#E7F5FF' },
};
const SUMMARY_VARIANTS = {
total: { background: '#F9F7ED', label: '#7C5E24', value: '#232528' },
approved: { background: '#F3FAF4', label: '#2F663C', value: '#232528' },
- pending: { background: '#E7F5FF', label: '#003CFF', value: '#232528' },
+ // pending: { background: '#E7F5FF', label: '#003CFF', value: '#232528' },
+ submitted: { background: '#E7F5FF', text: '#003CFF', value: '#232528',border: '#FEF2F2' },
rejected: { background: '#FEF2F2', label: '#B52520', value: '#232528' },
resubmitted: { background: '#F9F7ED', text: '#7C5E24', border: '#FEF2F2' },
- submitted: { background: '#E7F5FF', text: '#003CFF', border: '#FEF2F2' },
};
@@ -281,7 +281,7 @@ React.useEffect(() => {
}
}, [location.state, showToast]);
- const fetchDashboardData = async (quarter, year, page = 1, limit = 10) => {
+ const fetchDashboardData = async (quarter, year, page = 1, limit = 10000) => {
try {
const params = {
quarter: quarter && quarter !== 'All' ? quarter : undefined,
@@ -290,12 +290,9 @@ React.useEffect(() => {
limit
};
- console.log('Fetching with params:', params);
const result = await getSubmissions(params);
- console.log('API Response:', result);
// Check if result has a data property or is the data array itself
const data = Array.isArray(result) ? result : (result?.data || []);
- console.log('Processed data:', data);
return data;
} catch (error) {
console.error('Error fetching dashboard data:', error);
@@ -402,7 +399,6 @@ React.useEffect(() => {
return matches;
});
- console.log('Filtered items count:', filteredItems.length);
return filteredItems;
}, [submissions, search, year, quarter, emirate, status]);
@@ -410,8 +406,9 @@ React.useEffect(() => {
() => ({
total: submissions.length,
approved: submissions.filter((i) => i.status === 'Approved').length,
- pending: submissions.filter((i) => i.status === 'Pending').length,
+ submitted: submissions.filter((i) => i.status === 'Submitted').length,
rejected: submissions.filter((i) => i.status === 'Rejected').length,
+ resubmitted: submissions.filter((i) => i.status === 'Resubmitted').length,
}),
[submissions]
);
@@ -829,6 +826,10 @@ React.useEffect(() => {
{showRejectError && !rejectReason.trim() && (
Reason for rejection is required
)}
+
+ Please refer to the screenshot
+
+
} - Response data
*/
export const getSubmissions = async (params = {}) => {
@@ -16,7 +16,7 @@ export const getSubmissions = async (params = {}) => {
quarter = '',
year = '',
page = 1,
- limit = 100,
+ limit = 10000,
...restParams
} = params;