@@ -359,4 +498,4 @@ const ValidationReview = () => {
);
};
-export default ValidationReview;
+export default ValidationReview;
\ No newline at end of file
diff --git a/ipi-survey-platform/src/pages/Admin/Validations.jsx b/ipi-survey-platform/src/pages/Admin/Validations.jsx
index b713edd..de0fcb6 100644
--- a/ipi-survey-platform/src/pages/Admin/Validations.jsx
+++ b/ipi-survey-platform/src/pages/Admin/Validations.jsx
@@ -4,9 +4,9 @@ import AdminHeader from '@/components/admin/AdminHeader';
import Table from '@/components/common/Table';
import { SelectField } from '@/components/common/FormControls';
import { getSubmissions } from '@/services/admin/submission';
-
+import { CheckCircle2, XCircle, Eye } from 'lucide-react';
+import Footer from '@/components/common/Footer';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
-const downloadIconSrc = '/assets/images/downloadsimple.svg';
const caretUpSrc = '/assets/images/caret-up.svg';
const STATUS_VARIANTS = {
@@ -22,6 +22,21 @@ const SUMMARY_VARIANTS = {
rejected: { background: '#FEF2F2', label: '#B52520', value: '#232528' },
};
+const formatDateTime = (value) => {
+ if (!value) return '—';
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return '—';
+ const formatted = date.toLocaleString('en-GB', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: true,
+ });
+ return formatted.replace(/\s?(am|pm)$/i, (m) => m.toUpperCase());
+};
+
const Badge = ({ children, status }) => {
const variant = STATUS_VARIANTS[status] || { background: '#F2F4F7', text: '#475467', border: '#EAECF0' };
return (
@@ -39,7 +54,6 @@ const ManageSubmissions = () => {
const [submissions, setSubmissions] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
-
const [search, setSearch] = React.useState('');
const [year, setYear] = React.useState('');
const [quarter, setQuarter] = React.useState('');
@@ -48,31 +62,26 @@ const ManageSubmissions = () => {
const [currentPage, setCurrentPage] = React.useState(1);
const pageSize = 10;
- // 🔹 Fetch data from backend
React.useEffect(() => {
const fetchSubmissions = async () => {
try {
const result = await getSubmissions();
-
- // Handle success or direct data array
const data = result?.status === 'success' ? result.data : result;
-
- // Map API fields to table fields
+
const mapped = (data || []).map((item) => ({
id: item.id,
- establishment: '', // blank
- emirate: '', // blank
- year: String(item.year ?? ''),
- quarter: item.quarter ?? '',
- products: '', // blank
- submittedBy: item.created_by ?? '',
- submittedAt: item.created_at
- ? new Date(item.created_at).toLocaleString()
- : '',
- status: item.status ?? '',
- reviewer: '', // blank
+ establishment: item?.establishment?.factory_name || '-',
+ emirate: item?.establishment?.establishment_emirate?.name || '-',
+ year: String(item.year ?? '-'),
+ quarter: item.quarter ?? '-',
+ products: item?.product_count || '-',
+ submittedBy: item.created_by ?? '-',
+ submittedAt: formatDateTime(item.created_at),
+ status: item.status ?? '-',
+ reviewer: item?.updated_by || '-',
+ reviewerOn: formatDateTime(item?.updated_at),
}));
-
+
setSubmissions(mapped);
} catch (err) {
console.error('Failed to load submissions', err);
@@ -81,9 +90,8 @@ const ManageSubmissions = () => {
setLoading(false);
}
};
-
fetchSubmissions();
- }, []);
+ }, []);
const yearOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.year))], [submissions]);
const quarterOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.quarter))], [submissions]);
@@ -92,10 +100,10 @@ const ManageSubmissions = () => {
const filtered = React.useMemo(() => {
return submissions.filter((item) => {
- const matchSearch = search
- ? item.establishment?.toLowerCase().includes(search.toLowerCase()) ||
- item.submittedBy?.toLowerCase().includes(search.toLowerCase())
- : true;
+ const matchSearch =
+ !search ||
+ item.establishment?.toLowerCase().includes(search.toLowerCase()) ||
+ item.submittedBy?.toLowerCase().includes(search.toLowerCase());
const matchYear = !year || year === 'All' || item.year === year;
const matchQuarter = !quarter || quarter === 'All' || item.quarter === quarter;
const matchEmirate = !emirate || emirate === 'All' || item.emirate === emirate;
@@ -104,12 +112,15 @@ const ManageSubmissions = () => {
});
}, [submissions, search, year, quarter, emirate, status]);
- const summaryCounts = React.useMemo(() => ({
- total: submissions.length,
- approved: submissions.filter((i) => i.status === 'Approved').length,
- pending: submissions.filter((i) => i.status === 'Pending').length,
- rejected: submissions.filter((i) => i.status === 'Rejected').length,
- }), [submissions]);
+ const summaryCounts = React.useMemo(
+ () => ({
+ total: submissions.length,
+ approved: submissions.filter((i) => i.status === 'Approved').length,
+ pending: submissions.filter((i) => i.status === 'Pending').length,
+ rejected: submissions.filter((i) => i.status === 'Rejected').length,
+ }),
+ [submissions]
+ );
const headers = [
'Establishment',
@@ -121,6 +132,7 @@ const ManageSubmissions = () => {
'Submission Date & Time',
'Status',
'Reviewer',
+ 'Reviewer On',
'Actions',
];
@@ -134,30 +146,11 @@ const ManageSubmissions = () => {
item.submittedAt,
item.status,
item.reviewer,
- item.id,
+ item.reviewerOn,
+ item,
]);
- const columnWidths = [302, 105, 83, 91, 100, 152, 208, 120, 104, 119];
-
- if (loading) {
- return (
-
-
- Loading submissions...
-
- );
- }
-
- if (error) {
- return (
-
- {error}
-
- );
- }
+ const columnWidths = [302, 105, 83, 91, 100, 152, 208, 120, 104, 140, 130];
const toolbar = (
@@ -184,7 +177,21 @@ const ManageSubmissions = () => {
);
+ if (loading)
+ return (
+
+
+ Loading submissions...
+
+ );
+
+ if (error) return
{error}
;
+
return (
+ <>
@@ -192,34 +199,27 @@ const ManageSubmissions = () => {
Dashboard

-
Manage Submissions
+
Manage Submissions
- {/* Summary bar */}
-
- {Object.entries(summaryCounts).map(([key, val]) => {
- const v = SUMMARY_VARIANTS[key];
- return (
-
-
- {key.charAt(0).toUpperCase() + key.slice(1)}:
-
-
- {val}
-
-
- );
- })}
-
+
+
+ {Object.entries(summaryCounts).map(([key, val]) => {
+ const v = SUMMARY_VARIANTS[key];
+ return (
+
+
+ {key.charAt(0).toUpperCase() + key.slice(1)}:
+
+
+ {val}
+
+
+ );
+ })}
+
- {/* Table */}
{
pageSize,
totalItems: filtered.length,
}}
- renderCell={(value, rowIndex, columnIndex) => {
+ renderCell={(value, _, columnIndex) => {
if (columnIndex === 7)
- return {value}
;
- if (columnIndex === 9)
return (
-
+
+ {value}
+
);
+
+ if (columnIndex === 10) {
+ const item = value;
+ return (
+
+
+
+
+ Approve
+
+
+
+
+
+ Reject
+
+
+
+ navigate(`/admin/validations/${item.id}`)}
+ />
+
+ View
+
+
+
+ );
+ }
return value;
}}
/>
+
+ >
);
};
diff --git a/ipi-survey-platform/src/pages/Login/Login.jsx b/ipi-survey-platform/src/pages/Login/Login.jsx
index 66ccd99..e25a105 100644
--- a/ipi-survey-platform/src/pages/Login/Login.jsx
+++ b/ipi-survey-platform/src/pages/Login/Login.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { login } from '@/services/auth/authService.js';
-
+import { RefreshCw } from "lucide-react";
const logoSrc = '/assets/images/FCSCLogo.svg';
const Login = () => {
@@ -277,12 +277,13 @@ const Login = () => {
{captcha}
+ 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 6b656f3..26a120b 100644
--- a/ipi-survey-platform/src/pages/PublicContactForm.jsx
+++ b/ipi-survey-platform/src/pages/PublicContactForm.jsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import apiClient from '@/services/api/apiClient';
-
+import { RefreshCw } from "lucide-react";
const logoSrc = '/assets/images/FCSCLogo.svg';
const PublicContactForm = () => {
@@ -266,14 +266,18 @@ const PublicContactForm = () => {
{captcha}
+ 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}
+