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 && ( -
-
- - - - {toast.text} -
- -
- )} -
@@ -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} +