diff --git a/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
index bddb4e3..a9466ff 100644
--- a/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
+++ b/ipi-survey-platform/src/pages/Admin/ValidationReview.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import AdminHeader from '@/components/admin/AdminHeader';
import { getSubmissionById } from '@/services/admin/submission';
+import apiClient from '@/services/api/apiClient';
const caretUpSrc = '/assets/images/caret-up.svg';
const establishmentIconSrc = '/assets/images/Establishment.svg';
@@ -25,10 +26,10 @@ const employmentIcons = {
const ValidationReview = () => {
const navigate = useNavigate();
const { id } = useParams();
- const [toast, setToast] = React.useState(null);
const [isRejectOpen, setIsRejectOpen] = React.useState(false);
const [rejectReason, setRejectReason] = React.useState('');
const [loading, setLoading] = React.useState(true);
+ const [actionLoading, setActionLoading] = React.useState(false);
const [error, setError] = React.useState(null);
const [submissionData, setSubmissionData] = React.useState(null);
@@ -71,22 +72,69 @@ const ValidationReview = () => {
fetchSubmission();
}, [id]);
- const showApproveToast = () => {
- const establishmentName = submissionData?.establishment?.factory_name || 'Establishment';
- setToast({ type: 'success', text: `${establishmentName} has been approved successfully.` });
- window.clearTimeout(showApproveToast._t);
- showApproveToast._t = window.setTimeout(() => setToast(null), 4000);
+ const handleApprove = async () => {
+ if (actionLoading) return;
+
+ setActionLoading(true);
+ try {
+ const response = await apiClient.put(`/approveOrRejectSubmission/${id}`, {
+ status: 'Approved',
+ remarks: ''
+ });
+
+ if (response?.data || response?.status === 'success') {
+ const establishmentName = submissionData?.establishment?.factory_name || 'Establishment';
+ navigate('/admin/validations', {
+ state: {
+ showSuccessToast: true,
+ successMessage: `${establishmentName} has been approved successfully.`
+ }
+ });
+ } else {
+ throw new Error('Failed to approve submission');
+ }
+ } catch (err) {
+ console.error('Approval failed:', err);
+ alert(err.response?.data?.message || err.message || 'Failed to approve submission. Please try again.');
+ } finally {
+ setActionLoading(false);
+ }
};
- const handleRejectSubmit = () => {
- setToast({
- type: 'info',
- text: rejectReason ? `Submission rejected. Reason: ${rejectReason}` : 'Submission rejected.'
- });
- window.clearTimeout(showApproveToast._t);
- showApproveToast._t = window.setTimeout(() => setToast(null), 4000);
- setIsRejectOpen(false);
- setRejectReason('');
+ const handleRejectSubmit = async () => {
+ if (actionLoading) return;
+
+ if (!rejectReason.trim()) {
+ alert('Please provide a reason for rejection.');
+ return;
+ }
+
+ setActionLoading(true);
+ try {
+ const response = await apiClient.put(`/approveOrRejectSubmission/${id}`, {
+ status: 'Rejected',
+ remarks: rejectReason.trim()
+ });
+
+ if (response?.data || response?.status === 'success') {
+ const establishmentName = submissionData?.establishment?.factory_name || 'Establishment';
+ navigate('/admin/validations', {
+ state: {
+ showSuccessToast: true,
+ successMessage: `${establishmentName} has been rejected successfully.`
+ }
+ });
+ } else {
+ throw new Error('Failed to reject submission');
+ }
+ } catch (err) {
+ console.error('Rejection failed:', err);
+ alert(err.response?.data?.message || err.message || 'Failed to reject submission. Please try again.');
+ } finally {
+ setActionLoading(false);
+ setIsRejectOpen(false);
+ setRejectReason('');
+ }
};
if (loading) {
@@ -253,22 +301,6 @@ const ValidationReview = () => {
- {toast && (
-
-
-
-
- )}
-
@@ -454,8 +488,12 @@ const ValidationReview = () => {
@@ -469,26 +507,29 @@ const ValidationReview = () => {
placeholder="Enter the reason for rejection..."
value={rejectReason}
onChange={(event) => setRejectReason(event.target.value)}
+ disabled={actionLoading}
/>
diff --git a/ipi-survey-platform/src/pages/Admin/Validations.jsx b/ipi-survey-platform/src/pages/Admin/Validations.jsx
index de0fcb6..e0a4da1 100644
--- a/ipi-survey-platform/src/pages/Admin/Validations.jsx
+++ b/ipi-survey-platform/src/pages/Admin/Validations.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Link, useNavigate } from 'react-router-dom';
+import { Link, useNavigate, useLocation } from 'react-router-dom';
import AdminHeader from '@/components/admin/AdminHeader';
import Table from '@/components/common/Table';
import { SelectField } from '@/components/common/FormControls';
@@ -51,6 +51,7 @@ const Badge = ({ children, status }) => {
const ManageSubmissions = () => {
const navigate = useNavigate();
+ const location = useLocation();
const [submissions, setSubmissions] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
@@ -60,8 +61,47 @@ const ManageSubmissions = () => {
const [emirate, setEmirate] = React.useState('');
const [status, setStatus] = React.useState('');
const [currentPage, setCurrentPage] = React.useState(1);
+ const [toast, setToast] = React.useState(null);
const pageSize = 10;
+ const toastTimeoutRef = React.useRef(null);
+
+ const showToast = React.useCallback((type, message) => {
+ if (!message) return;
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast({ type, message });
+ toastTimeoutRef.current = window.setTimeout(() => {
+ setToast(null);
+ toastTimeoutRef.current = null;
+ }, 4000);
+ }, []);
+
+ const closeToast = React.useCallback(() => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast(null);
+ }, []);
+
+ React.useEffect(() => {
+ return () => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ React.useEffect(() => {
+ if (location.state?.showSuccessToast && location.state?.successMessage) {
+ showToast('success', location.state.successMessage);
+ window.history.replaceState({}, document.title);
+ }
+ }, [location.state, showToast]);
+
React.useEffect(() => {
const fetchSubmissions = async () => {
try {
@@ -194,6 +234,30 @@ const ManageSubmissions = () => {
<>
+
+ {toast && (
+
+
+
+ {toast.message}
+
+
+
+
+ )}
+
-
-
-
setPassword(e.target.value)}
- placeholder="Enter password"
- className="block w-full h-10 rounded-md border-2 border-[#92722A] focus:border-[#92722A] focus:ring-0 px-4 pr-11 text-sm bg-white"
- required
- />
-
-
-
+
+
+
setPassword(e.target.value)}
+ placeholder="Enter password"
+ className="block w-full h-10 rounded-md border-2 border-[#92722A] focus:border-[#92722A] focus:ring-0 px-4 pr-11 text-sm bg-white"
+ required
+ />
+
+
+
+ type="button"
+ onClick={generateCaptcha}
+ className="flex items-center gap-1 text-sm text-[#92722A] hover:underline"
+ >
+
+ Refresh
+
If you cannot read the code, click "Refresh" or type:{" "}
diff --git a/ipi-survey-platform/src/pages/PublicContactForm.jsx b/ipi-survey-platform/src/pages/PublicContactForm.jsx
index 26a120b..606715e 100644
--- a/ipi-survey-platform/src/pages/PublicContactForm.jsx
+++ b/ipi-survey-platform/src/pages/PublicContactForm.jsx
@@ -11,9 +11,6 @@ const PublicContactForm = () => {
establishmentName: "",
establishmentId: "",
email: "",
- // contactName: "",
- // contactPhone: "",
- // notes: "",
});
const [errors, setErrors] = useState({});
@@ -28,6 +25,9 @@ const PublicContactForm = () => {
const [otp, setOtp] = useState("");
const [otpMsg, setOtpMsg] = useState("");
const [otpError, setOtpError] = useState("");
+ const [toast, setToast] = useState(null);
+
+ const toastTimeoutRef = React.useRef(null);
const generateCaptcha = () => {
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
@@ -42,6 +42,35 @@ const PublicContactForm = () => {
generateCaptcha();
}, []);
+ useEffect(() => {
+ return () => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ const showToast = (type, message) => {
+ if (!message) return;
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast({ type, message });
+ toastTimeoutRef.current = window.setTimeout(() => {
+ setToast(null);
+ toastTimeoutRef.current = null;
+ }, 4000);
+ };
+
+ const closeToast = () => {
+ if (toastTimeoutRef.current) {
+ window.clearTimeout(toastTimeoutRef.current);
+ toastTimeoutRef.current = null;
+ }
+ setToast(null);
+ };
+
const validate = (data = formData) => {
const newErrors = {};
@@ -49,33 +78,12 @@ const PublicContactForm = () => {
if (!data.email.trim()) newErrors.email = "Registered Email is required.";
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email))
newErrors.email = "Enter a valid email address.";
- // if (!data.contactName.trim()) newErrors.contactName = "Contact Person Name is required.";
- // if (!data.contactPhone.trim()) {
- // newErrors.contactPhone = "Contact Phone is required.";
- // } else if (!/^\d{10,12}$/.test(data.contactPhone)) {
- // newErrors.contactPhone = "Enter a valid phone number (10–12 digits).";
- // }
setErrors(newErrors);
setIsValid(Object.keys(newErrors).length === 0);
return newErrors;
};
-// const handleNameChange = (e) => {
-// const { value } = e.target;
-// const filtered = value.replace(/[^A-Za-z\s]/g, "");
-// setFormData((prev) => ({ ...prev, contactName: filtered }));
-// validate({ ...formData, contactName: filtered });
-// };
-
-// const handlePhoneChange = (e) => {
-// let { value } = e.target;
-// value = value.replace(/\D/g, "");
-// if (value.length > 12) value = value.slice(0, 12);
-// setFormData((prev) => ({ ...prev, contactPhone: value }));
-// validate({ ...formData, contactPhone: value });
-// };
-
const handleChange = (e) => {
const { name, value } = e.target;
const updated = { ...formData, [name]: value };
@@ -113,9 +121,12 @@ const PublicContactForm = () => {
const response = await apiClient.post("/forgot-password/request-otp", payload);
if (response?.data) {
- navigate("/change-password", {
- state: { email: formData.email.trim() },
- });
+ showToast('success', 'OTP sent to your registered email successfully!');
+ setTimeout(() => {
+ navigate("/change-password", {
+ state: { email: formData.email.trim() },
+ });
+ }, 1500);
} else {
setErrorMsg("Something went wrong. Please try again.");
}
@@ -165,7 +176,6 @@ const PublicContactForm = () => {
setLoading(false);
}
};
-
const handleResendOtp = () => {
setOtpMsg("A new OTP has been sent to your registered email.");
@@ -174,6 +184,29 @@ const PublicContactForm = () => {
return (
+ {toast && (
+
+
+
+ {toast.message}
+
+
+
+
+ )}
+
@@ -210,8 +243,9 @@ const PublicContactForm = () => {
error={errors.establishmentName}
/>
@@ -224,35 +258,6 @@ const PublicContactForm = () => {
onChange={handleChange}
error={errors.email}
/>
- {/*
-
-
-
-
-
*/}
+ type="button"
+ onClick={generateCaptcha}
+ className="flex items-center gap-1 text-sm text-[#92722A] hover:underline"
+ >
+
+ Refresh
+
- If you cannot read the code, click "Refresh" or type:{" "}
- {captcha}
-
+ If you cannot read the code, click "Refresh" or type:{" "}
+
{captcha}
+